From d50a416304bf6037df13d97ac0f1194c341abb87 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Mon, 14 Aug 2023 22:33:00 +0300 Subject: [PATCH] feat(nuxt): add `experimental.headNext` unhead integration (#22620) --- packages/nuxt/package.json | 6 +- .../nuxt/src/core/runtime/nitro/renderer.ts | 22 +++++-- packages/nuxt/src/head/module.ts | 25 +++++-- .../nuxt/src/head/runtime/plugins/capo.ts | 9 --- .../nuxt/src/head/runtime/plugins/unhead.ts | 9 ++- packages/schema/package.json | 2 +- packages/schema/src/config/experimental.ts | 8 ++- pnpm-lock.yaml | 66 +++++++++---------- test/bundle.test.ts | 8 +-- test/fixtures/basic/nuxt.config.ts | 2 +- 10 files changed, 91 insertions(+), 66 deletions(-) delete mode 100644 packages/nuxt/src/head/runtime/plugins/capo.ts diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index e7b47e80f8..e4c48ab08b 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -58,9 +58,9 @@ "@nuxt/telemetry": "^2.4.1", "@nuxt/ui-templates": "^1.3.1", "@nuxt/vite-builder": "workspace:../vite", - "@unhead/ssr": "^1.2.2", - "@unhead/vue": "^1.2.2", - "@unhead/dom": "^1.2.2", + "@unhead/ssr": "^1.3.2", + "@unhead/vue": "^1.3.2", + "@unhead/dom": "^1.3.2", "@vue/shared": "^3.3.4", "acorn": "8.10.0", "c12": "^1.4.2", diff --git a/packages/nuxt/src/core/runtime/nitro/renderer.ts b/packages/nuxt/src/core/runtime/nitro/renderer.ts index 891acb6be6..1cfda97721 100644 --- a/packages/nuxt/src/core/runtime/nitro/renderer.ts +++ b/packages/nuxt/src/core/runtime/nitro/renderer.ts @@ -17,12 +17,15 @@ import { joinURL, withoutTrailingSlash } from 'ufo' import { renderToString as _renderToString } from 'vue/server-renderer' import { hash } from 'ohash' import { renderSSRHead } from '@unhead/ssr' +import type { HeadEntryOptions } from '@unhead/schema' import { defineRenderHandler, getRouteRules, useRuntimeConfig, useStorage } from '#internal/nitro' import { useNitroApp } from '#internal/nitro/app' import type { Link, Script } from '@unhead/vue' import { createServerHead } from '@unhead/vue' +// @ts-expect-error virtual file +import unheadPlugins from '#internal/unhead-plugins.mjs' // eslint-disable-next-line import/no-restricted-paths import type { NuxtPayload, NuxtSSRContext } from '#app/nuxt' // @ts-expect-error virtual file @@ -239,8 +242,12 @@ export default defineRenderHandler(async (event): Promise { + config.virtual!['#internal/unhead-plugins.mjs'] = () => nuxt.vfs['#build/unhead-plugins'] + }) // Add library-specific plugin addPlugin({ src: resolve(runtimeDir, 'plugins/unhead') }) diff --git a/packages/nuxt/src/head/runtime/plugins/capo.ts b/packages/nuxt/src/head/runtime/plugins/capo.ts deleted file mode 100644 index 3623a51885..0000000000 --- a/packages/nuxt/src/head/runtime/plugins/capo.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CapoPlugin } from '@unhead/vue' -import { defineNuxtPlugin } from '#app/nuxt' - -export default defineNuxtPlugin({ - name: 'nuxt:head:capo', - setup (nuxtApp) { - nuxtApp.vueApp._context.provides.usehead.use(CapoPlugin({ track: true })) - } -}) diff --git a/packages/nuxt/src/head/runtime/plugins/unhead.ts b/packages/nuxt/src/head/runtime/plugins/unhead.ts index ece6af9f2a..26f82adeda 100644 --- a/packages/nuxt/src/head/runtime/plugins/unhead.ts +++ b/packages/nuxt/src/head/runtime/plugins/unhead.ts @@ -2,10 +2,17 @@ import { createHead as createClientHead } from '@unhead/vue' import { renderDOMHead } from '@unhead/dom' import { defineNuxtPlugin } from '#app/nuxt' +// @ts-expect-error virtual file +import unheadPlugins from '#build/unhead-plugins.mjs' + export default defineNuxtPlugin({ name: 'nuxt:head', setup (nuxtApp) { - const head = import.meta.server ? nuxtApp.ssrContext!.head : createClientHead() + const head = import.meta.server + ? nuxtApp.ssrContext!.head + : createClientHead({ + plugins: unheadPlugins + }) // nuxt.config appHead is set server-side within the renderer nuxtApp.vueApp.use(head) diff --git a/packages/schema/package.json b/packages/schema/package.json index 535959a435..727f5edfd1 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -30,7 +30,7 @@ "@types/file-loader": "5.0.1", "@types/pug": "2.0.6", "@types/sass-loader": "8.0.5", - "@unhead/schema": "1.2.2", + "@unhead/schema": "1.3.2", "@vitejs/plugin-vue": "4.2.3", "@vitejs/plugin-vue-jsx": "3.0.1", "@vue/compiler-core": "3.3.4", diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts index 77fade2e49..f0a261ecbf 100644 --- a/packages/schema/src/config/experimental.ts +++ b/packages/schema/src/config/experimental.ts @@ -232,10 +232,12 @@ export default defineUntypedSchema({ asyncContext: false, /** - * Add the capo.js head plugin in order to render tags in of the head in a more performant way. + * Use new experimental head optimisations: + * - Add the capo.js head plugin in order to render tags in of the head in a more performant way. + * - Uses the hash hydration plugin to reduce initial hydration * - * @see https://rviscomi.github.io/capo.js/user/rules/ + * @see https://github.com/nuxt/nuxt/discussions/22632 */ - headCapoPlugin: false + headNext: false } }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44f1ae2ec1..c82df9f924 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -365,14 +365,14 @@ importers: specifier: ^14.18.0 || >=16.10.0 version: 18.17.5 '@unhead/dom': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.3.2 + version: 1.3.2 '@unhead/ssr': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.3.2 + version: 1.3.2 '@unhead/vue': - specifier: ^1.2.2 - version: 1.2.2(vue@3.3.4) + specifier: ^1.3.2 + version: 1.3.2(vue@3.3.4) '@vue/shared': specifier: ^3.3.4 version: 3.3.4 @@ -577,8 +577,8 @@ importers: specifier: 8.0.5 version: 8.0.5 '@unhead/schema': - specifier: 1.2.2 - version: 1.2.2 + specifier: 1.3.2 + version: 1.3.2 '@vitejs/plugin-vue': specifier: 4.2.3 version: 4.2.3(vite@4.4.9)(vue@3.3.4) @@ -3239,41 +3239,41 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@unhead/dom@1.2.2: - resolution: {integrity: sha512-ohganmg4i1Dd4wwQ2A9oLWEkJNpJRoERJNmFgzmScw9Vi3zMqoS4gPIofT20zUR5rhyyAsFojuDPojJ5vKcmqw==} + /@unhead/dom@1.3.2: + resolution: {integrity: sha512-iShW0eKzS4TvL0ATtmFNyRdx4JxFKiksoUSDAgkPrMaI8EhYtryU5IL0i5TVySSk4kIjMfLgd8uElOAfUHpTsQ==} dependencies: - '@unhead/schema': 1.2.2 - '@unhead/shared': 1.2.2 + '@unhead/schema': 1.3.2 + '@unhead/shared': 1.3.2 dev: false - /@unhead/schema@1.2.2: - resolution: {integrity: sha512-cGtNvadL76eGl7QxGjWHZxFqLv9a2VrmRpeEb1d7sm0cvnN0bWngdXDTdUyXzn7RVv/Um+/yae6eiT6A+pyQOw==} + /@unhead/schema@1.3.2: + resolution: {integrity: sha512-RiJUPipN6wntwpJvHBS8+84/eQyQdnznWTY+AC3woWTUfPHz7M4Hzu6jEkdnzpTX77HQMIIuu734vohmo+L91A==} dependencies: hookable: 5.5.3 zhead: 2.0.10 - /@unhead/shared@1.2.2: - resolution: {integrity: sha512-bWRjRyVzFsunih9GbHctvS8Aenj6KBe5ycql1JE4LawBL/NRYvCYUCPpdK5poVOqjYr0yDAf9m4JGaM2HwpVLw==} + /@unhead/shared@1.3.2: + resolution: {integrity: sha512-omOLfVnSkCpiIgikGKkgW6dzs+2jncAXtmPb+/IkFSkevbEfzyQlciDL12h9ChetRXjcWBZhu+OCCw0oY8W/Nw==} dependencies: - '@unhead/schema': 1.2.2 + '@unhead/schema': 1.3.2 dev: false - /@unhead/ssr@1.2.2: - resolution: {integrity: sha512-mpWSNNbrQFJZolAfdVInPPiSGUva08bK9UbNV1zgDScUz+p+FnRg4cj77X+PpVeJ0+KPgjXfOsI8VQKYt+buYA==} + /@unhead/ssr@1.3.2: + resolution: {integrity: sha512-ygbvJcJoN6wzGZnRfc3QmBtHp1awxEy2IIPbLyqgNWmkDElUfC14xM2BvNAEybSTR/EeX8Sf492CGw+5I0RqPw==} dependencies: - '@unhead/schema': 1.2.2 - '@unhead/shared': 1.2.2 + '@unhead/schema': 1.3.2 + '@unhead/shared': 1.3.2 dev: false - /@unhead/vue@1.2.2(vue@3.3.4): - resolution: {integrity: sha512-AxOmY5JPn4fS34ovaivPnqg2my+InIkZDNSxCKfRkmbBtstFre/Fyf0d92Qfx0u8PJiSRPOjthEHx5vKDgTEJQ==} + /@unhead/vue@1.3.2(vue@3.3.4): + resolution: {integrity: sha512-pM5SbKTTTzLroVHGfqBkDA8WCVk1qVWBe8sia5Rw1pSntp99VgDawJkiCdJJbyh/bOUOvCuAgUVHH1IRpB8QmQ==} peerDependencies: vue: '>=2.7 || >=3' dependencies: - '@unhead/schema': 1.2.2 - '@unhead/shared': 1.2.2 + '@unhead/schema': 1.3.2 + '@unhead/shared': 1.3.2 hookable: 5.5.3 - unhead: 1.2.2 + unhead: 1.3.2 vue: 3.3.4 dev: false @@ -7183,7 +7183,7 @@ packages: graceful-fs: 4.2.11 /jstransformer@1.0.0: - resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} + resolution: {integrity: sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=} dependencies: is-promise: 2.2.2 promise: 7.3.1 @@ -10316,7 +10316,7 @@ packages: engines: {node: '>=0.6'} /token-stream@1.0.0: - resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==} + resolution: {integrity: sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=} dev: false /totalist@1.1.0: @@ -10532,12 +10532,12 @@ packages: node-fetch-native: 1.2.0 pathe: 1.1.1 - /unhead@1.2.2: - resolution: {integrity: sha512-9wDuiso7YWNe0BTA5NGsHR0dtqn0YrL/5+NumfuXDxxYykavc6N27pzZxTXiuvVHbod8tFicsxA6pC9WhQvzqg==} + /unhead@1.3.2: + resolution: {integrity: sha512-s4qW/Rcp6OD4GRBreAQYRD4B1ch7zhVt57IGUIGdn6xwT0tHJucHBv2GbqdpaTLmZcUOdblBIt2HXdOlbW2YHg==} dependencies: - '@unhead/dom': 1.2.2 - '@unhead/schema': 1.2.2 - '@unhead/shared': 1.2.2 + '@unhead/dom': 1.3.2 + '@unhead/schema': 1.3.2 + '@unhead/shared': 1.3.2 hookable: 5.5.3 dev: false diff --git a/test/bundle.test.ts b/test/bundle.test.ts index 3e7f4e6e2b..a7af0143f9 100644 --- a/test/bundle.test.ts +++ b/test/bundle.test.ts @@ -19,7 +19,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM for (const outputDir of ['.output', '.output-inline']) { it('default client bundle size', async () => { const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public')) - expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot('"97.4k"') + expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot('"95.0k"') expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(` [ "_nuxt/entry.js", @@ -32,10 +32,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM const serverDir = join(rootDir, '.output/server') const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) - expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"64.5k"') + expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"64.6k"') const modules = await analyzeSizes('node_modules/**/*', serverDir) - expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2342k"') + expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2335k"') const packages = modules.files .filter(m => m.endsWith('package.json')) @@ -95,7 +95,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"370k"') const modules = await analyzeSizes('node_modules/**/*', serverDir) - expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"604k"') + expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"597k"') const packages = modules.files .filter(m => m.endsWith('package.json')) diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts index b4ca4d6c14..c0e0268427 100644 --- a/test/fixtures/basic/nuxt.config.ts +++ b/test/fixtures/basic/nuxt.config.ts @@ -195,7 +195,7 @@ export default defineNuxtConfig({ treeshakeClientOnly: true, payloadExtraction: true, asyncContext: process.env.TEST_CONTEXT === 'async', - headCapoPlugin: true + headNext: true }, appConfig: { fromNuxtConfig: true,