Compare commits

...

7 Commits

Author SHA1 Message Date
renovate[bot]
754955545e
chore(deps): update all non-major dependencies (main) (#22866)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Roe <daniel@roe.dev>
2023-09-05 12:27:41 +01:00
Julien Huang
5390ca4321
docs: add information about server component context (#22964) 2023-09-05 11:29:28 +01:00
Saman
beb7410777
fix(nuxt): always use increment for id with client side islands (#22975) 2023-09-05 11:27:00 +01:00
Harlan Wilton
19145386e3
fix(nuxt): resolve head instance from Nuxt app (#22973) 2023-09-05 11:25:46 +01:00
Kekeocha Justin Chetachukwu
fe59e0d739
docs: accessing custom props for NuxtLayout (#22989) 2023-09-05 11:09:23 +01:00
Daniel Roe
1a08079710
fix(nuxt): use destr in more places over JSON.parse (#22997) 2023-09-05 09:42:16 +01:00
xjccc
b27740cf50
docs: docs/3.api/3.utils/define-page-meta.md (#23006) 2023-09-05 10:04:48 +02:00
31 changed files with 596 additions and 566 deletions

View File

@ -86,7 +86,7 @@ jobs:
run: pnpm install run: pnpm install
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4 uses: github/codeql-action/init@00e563ead9f72a8461b24876bee2d0c2e8bd2ee8 # v2.21.5
with: with:
languages: javascript languages: javascript
queries: +security-and-quality queries: +security-and-quality
@ -98,7 +98,7 @@ jobs:
path: packages path: packages
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4 uses: github/codeql-action/analyze@00e563ead9f72a8461b24876bee2d0c2e8bd2ee8 # v2.21.5
with: with:
category: "/language:javascript" category: "/language:javascript"

View File

@ -64,7 +64,7 @@ jobs:
repo: pr.head.repo.full_name repo: pr.head.repo.full_name
} }
- id: generate-token - id: generate-token
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 # v1.8.0 uses: tibdex/github-app-token@0d49dd721133f900ebd5e0dff2810704e8defbc6 # v1.8.2
with: with:
app_id: ${{ secrets.ECOSYSTEM_CI_GITHUB_APP_ID }} app_id: ${{ secrets.ECOSYSTEM_CI_GITHUB_APP_ID }}
private_key: ${{ secrets.ECOSYSTEM_CI_GITHUB_APP_PRIVATE_KEY }} private_key: ${{ secrets.ECOSYSTEM_CI_GITHUB_APP_PRIVATE_KEY }}

View File

@ -66,6 +66,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4 uses: github/codeql-action/upload-sarif@00e563ead9f72a8461b24876bee2d0c2e8bd2ee8 # v2.21.5
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -129,7 +129,7 @@ useHead({
}) })
``` ```
Nuxt uses `unhead` under the hood, and you can refer to its full documentation [here](https://unhead.harlanzw.com/). Nuxt uses `unhead` under the hood, and you can refer to its full documentation [here](https://unhead.unjs.io/).
### Modifying The Rendered Head With A Nitro Plugin ### Modifying The Rendered Head With A Nitro Plugin

View File

@ -36,7 +36,7 @@ Shortcuts are available to make configuration easier: `charset` and `viewport`.
## `useHead` ## `useHead`
The [`useHead`](/docs/api/composables/use-head) composable function allows you to manage your head tags in a programmatic and reactive way, The [`useHead`](/docs/api/composables/use-head) composable function allows you to manage your head tags in a programmatic and reactive way,
powered by [Unhead](https://unhead.harlanzw.com/). powered by [Unhead](https://unhead.unjs.io/).
As with all composables, it can only be used with a components `setup` and lifecycle hooks. As with all composables, it can only be used with a components `setup` and lifecycle hooks.

View File

@ -314,6 +314,19 @@ Now you can register server-only components with the `.server` suffix and use th
Server-only components use `<NuxtIsland>` under the hood, meaning that `lazy` prop and `#fallback` slot are both passed down to `<NuxtIsland>`. Server-only components use `<NuxtIsland>` under the hood, meaning that `lazy` prop and `#fallback` slot are both passed down to `<NuxtIsland>`.
#### Server Component Context
When rendering a server-only or island component, `<NuxtIsland>` makes a fetch request which comes back with a `NuxtIslandResponse`. (This is an internal request if rendered on the server, or a request that you can see in the network tab if it's rendering on client-side navigation.)
This means:
- A new Vue app will be created server-side to create the `NuxtIslandResponse`.
- A new 'island context' will be created while rendering the component.
- You can't access the 'island context' from the rest of your app and you can't access the context of the rest of your app from the island component. In other words, the server component or island is _isolated_ from the rest of your app.
- Your plugins will run again when rendering the island, unless they have `env: { islands: false }` set (which you can do in an object-syntax plugin).
Within an island component, you can access its island context through `nuxtApp.ssrContext.islandContext`. Note that while island components are still marked as experimental, the format of this context may change.
::alert{type=info} ::alert{type=info}
Slots can be interactive and are wrapped within a `<div>` with `display: contents;` Slots can be interactive and are wrapped within a `<div>` with `display: contents;`
:: ::

View File

@ -24,7 +24,7 @@ useHeadSafe({
// <meta content="0;javascript:alert(1)"> // <meta content="0;javascript:alert(1)">
``` ```
Read more on [unhead documentation](https://unhead.harlanzw.com/guide/composables/use-head-safe). Read more on [unhead documentation](https://unhead.unjs.io/usage/composables/use-head-safe).
## Type ## Type

View File

@ -4,7 +4,7 @@ description: useHead customizes the head properties of individual pages of your
# useHead # useHead
The [`useHead`](/docs/api/composables/use-head) composable function allows you to manage your head tags in a programmatic and reactive way, powered by [Unhead](https://unhead.harlanzw.com/). If the data comes from a user or other untrusted source, we recommend you check out [`useHeadSafe`](/docs/api/composables/use-head-safe) The [`useHead`](/docs/api/composables/use-head) composable function allows you to manage your head tags in a programmatic and reactive way, powered by [Unhead](https://unhead.unjs.io/). If the data comes from a user or other untrusted source, we recommend you check out [`useHeadSafe`](/docs/api/composables/use-head-safe)
:ReadMore{link="/docs/getting-started/seo-meta"} :ReadMore{link="/docs/getting-started/seo-meta"}

View File

@ -47,6 +47,26 @@ Please note the layout name is normalized to kebab-case, so if your layout file
</template> </template>
``` ```
## Passing Additional Props
`NuxtLayout` also accepts any additional props that you may need to pass to the layout. These custom props are then made accessible as attributes.
```vue[pages/some-page.vue]
<div>
<NuxtLayout name="custom" title="I am a custom layout">
</NuxtLayout>
</div>
```
In the above example, the value of `title` will be available using `$attrs.title` in the template or `useAttrs().title` in `<script setup>` at custom.vue.
```vue[layouts/custom.vue]
<script setup lang="ts">
const layoutCustomProps = useAttrs()
console.log(layoutCustomProps.title) // I am a custom layout
</script>
```
## Layout and Transition ## Layout and Transition
`<NuxtLayout />` renders incoming content via `<slot />`, which is then wrapped around Vues `<Transition />` component to activate layout transition. For this to work as expected, it is recommended that `<NuxtLayout />` is **not** the root element of the page component. `<NuxtLayout />` renders incoming content via `<slot />`, which is then wrapped around Vues `<Transition />` component to activate layout transition. For this to work as expected, it is recommended that `<NuxtLayout />` is **not** the root element of the page component.

View File

@ -25,6 +25,7 @@ definePageMeta(meta: PageMeta) => void
interface PageMeta { interface PageMeta {
validate?: (route: RouteLocationNormalized) => boolean | Promise<boolean> | Partial<NuxtError> | Promise<Partial<NuxtError>> validate?: (route: RouteLocationNormalized) => boolean | Promise<boolean> | Partial<NuxtError> | Promise<Partial<NuxtError>>
redirect?: RouteRecordRedirectOption redirect?: RouteRecordRedirectOption
path?: string
alias?: string | string[] alias?: string | string[]
pageTransition?: boolean | TransitionProps pageTransition?: boolean | TransitionProps
layoutTransition?: boolean | TransitionProps layoutTransition?: boolean | TransitionProps
@ -45,6 +46,12 @@ interface PageMeta {
An object accepting the following page metadata: An object accepting the following page metadata:
**`path`**
- **Type**: `string`
You may define a path matcher, if you have a more complex pattern than can be expressed with the file name.
**`alias`** **`alias`**
- **Type**: `string | string[]` - **Type**: `string | string[]`

View File

@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "pnpm --filter './packages/**' prepack", "build": "pnpm --filter './packages/**' prepack",
"build:stub": "pnpm --filter './packages/**' prepack --stub", "build:stub": "pnpm --filter './packages/**' prepack --stub",
"cleanup": "rimraf 'packages/**/node_modules' 'examples/**/node_modules' 'docs/node_modules' 'playground/node_modules' 'node_modules'", "cleanup": "rimraf 'packages/**/node_modules' 'playground/node_modules' 'node_modules'",
"dev": "pnpm play", "dev": "pnpm play",
"lint": "eslint --ext .vue,.ts,.js,.mjs .", "lint": "eslint --ext .vue,.ts,.js,.mjs .",
"lint:fix": "eslint --ext .vue,.ts,.js,.mjs . --fix", "lint:fix": "eslint --ext .vue,.ts,.js,.mjs . --fix",
@ -42,10 +42,10 @@
"@actions/core": "1.10.0", "@actions/core": "1.10.0",
"@nuxt/test-utils": "workspace:*", "@nuxt/test-utils": "workspace:*",
"@nuxt/webpack-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*",
"@nuxtjs/eslint-config-typescript": "12.0.0", "@nuxtjs/eslint-config-typescript": "12.1.0",
"@types/fs-extra": "11.0.1", "@types/fs-extra": "11.0.1",
"@types/node": "18.17.11", "@types/node": "18.17.14",
"@types/semver": "7.5.0", "@types/semver": "7.5.1",
"case-police": "0.6.1", "case-police": "0.6.1",
"chalk": "5.3.0", "chalk": "5.3.0",
"changelogen": "0.5.5", "changelogen": "0.5.5",
@ -60,13 +60,13 @@
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"globby": "13.2.2", "globby": "13.2.2",
"h3": "1.8.1", "h3": "1.8.1",
"happy-dom": "10.11.0", "happy-dom": "10.11.2",
"jiti": "1.19.3", "jiti": "1.19.3",
"markdownlint-cli": "^0.33.0", "markdownlint-cli": "^0.33.0",
"nitropack": "2.6.2", "nitropack": "2.6.2",
"nuxi": "3.7.2", "nuxi": "3.7.3",
"nuxt": "workspace:*", "nuxt": "workspace:*",
"nuxt-vitest": "0.10.2", "nuxt-vitest": "0.10.4",
"ofetch": "1.3.3", "ofetch": "1.3.3",
"pathe": "1.1.1", "pathe": "1.1.1",
"playwright-core": "1.37.1", "playwright-core": "1.37.1",
@ -77,13 +77,13 @@
"ufo": "1.3.0", "ufo": "1.3.0",
"vite": "4.4.9", "vite": "4.4.9",
"vitest": "0.33.0", "vitest": "0.33.0",
"vitest-environment-nuxt": "0.10.2", "vitest-environment-nuxt": "0.10.4",
"vue": "3.3.4", "vue": "3.3.4",
"vue-eslint-parser": "9.3.1", "vue-eslint-parser": "9.3.1",
"vue-router": "4.2.4", "vue-router": "4.2.4",
"vue-tsc": "1.8.8" "vue-tsc": "1.8.8"
}, },
"packageManager": "pnpm@8.6.12", "packageManager": "pnpm@8.7.1",
"engines": { "engines": {
"node": "^14.18.0 || >=16.10.0" "node": "^14.18.0 || >=16.10.0"
} }

View File

@ -20,7 +20,7 @@
"prepack": "unbuild" "prepack": "unbuild"
}, },
"dependencies": { "dependencies": {
"@nuxt/schema": "workspace:../schema", "@nuxt/schema": "workspace:*",
"c12": "^1.4.2", "c12": "^1.4.2",
"consola": "^3.2.3", "consola": "^3.2.3",
"defu": "^6.1.2", "defu": "^6.1.2",
@ -29,20 +29,20 @@
"ignore": "^5.2.4", "ignore": "^5.2.4",
"jiti": "^1.19.3", "jiti": "^1.19.3",
"knitwork": "^1.0.0", "knitwork": "^1.0.0",
"mlly": "^1.4.1", "mlly": "^1.4.2",
"pathe": "^1.1.1", "pathe": "^1.1.1",
"pkg-types": "^1.0.3", "pkg-types": "^1.0.3",
"scule": "^1.0.0", "scule": "^1.0.0",
"semver": "^7.5.4", "semver": "^7.5.4",
"ufo": "^1.3.0", "ufo": "^1.3.0",
"unctx": "^2.3.1", "unctx": "^2.3.1",
"unimport": "^3.2.0", "unimport": "^3.3.0",
"untyped": "^1.4.0" "untyped": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@types/hash-sum": "1.0.0", "@types/hash-sum": "1.0.0",
"@types/lodash-es": "4.17.8", "@types/lodash-es": "4.17.9",
"@types/semver": "7.5.0", "@types/semver": "7.5.1",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"nitropack": "2.6.2", "nitropack": "2.6.2",
"unbuild": "latest", "unbuild": "latest",

View File

@ -53,14 +53,14 @@
}, },
"dependencies": { "dependencies": {
"@nuxt/devalue": "^2.0.2", "@nuxt/devalue": "^2.0.2",
"@nuxt/kit": "workspace:../kit", "@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:../schema", "@nuxt/schema": "workspace:*",
"@nuxt/telemetry": "^2.4.1", "@nuxt/telemetry": "^2.4.1",
"@nuxt/ui-templates": "^1.3.1", "@nuxt/ui-templates": "^1.3.1",
"@nuxt/vite-builder": "workspace:../vite", "@nuxt/vite-builder": "workspace:*",
"@unhead/dom": "^1.3.9", "@unhead/dom": "^1.5.2",
"@unhead/ssr": "^1.3.9", "@unhead/ssr": "^1.5.2",
"@unhead/vue": "^1.3.9", "@unhead/vue": "^1.5.2",
"@vue/shared": "^3.3.4", "@vue/shared": "^3.3.4",
"acorn": "8.10.0", "acorn": "8.10.0",
"c12": "^1.4.2", "c12": "^1.4.2",
@ -80,10 +80,10 @@
"klona": "^2.0.6", "klona": "^2.0.6",
"knitwork": "^1.0.0", "knitwork": "^1.0.0",
"magic-string": "^0.30.3", "magic-string": "^0.30.3",
"mlly": "^1.4.1", "mlly": "^1.4.2",
"nitropack": "^2.6.2", "nitropack": "^2.6.2",
"nuxi": "^3.7.2", "nuxi": "^3.7.3",
"nypm": "^0.3.1", "nypm": "^0.3.2",
"ofetch": "^1.3.3", "ofetch": "^1.3.3",
"ohash": "^1.1.3", "ohash": "^1.1.3",
"pathe": "^1.1.1", "pathe": "^1.1.1",
@ -94,11 +94,11 @@
"std-env": "^3.4.3", "std-env": "^3.4.3",
"strip-literal": "^1.3.0", "strip-literal": "^1.3.0",
"ufo": "^1.3.0", "ufo": "^1.3.0",
"ultrahtml": "^1.3.0", "ultrahtml": "^1.4.0",
"uncrypto": "^0.1.3", "uncrypto": "^0.1.3",
"unctx": "^2.3.1", "unctx": "^2.3.1",
"unenv": "^1.7.4", "unenv": "^1.7.4",
"unimport": "^3.2.0", "unimport": "^3.3.0",
"unplugin": "^1.4.0", "unplugin": "^1.4.0",
"unplugin-vue-router": "^0.6.4", "unplugin-vue-router": "^0.6.4",
"untyped": "^1.4.0", "untyped": "^1.4.0",
@ -112,7 +112,7 @@
"@types/estree": "1.0.1", "@types/estree": "1.0.1",
"@types/fs-extra": "11.0.1", "@types/fs-extra": "11.0.1",
"@types/prompts": "2.4.4", "@types/prompts": "2.4.4",
"@vitejs/plugin-vue": "4.3.3", "@vitejs/plugin-vue": "4.3.4",
"unbuild": "latest", "unbuild": "latest",
"vite": "4.4.9", "vite": "4.4.9",
"vitest": "0.33.0" "vitest": "0.33.0"

View File

@ -31,7 +31,6 @@ export default defineComponent({
setup (props, ctx) { setup (props, ctx) {
const mounted = ref(false) const mounted = ref(false)
// This is deliberate - `uid` should not be provided by user but by a transform plugin and will not be reactive. // This is deliberate - `uid` should not be provided by user but by a transform plugin and will not be reactive.
// eslint-disable-next-line vue/no-setup-props-destructure
const ssrFailed = useState(`${props.uid}`) const ssrFailed = useState(`${props.uid}`)
if (ssrFailed.value) { if (ssrFailed.value) {

View File

@ -10,7 +10,6 @@ const props = defineProps({
}) })
// Deliberately prevent reactive update when error is cleared // Deliberately prevent reactive update when error is cleared
// eslint-disable-next-line vue/no-setup-props-destructure
const _error = props.error const _error = props.error
// TODO: extract to a separate utility // TODO: extract to a separate utility

View File

@ -86,7 +86,7 @@ export default defineComponent({
ssrHTML.value = renderedHTML ssrHTML.value = renderedHTML
} }
const slotProps = computed(() => getSlotProps(ssrHTML.value)) const slotProps = computed(() => getSlotProps(ssrHTML.value))
const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? randomUUID()) const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? getId())
const availableSlots = computed(() => [...ssrHTML.value.matchAll(SLOTNAME_RE)].map(m => m[1])) const availableSlots = computed(() => [...ssrHTML.value.matchAll(SLOTNAME_RE)].map(m => m[1]))
const html = computed(() => { const html = computed(() => {

View File

@ -26,7 +26,6 @@ const LayoutLoader = defineComponent({
// This is a deliberate hack - this component must always be called with an explicit key to ensure // This is a deliberate hack - this component must always be called with an explicit key to ensure
// that setup reruns when the name changes. // that setup reruns when the name changes.
// eslint-disable-next-line vue/no-setup-props-destructure
const LayoutComponent = await layouts[props.name]().then((r: any) => r.default || r) const LayoutComponent = await layouts[props.name]().then((r: any) => r.default || r)
return () => h(LayoutComponent, props.layoutProps, context.slots) return () => h(LayoutComponent, props.layoutProps, context.slots)
@ -103,7 +102,7 @@ const LayoutProvider = defineComponent({
}, },
setup (props, context) { setup (props, context) {
// Prevent reactivity when the page will be rerendered in a different suspense fork // Prevent reactivity when the page will be rerendered in a different suspense fork
// eslint-disable-next-line vue/no-setup-props-destructure
const name = props.name const name = props.name
if (props.shouldProvide) { if (props.shouldProvide) {
provide(LayoutMetaSymbol, { provide(LayoutMetaSymbol, {

View File

@ -27,7 +27,6 @@ export default defineComponent({
}, },
setup (props, { slots }) { setup (props, { slots }) {
// TODO: use computed values in useLoadingIndicator // TODO: use computed values in useLoadingIndicator
// eslint-disable-next-line vue/no-setup-props-destructure
const indicator = useLoadingIndicator({ const indicator = useLoadingIndicator({
duration: props.duration, duration: props.duration,
throttle: props.throttle throttle: props.throttle

View File

@ -20,9 +20,7 @@ export const RouteProvider = defineComponent({
}, },
setup (props) { setup (props) {
// Prevent reactivity when the page will be rerendered in a different suspense fork // Prevent reactivity when the page will be rerendered in a different suspense fork
// eslint-disable-next-line vue/no-setup-props-destructure
const previousKey = props.renderKey const previousKey = props.renderKey
// eslint-disable-next-line vue/no-setup-props-destructure
const previousRoute = props.route const previousRoute = props.route
// Provide a reactive route within the page // Provide a reactive route within the page

View File

@ -2,6 +2,7 @@ import { parseURL } from 'ufo'
import { defineComponent, h } from 'vue' import { defineComponent, h } from 'vue'
import { parseQuery } from 'vue-router' import { parseQuery } from 'vue-router'
import { resolve } from 'pathe' import { resolve } from 'pathe'
import destr from 'destr'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import { devRootDir } from '#build/nuxt.config.mjs' import { devRootDir } from '#build/nuxt.config.mjs'
@ -10,7 +11,7 @@ export default (url: string) => defineComponent({
async setup (props, { attrs }) { async setup (props, { attrs }) {
const query = parseQuery(parseURL(url).search) const query = parseQuery(parseURL(url).search)
const urlProps = query.props ? JSON.parse(query.props as string) : {} const urlProps = query.props ? destr<Record<string, any>>(query.props as string) : {}
const path = resolve(query.path as string) const path = resolve(query.path as string)
if (!path.startsWith(devRootDir)) { if (!path.startsWith(devRootDir)) {
throw new Error(`[nuxt] Cannot access path outside of project root directory: \`${path}\`.`) throw new Error(`[nuxt] Cannot access path outside of project root directory: \`${path}\`.`)

View File

@ -1,3 +1,4 @@
import destr from 'destr'
import { useNuxtApp } from '#app/nuxt' import { useNuxtApp } from '#app/nuxt'
export interface ReloadNuxtAppOptions { export interface ReloadNuxtAppOptions {
@ -34,7 +35,7 @@ export function reloadNuxtApp (options: ReloadNuxtAppOptions = {}) {
let handledPath: Record<string, any> = {} let handledPath: Record<string, any> = {}
try { try {
handledPath = JSON.parse(sessionStorage.getItem('nuxt:reload') || '{}') handledPath = destr(sessionStorage.getItem('nuxt:reload') || '{}')
} catch {} } catch {}
if (options.force || handledPath?.path !== path || handledPath?.expires < Date.now()) { if (options.force || handledPath?.path !== path || handledPath?.expires < Date.now()) {

View File

@ -1,3 +1,4 @@
import destr from 'destr'
import { defineNuxtPlugin, useNuxtApp } from '#app/nuxt' import { defineNuxtPlugin, useNuxtApp } from '#app/nuxt'
export default defineNuxtPlugin({ export default defineNuxtPlugin({
@ -9,7 +10,7 @@ export default defineNuxtPlugin({
const state = sessionStorage.getItem('nuxt:reload:state') const state = sessionStorage.getItem('nuxt:reload:state')
if (state) { if (state) {
sessionStorage.removeItem('nuxt:reload:state') sessionStorage.removeItem('nuxt:reload:state')
Object.assign(nuxtApp.payload.state, JSON.parse(state)?.state) Object.assign(nuxtApp.payload.state, destr<Record<string, any>>(state)?.state)
} }
} catch {} } catch {}
} }

View File

@ -1,4 +1,5 @@
import { reactive, ref, shallowReactive, shallowRef } from 'vue' import { reactive, ref, shallowReactive, shallowRef } from 'vue'
import destr from 'destr'
import { definePayloadReviver, getNuxtClientPayload } from '#app/composables/payload' import { definePayloadReviver, getNuxtClientPayload } from '#app/composables/payload'
import { createError } from '#app/composables/error' import { createError } from '#app/composables/error'
import { defineNuxtPlugin, useNuxtApp } from '#app/nuxt' import { defineNuxtPlugin, useNuxtApp } from '#app/nuxt'
@ -8,8 +9,8 @@ import { componentIslands } from '#build/nuxt.config.mjs'
const revivers: Record<string, (data: any) => any> = { const revivers: Record<string, (data: any) => any> = {
NuxtError: data => createError(data), NuxtError: data => createError(data),
EmptyShallowRef: data => shallowRef(data === '_' ? undefined : data === '0n' ? BigInt(0) : JSON.parse(data)), EmptyShallowRef: data => shallowRef(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data)),
EmptyRef: data => ref(data === '_' ? undefined : data === '0n' ? BigInt(0) : JSON.parse(data)), EmptyRef: data => ref(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data)),
ShallowRef: data => shallowRef(data), ShallowRef: data => shallowRef(data),
ShallowReactive: data => shallowReactive(data), ShallowReactive: data => shallowReactive(data),
Ref: data => ref(data), Ref: data => ref(data),

View File

@ -1,18 +1,25 @@
import { createHead as createClientHead } from '@unhead/vue' import { createHead as createClientHead, setHeadInjectionHandler } from '@unhead/vue'
import { renderDOMHead } from '@unhead/dom' import { renderDOMHead } from '@unhead/dom'
import { defineNuxtPlugin } from '#app/nuxt' import { defineNuxtPlugin } from '#app/nuxt'
import { useNuxtApp } from '#app'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import unheadPlugins from '#build/unhead-plugins.mjs' import unheadPlugins from '#build/unhead-plugins.mjs'
export default defineNuxtPlugin({ export default defineNuxtPlugin({
name: 'nuxt:head', name: 'nuxt:head',
enforce: 'pre',
setup (nuxtApp) { setup (nuxtApp) {
const head = import.meta.server const head = import.meta.server
? nuxtApp.ssrContext!.head ? nuxtApp.ssrContext!.head
: createClientHead({ : createClientHead({
plugins: unheadPlugins plugins: unheadPlugins
}) })
// allow useHead to be used outside a Vue context but within a Nuxt context
setHeadInjectionHandler(
// need a fresh instance of the nuxt app to avoid parallel requests interfering with each other
() => useNuxtApp().vueApp._context.provides.usehead
)
// nuxt.config appHead is set server-side within the renderer // nuxt.config appHead is set server-side within the renderer
nuxtApp.vueApp.use(head) nuxtApp.vueApp.use(head)

View File

@ -32,11 +32,11 @@
"@types/file-loader": "5.0.1", "@types/file-loader": "5.0.1",
"@types/pug": "2.0.6", "@types/pug": "2.0.6",
"@types/sass-loader": "8.0.5", "@types/sass-loader": "8.0.5",
"@unhead/schema": "1.3.9", "@unhead/schema": "1.5.2",
"@vitejs/plugin-vue": "4.3.3", "@vitejs/plugin-vue": "4.3.4",
"@vitejs/plugin-vue-jsx": "3.0.2", "@vitejs/plugin-vue-jsx": "3.0.2",
"@vue/compiler-core": "3.3.4", "@vue/compiler-core": "3.3.4",
"esbuild-loader": "4.0.1", "esbuild-loader": "4.0.2",
"h3": "1.8.1", "h3": "1.8.1",
"ignore": "5.2.4", "ignore": "5.2.4",
"nitropack": "2.6.2", "nitropack": "2.6.2",
@ -59,7 +59,7 @@
"postcss-import-resolver": "^2.0.0", "postcss-import-resolver": "^2.0.0",
"std-env": "^3.4.3", "std-env": "^3.4.3",
"ufo": "^1.3.0", "ufo": "^1.3.0",
"unimport": "^3.2.0", "unimport": "^3.3.0",
"untyped": "^1.4.0" "untyped": "^1.4.0"
}, },
"engines": { "engines": {

View File

@ -22,8 +22,8 @@
"prepack": "unbuild" "prepack": "unbuild"
}, },
"dependencies": { "dependencies": {
"@nuxt/kit": "workspace:../kit", "@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:../schema", "@nuxt/schema": "workspace:*",
"consola": "^3.2.3", "consola": "^3.2.3",
"defu": "^6.1.2", "defu": "^6.1.2",
"execa": "^7.2.0", "execa": "^7.2.0",

View File

@ -18,7 +18,7 @@
"prepack": "unbuild" "prepack": "unbuild"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/schema": "workspace:../schema", "@nuxt/schema": "workspace:*",
"@types/clear": "0.1.2", "@types/clear": "0.1.2",
"@types/estree": "1.0.1", "@types/estree": "1.0.1",
"@types/fs-extra": "11.0.1", "@types/fs-extra": "11.0.1",
@ -26,9 +26,9 @@
"vue": "3.3.4" "vue": "3.3.4"
}, },
"dependencies": { "dependencies": {
"@nuxt/kit": "workspace:../kit", "@nuxt/kit": "workspace:*",
"@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-replace": "^5.0.2",
"@vitejs/plugin-vue": "^4.3.3", "@vitejs/plugin-vue": "^4.3.4",
"@vitejs/plugin-vue-jsx": "^3.0.2", "@vitejs/plugin-vue-jsx": "^3.0.2",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"clear": "^0.1.0", "clear": "^0.1.0",
@ -44,12 +44,12 @@
"h3": "^1.8.1", "h3": "^1.8.1",
"knitwork": "^1.0.0", "knitwork": "^1.0.0",
"magic-string": "^0.30.3", "magic-string": "^0.30.3",
"mlly": "^1.4.1", "mlly": "^1.4.2",
"ohash": "^1.1.3", "ohash": "^1.1.3",
"pathe": "^1.1.1", "pathe": "^1.1.1",
"perfect-debounce": "^1.0.0", "perfect-debounce": "^1.0.0",
"pkg-types": "^1.0.3", "pkg-types": "^1.0.3",
"postcss": "^8.4.28", "postcss": "^8.4.29",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"rollup-plugin-visualizer": "^5.9.2", "rollup-plugin-visualizer": "^5.9.2",

View File

@ -20,13 +20,13 @@
}, },
"dependencies": { "dependencies": {
"@nuxt/friendly-errors-webpack-plugin": "^2.5.2", "@nuxt/friendly-errors-webpack-plugin": "^2.5.2",
"@nuxt/kit": "workspace:../kit", "@nuxt/kit": "workspace:*",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"css-loader": "^6.8.1", "css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1", "css-minimizer-webpack-plugin": "^5.0.1",
"cssnano": "^6.0.1", "cssnano": "^6.0.1",
"defu": "^6.1.2", "defu": "^6.1.2",
"esbuild-loader": "^4.0.1", "esbuild-loader": "^4.0.2",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
@ -38,11 +38,11 @@
"magic-string": "^0.30.3", "magic-string": "^0.30.3",
"memfs": "^4.2.1", "memfs": "^4.2.1",
"mini-css-extract-plugin": "^2.7.6", "mini-css-extract-plugin": "^2.7.6",
"mlly": "^1.4.1", "mlly": "^1.4.2",
"ohash": "^1.1.3", "ohash": "^1.1.3",
"pathe": "^1.1.1", "pathe": "^1.1.1",
"pify": "^6.1.0", "pify": "^6.1.0",
"postcss": "^8.4.28", "postcss": "^8.4.29",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-loader": "^7.3.3", "postcss-loader": "^7.3.3",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
@ -55,17 +55,17 @@
"vue-bundle-renderer": "^2.0.0", "vue-bundle-renderer": "^2.0.0",
"vue-loader": "^17.2.2", "vue-loader": "^17.2.2",
"webpack": "^5.88.2", "webpack": "^5.88.2",
"webpack-bundle-analyzer": "^4.9.0", "webpack-bundle-analyzer": "^4.9.1",
"webpack-dev-middleware": "^6.1.1", "webpack-dev-middleware": "^6.1.1",
"webpack-hot-middleware": "^2.25.4", "webpack-hot-middleware": "^2.25.4",
"webpack-virtual-modules": "^0.5.0", "webpack-virtual-modules": "^0.5.0",
"webpackbar": "^5.0.2" "webpackbar": "^5.0.2"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/schema": "workspace:../schema", "@nuxt/schema": "workspace:*",
"@types/fs-extra": "11.0.1", "@types/fs-extra": "11.0.1",
"@types/hash-sum": "1.0.0", "@types/hash-sum": "1.0.0",
"@types/lodash-es": "4.17.8", "@types/lodash-es": "4.17.9",
"@types/pify": "5.0.1", "@types/pify": "5.0.1",
"@types/webpack-bundle-analyzer": "4.6.0", "@types/webpack-bundle-analyzer": "4.6.0",
"@types/webpack-hot-middleware": "2.25.6", "@types/webpack-hot-middleware": "2.25.6",

File diff suppressed because it is too large Load Diff

View File

@ -1386,7 +1386,7 @@ describe('server components/islands', () => {
await page.getByText('Go to page without lazy server component').click() await page.getByText('Go to page without lazy server component').click()
const text = await page.innerText('pre') const text = await page.innerText('pre')
expect(text).toMatchInlineSnapshot('" End page <pre></pre><section id=\\"fallback\\"><div nuxt-ssr-component-uid=\\"0\\"> This is a .server (20ms) async component that was very long ... <div id=\\"async-server-component-count\\">42</div><div style=\\"display:contents;\\" nuxt-ssr-slot-name=\\"default\\"></div></div></section><section id=\\"no-fallback\\"><div nuxt-ssr-component-uid=\\"1\\"> This is a .server (20ms) async component that was very long ... <div id=\\"async-server-component-count\\">42</div><div style=\\"display:contents;\\" nuxt-ssr-slot-name=\\"default\\"></div></div></section>"') expect(text).toMatchInlineSnapshot('" End page <pre></pre><section id=\\"fallback\\"><div nuxt-ssr-component-uid=\\"2\\"> This is a .server (20ms) async component that was very long ... <div id=\\"async-server-component-count\\">42</div><div style=\\"display:contents;\\" nuxt-ssr-slot-name=\\"default\\"></div></div></section><section id=\\"no-fallback\\"><div nuxt-ssr-component-uid=\\"3\\"> This is a .server (20ms) async component that was very long ... <div id=\\"async-server-component-count\\">42</div><div style=\\"display:contents;\\" nuxt-ssr-slot-name=\\"default\\"></div></div></section>"')
expect(text).toContain('async component that was very long') expect(text).toContain('async component that was very long')
// Wait for all pending micro ticks to be cleared // Wait for all pending micro ticks to be cleared

View File

@ -19,7 +19,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
for (const outputDir of ['.output', '.output-inline']) { for (const outputDir of ['.output', '.output-inline']) {
it('default client bundle size', async () => { it('default client bundle size', async () => {
const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public')) const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public'))
expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot('"95.5k"') expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot('"95.9k"')
expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(` expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
[ [
"_nuxt/entry.js", "_nuxt/entry.js",
@ -32,7 +32,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output/server') const serverDir = join(rootDir, '.output/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"299k"') expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"300k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir) const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"1822k"') expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"1822k"')
@ -71,10 +71,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output-inline/server') const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"605k"') expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"607k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir) const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"70.6k"') expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"71.0k"')
const packages = modules.files const packages = modules.files
.filter(m => m.endsWith('package.json')) .filter(m => m.endsWith('package.json'))