Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nuxt): enable component islands when server pages/components are present #26223

Merged
merged 2 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 0 additions & 4 deletions docs/2.guide/2.directory-structure/1.pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,10 +365,6 @@ You can define a page as [client only](/docs/guide/directory-structure/component

You can define a page as [server only](/docs/guide/directory-structure/components#server-components) by giving it a `.server.vue` suffix. While you will be able to navigate to the page using client-side navigation, controlled by `vue-router`, it will be rendered with a server component automatically, meaning the code required to render the page will not be in your client-side bundle.

::note
You will also need to enable `experimental.componentIslands` in order to make this possible.
::

## Custom Routing

As your app gets bigger and more complex, your routing might require more flexibility. For this reason, Nuxt directly exposes the router, routes and router options for customization in different ways.
Expand Down
4 changes: 0 additions & 4 deletions docs/3.api/1.components/8.nuxt-island.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ When rendering an island component, the content of the island component is stati

Changing the island component props triggers a refetch of the island component to re-render it again.

::read-more{to="/docs/guide/going-further/experimental-features#componentislands" icon="i-ph-star-duotone"}
This component is experimental and in order to use it you must enable the `experimental.componentIslands` option in your `nuxt.config`.
::

::note
Global styles of your application are sent with the response.
::
Expand Down
3 changes: 3 additions & 0 deletions packages/nuxt/src/components/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ export default defineNuxtModule<ComponentsOptions>({
chunkName: 'components/' + component.kebabName
})
}
if (component.mode === 'server' && !nuxt.options.ssr) {
logger.warn(`Using server components with \`ssr: false\` is not supported with auto-detected component islands. If you need to use server component \`${component.pascalName}\`, set \`experimental.componentIslands\` to \`true\`.`)
}
}
context.components = newComponents
app.components = newComponents
Expand Down
1 change: 0 additions & 1 deletion packages/nuxt/src/core/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
'process.env.NUXT_NO_SCRIPTS': !!nuxt.options.features.noScripts && !nuxt.options.dev,
'process.env.NUXT_INLINE_STYLES': !!nuxt.options.features.inlineStyles,
'process.env.NUXT_JSON_PAYLOADS': !!nuxt.options.experimental.renderJsonPayloads,
'process.env.NUXT_COMPONENT_ISLANDS': !!nuxt.options.experimental.componentIslands,
'process.env.NUXT_ASYNC_CONTEXT': !!nuxt.options.experimental.asyncContext,
'process.env.NUXT_SHARED_DATA': !!nuxt.options.experimental.sharedPrerenderData,
'process.dev': nuxt.options.dev,
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt/src/core/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ async function initNuxt (nuxt: Nuxt) {
filePath: resolve(nuxt.options.appDir, 'components/nuxt-island')
})

