feat(nuxt): enable islands if server pages/components present (#26223)

This commit is contained in:
Daniel Roe 2024-03-13 07:39:35 -07:00 committed by GitHub
parent f080c426a2
commit 9bfd988ca6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 20 additions and 16 deletions

View File

@ -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. 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 ## 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. 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.

View File

@ -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. 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 ::note
Global styles of your application are sent with the response. Global styles of your application are sent with the response.
:: ::

View File

@ -176,6 +176,9 @@ export default defineNuxtModule<ComponentsOptions>({
chunkName: 'components/' + component.kebabName 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 context.components = newComponents
app.components = newComponents app.components = newComponents

View File

@ -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_NO_SCRIPTS': !!nuxt.options.features.noScripts && !nuxt.options.dev,
'process.env.NUXT_INLINE_STYLES': !!nuxt.options.features.inlineStyles, 'process.env.NUXT_INLINE_STYLES': !!nuxt.options.features.inlineStyles,
'process.env.NUXT_JSON_PAYLOADS': !!nuxt.options.experimental.renderJsonPayloads, '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_ASYNC_CONTEXT': !!nuxt.options.experimental.asyncContext,
'process.env.NUXT_SHARED_DATA': !!nuxt.options.experimental.sharedPrerenderData, 'process.env.NUXT_SHARED_DATA': !!nuxt.options.experimental.sharedPrerenderData,
'process.dev': nuxt.options.dev, 'process.dev': nuxt.options.dev,

View File

@ -314,7 +314,7 @@ async function initNuxt (nuxt: Nuxt) {
filePath: resolve(nuxt.options.appDir, 'components/nuxt-island') 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.ssr = true
nuxt.options.nitro.routeRules ||= {} nuxt.options.nitro.routeRules ||= {}
nuxt.options.nitro.routeRules['/**'] = defu(nuxt.options.nitro.routeRules['/**'], { ssr: false }) nuxt.options.nitro.routeRules['/**'] = defu(nuxt.options.nitro.routeRules['/**'], { ssr: false })

View File

@ -29,7 +29,7 @@ import unheadPlugins from '#internal/unhead-plugins.mjs'
// eslint-disable-next-line import/no-restricted-paths // eslint-disable-next-line import/no-restricted-paths
import type { NuxtPayload, NuxtSSRContext } from '#app' import type { NuxtPayload, NuxtSSRContext } from '#app'
// @ts-expect-error virtual file // @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 // @ts-expect-error virtual file
import { buildAssetsURL, publicAssetsURL } from '#paths' import { buildAssetsURL, publicAssetsURL } from '#paths'
@ -263,7 +263,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
} }
// Check for island component rendering // 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 const islandContext = isRenderingIsland ? await getIslandContext(event) : undefined
if (import.meta.prerender && islandContext && event.path && await islandCache!.hasItem(event.path)) { if (import.meta.prerender && islandContext && event.path && await islandCache!.hasItem(event.path)) {
@ -468,7 +468,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
bodyAttrs: bodyAttrs ? [bodyAttrs] : [], bodyAttrs: bodyAttrs ? [bodyAttrs] : [],
bodyPrepend: normalizeChunks([bodyTagsOpen, ssrContext.teleports?.body]), bodyPrepend: normalizeChunks([bodyTagsOpen, ssrContext.teleports?.body]),
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 APP_TELEPORT_OPEN_TAG + (HAS_APP_TELEPORTS ? joinTags([ssrContext.teleports?.[`#${appTeleportId}`]]) : '') + APP_TELEPORT_CLOSE_TAG
], ],
bodyAppend: [bodyTags] bodyAppend: [bodyTags]

View File

@ -374,10 +374,13 @@ export const nuxtConfigTemplate: NuxtTemplate = {
baseURL: undefined, baseURL: undefined,
headers: 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')
)
return [ return [
...Object.entries(ctx.nuxt.options.app).map(([k, v]) => `export const ${camelCase('app-' + k)} = ${JSON.stringify(v)}`), ...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 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 payloadExtraction = ${!!ctx.nuxt.options.experimental.payloadExtraction}`,
`export const cookieStore = ${!!ctx.nuxt.options.experimental.cookieStore}`, `export const cookieStore = ${!!ctx.nuxt.options.experimental.cookieStore}`,
`export const appManifest = ${!!ctx.nuxt.options.experimental.appManifest}`, `export const appManifest = ${!!ctx.nuxt.options.experimental.appManifest}`,

View File

@ -79,6 +79,10 @@ export default defineNuxtModule({
nuxt.hook('app:templates', async (app) => { nuxt.hook('app:templates', async (app) => {
app.pages = await resolvePagesRoutes() app.pages = await resolvePagesRoutes()
await nuxt.callHook('pages:extend', app.pages) 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 // Restart Nuxt when pages dir is added or removed

View File

@ -172,7 +172,10 @@ export default defineUntypedSchema({
/** /**
* Experimental component islands support with <NuxtIsland> and .island.vue files. * 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: { componentIslands: {
$resolve: (val) => { $resolve: (val) => {
@ -182,7 +185,7 @@ export default defineUntypedSchema({
if (val === 'local') { if (val === 'local') {
return true return true
} }
return val ?? false return val ?? 'auto'
} }
}, },