if (!nuxt.options.ssr) {
if (!nuxt.options.ssr && nuxt.options.experimental.componentIslands !== 'auto') {
nuxt.options.ssr = true
nuxt.options.nitro.routeRules ||= {}
nuxt.options.nitro.routeRules['/**'] = defu(nuxt.options.nitro.routeRules['/**'], { ssr: false })
Expand Down
6 changes: 3 additions & 3 deletions packages/nuxt/src/core/runtime/nitro/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import unheadPlugins from '#internal/unhead-plugins.mjs'
// eslint-disable-next-line import/no-restricted-paths
import type { NuxtPayload, NuxtSSRContext } from '#app'
// @ts-expect-error virtual file
import { appHead, appRootId, appRootTag, appTeleportId, appTeleportTag } from '#internal/nuxt.config.mjs'
import { appHead, appRootId, appRootTag, appTeleportId, appTeleportTag, componentIslands } from '#internal/nuxt.config.mjs'
// @ts-expect-error virtual file
import { buildAssetsURL, publicAssetsURL } from '#paths'

Expand Down Expand Up @@ -263,7 +263,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
}

// Check for island component rendering
const isRenderingIsland = (process.env.NUXT_COMPONENT_ISLANDS as unknown as boolean && event.path.startsWith('/__nuxt_island'))
const isRenderingIsland = (componentIslands as unknown as boolean && event.path.startsWith('/__nuxt_island'))
const islandContext = isRenderingIsland ? await getIslandContext(event) : undefined

if (import.meta.prerender && islandContext && event.path && await islandCache!.hasItem(event.path)) {
Expand Down Expand Up @@ -468,7 +468,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
bodyAttrs: bodyAttrs ? [bodyAttrs] : [],
bodyPrepend: normalizeChunks([bodyTagsOpen, ssrContext.teleports?.body]),
body: [
process.env.NUXT_COMPONENT_ISLANDS ? replaceIslandTeleports(ssrContext, _rendered.html) : _rendered.html,
componentIslands ? replaceIslandTeleports(ssrContext, _rendered.html) : _rendered.html,
APP_TELEPORT_OPEN_TAG + (HAS_APP_TELEPORTS ? joinTags([ssrContext.teleports?.[`#${appTeleportId}`]]) : '') + APP_TELEPORT_CLOSE_TAG
],
bodyAppend: [bodyTags]
Expand Down
5 changes: 4 additions & 1 deletion packages/nuxt/src/core/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,10 +374,13 @@ export const nuxtConfigTemplate: NuxtTemplate = {
baseURL: undefined,
headers: undefined
}
const shouldEnableComponentIslands = ctx.nuxt.options.experimental.componentIslands && (
ctx.nuxt.options.dev || ctx.nuxt.options.experimental.componentIslands !== 'auto' || ctx.app.pages?.some(p => p.mode === 'server') || ctx.app.components?.some(c => c.mode === 'server')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctx.nuxt.options.experimental.componentIslands !== 'auto' we can set false to componentIsland, not sure if anyone does that but it can enable island

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the first check prevents that - ctx.nuxt.options.experimental.componentIslands &&

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh indeed !

)
return [
...Object.entries(ctx.nuxt.options.app).map(([k, v]) => `export const ${camelCase('app-' + k)} = ${JSON.stringify(v)}`),
`export const renderJsonPayloads = ${!!ctx.nuxt.options.experimental.renderJsonPayloads}`,
`export const componentIslands = ${!!ctx.nuxt.options.experimental.componentIslands}`,
`export const componentIslands = ${shouldEnableComponentIslands}`,
`export const payloadExtraction = ${!!ctx.nuxt.options.experimental.payloadExtraction}`,
`export const cookieStore = ${!!ctx.nuxt.options.experimental.cookieStore}`,
`export const appManifest = ${!!ctx.nuxt.options.experimental.appManifest}`,
Expand Down
4 changes: 4 additions & 0 deletions packages/nuxt/src/pages/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export default defineNuxtModule({
nuxt.hook('app:templates', async (app) => {
app.pages = await resolvePagesRoutes()
await nuxt.callHook('pages:extend', app.pages)

if (!nuxt.options.ssr && app.pages.some(p => p.mode === 'server')) {
logger.warn('Using server pages with `ssr: false` is not supported with auto-detected component islands. Set `experimental.componentIslands` to `true`.')
}
})

// Restart Nuxt when pages dir is added or removed
Expand Down
7 changes: 5 additions & 2 deletions packages/schema/src/config/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,10 @@ export default defineUntypedSchema({

/**
* Experimental component islands support with <NuxtIsland> and .island.vue files.
* @type {true | 'local' | 'local+remote' | Partial<{ remoteIsland: boolean, selectiveClient: boolean }> | false}
*
* By default it is set to 'auto', which means it will be enabled only when there are islands,
* server components or server pages in your app.
* @type {true | 'auto' | 'local' | 'local+remote' | Partial<{ remoteIsland: boolean, selectiveClient: boolean }> | false}
*/
componentIslands: {
$resolve: (val) => {
Expand All @@ -182,7 +185,7 @@ export default defineUntypedSchema({
if (val === 'local') {
return true
}
return val ?? false
return val ?? 'auto'
}
},

Expand Down