Merge remote-tracking branch 'origin/main' into patch-21

This commit is contained in:
Daniel Roe 2024-08-20 12:12:14 +01:00
commit 31779d2371
No known key found for this signature in database
GPG Key ID: CBC814C393D93268
49 changed files with 1162 additions and 1107 deletions

View File

@ -85,7 +85,7 @@ jobs:
run: pnpm install run: pnpm install
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 uses: github/codeql-action/init@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3
with: with:
languages: javascript languages: javascript
queries: +security-and-quality queries: +security-and-quality
@ -97,7 +97,7 @@ jobs:
path: packages path: packages
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 uses: github/codeql-action/analyze@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3
with: with:
category: "/language:javascript" category: "/language:javascript"

View File

@ -0,0 +1,17 @@
name: reproduire-sur-stackblitz
on:
issues:
types:
opened
permissions:
issues: write
jobs:
reproduire-sur-stackblitz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: huang-julien/reproduire-sur-stackblitz@v1.0.0
with:
reproduction-heading: '### Reproduction'

View File

@ -68,7 +68,7 @@ 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@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 uses: github/codeql-action/upload-sarif@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3
if: github.repository == 'nuxt/nuxt' && success() if: github.repository == 'nuxt/nuxt' && success()
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -22,13 +22,13 @@ In order to allow you to manage your other testing dependencies, `@nuxt/test-uti
- you can choose between `vitest`, `cucumber`, `jest` and `playwright` for end-to-end test runners - you can choose between `vitest`, `cucumber`, `jest` and `playwright` for end-to-end test runners
- `playwright-core` is only required if you wish to use the built-in browser testing utilities (and are not using `@playwright/test` as your test runner) - `playwright-core` is only required if you wish to use the built-in browser testing utilities (and are not using `@playwright/test` as your test runner)
::code-group ::package-managers
```bash [yarn]
yarn add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
```bash [npm] ```bash [npm]
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
``` ```
```bash [yarn]
yarn add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
```bash [pnpm] ```bash [pnpm]
pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
``` ```
@ -421,13 +421,13 @@ If you prefer to use `@vue/test-utils` on its own for unit testing in Nuxt, and
1. Install the needed dependencies 1. Install the needed dependencies
::code-group ::package-managers
```bash [yarn]
yarn add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
```
```bash [npm] ```bash [npm]
npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
``` ```
```bash [yarn]
yarn add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
```
```bash [pnpm] ```bash [pnpm]
pnpm add -D vitest @vue/test-utils happy-dom @vitejs/plugin-vue pnpm add -D vitest @vue/test-utils happy-dom @vitejs/plugin-vue
``` ```
@ -487,13 +487,13 @@ If you prefer to use `@vue/test-utils` on its own for unit testing in Nuxt, and
6. Run vitest command 6. Run vitest command
::code-group ::package-managers
```bash [yarn]
yarn test
```
```bash [npm] ```bash [npm]
npm run test npm run test
``` ```
```bash [yarn]
yarn test
```
```bash [pnpm] ```bash [pnpm]
pnpm run test pnpm run test
``` ```
@ -658,13 +658,13 @@ const page = await createPage('/page')
We also provide first-class support for testing Nuxt within [the Playwright test runner](https://playwright.dev/docs/intro). We also provide first-class support for testing Nuxt within [the Playwright test runner](https://playwright.dev/docs/intro).
::code-group ::package-managers
```bash [yarn]
yarn add --dev @playwright/test @nuxt/test-utils
```
```bash [npm] ```bash [npm]
npm i --save-dev @playwright/test @nuxt/test-utils npm i --save-dev @playwright/test @nuxt/test-utils
``` ```
```bash [yarn]
yarn add --dev @playwright/test @nuxt/test-utils
```
```bash [pnpm] ```bash [pnpm]
pnpm add -D @playwright/test @nuxt/test-utils pnpm add -D @playwright/test @nuxt/test-utils
``` ```

View File

@ -11,7 +11,7 @@ navigation.icon: i-ph-arrow-circle-up-duotone
To upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases), use the `nuxi upgrade` command. To upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases), use the `nuxi upgrade` command.
::code-group ::package-managers
```bash [npm] ```bash [npm]
npx nuxi upgrade npx nuxi upgrade

View File

@ -35,7 +35,7 @@ Or follow the steps below to set up a new Nuxt project on your computer.
Open a terminal (if you're using [Visual Studio Code](https://code.visualstudio.com), you can open an [integrated terminal](https://code.visualstudio.com/docs/editor/integrated-terminal)) and use the following command to create a new starter project: Open a terminal (if you're using [Visual Studio Code](https://code.visualstudio.com), you can open an [integrated terminal](https://code.visualstudio.com/docs/editor/integrated-terminal)) and use the following command to create a new starter project:
::code-group ::package-managers
```bash [npm] ```bash [npm]
npx nuxi@latest init <project-name> npx nuxi@latest init <project-name>
@ -75,16 +75,16 @@ cd <project-name>
Now you'll be able to start your Nuxt app in development mode: Now you'll be able to start your Nuxt app in development mode:
::code-group ::package-managers
```bash [yarn]
yarn dev --open
```
```bash [npm] ```bash [npm]
npm run dev -- -o npm run dev -- -o
``` ```
```bash [yarn]
yarn dev --open
```
```bash [pnpm] ```bash [pnpm]
pnpm dev -o pnpm dev -o
``` ```

View File

@ -77,7 +77,7 @@ h1 {
You can also reference stylesheets that are distributed through npm. Let's use the popular `animate.css` library as an example. You can also reference stylesheets that are distributed through npm. Let's use the popular `animate.css` library as an example.
::code-group ::package-managers
```bash [npm] ```bash [npm]
npm install animate.css npm install animate.css

View File

@ -14,7 +14,7 @@ Use the [`nuxi generate` command](/docs/api/commands/generate) to build and pre-
This will build your site, stand up a nuxt instance, and, by default, prerender the root page `/` along with any of your site's pages it links to, any of your site's pages they link to, and so on. This will build your site, stand up a nuxt instance, and, by default, prerender the root page `/` along with any of your site's pages it links to, any of your site's pages they link to, and so on.
::code-group ::package-managers
```bash [npm] ```bash [npm]
npx nuxi generate npx nuxi generate

View File

@ -203,7 +203,7 @@ You can explore open source examples deployed on some of the platform mentioned
target: _blank target: _blank
ui.icon.base: text-black dark:text-white ui.icon.base: text-black dark:text-white
--- ---
An editable website with universal rendering based on CloudFlare KV. An editable website with universal rendering based on Cloudflare KV.
:: ::
:: ::

View File

@ -9,16 +9,16 @@ By default, Nuxt doesn't check types when you run [`nuxi dev`](/docs/api/command
To enable type-checking at build or development time, install `vue-tsc` and `typescript` as development dependency: To enable type-checking at build or development time, install `vue-tsc` and `typescript` as development dependency:
::code-group ::package-managers
```bash [yarn]
yarn add --dev vue-tsc typescript
```
```bash [npm] ```bash [npm]
npm install --save-dev vue-tsc typescript npm install --save-dev vue-tsc typescript
``` ```
```bash [yarn]
yarn add --dev vue-tsc typescript
```
```bash [pnpm] ```bash [pnpm]
pnpm add -D vue-tsc typescript pnpm add -D vue-tsc typescript
``` ```

View File

@ -246,13 +246,13 @@ If you want to use Vue plugins, like [vue-gtag](https://github.com/MatteoGabriel
First, install the Vue plugin dependency: First, install the Vue plugin dependency:
::code-group ::package-managers
```bash [yarn]
yarn add --dev vue-gtag-next
```
```bash [npm] ```bash [npm]
npm install --save-dev vue-gtag-next npm install --save-dev vue-gtag-next
``` ```
```bash [yarn]
yarn add --dev vue-gtag-next
```
```bash [pnpm] ```bash [pnpm]
pnpm add -D vue-gtag-next pnpm add -D vue-gtag-next
``` ```

View File

@ -13,7 +13,7 @@ With modules, you can encapsulate, properly test, and share custom solutions as
We recommend you get started with Nuxt Modules using our [starter template](https://github.com/nuxt/starter/tree/module): We recommend you get started with Nuxt Modules using our [starter template](https://github.com/nuxt/starter/tree/module):
::code-group ::package-managers
```bash [npm] ```bash [npm]
npx nuxi init -t module my-module npx nuxi init -t module my-module
@ -228,9 +228,9 @@ Learn more about asset injection in [the recipes section](#recipes).
::warning ::warning
Published modules cannot leverage auto-imports for assets within their runtime directory. Instead, they have to import them explicitly from `#imports` or alike. Published modules cannot leverage auto-imports for assets within their runtime directory. Instead, they have to import them explicitly from `#imports` or alike.
:br :br
Indeed, auto-imports are not enabled for files within `node_modules` (the location where a published module will eventually live) for performance reasons. Indeed, auto-imports are not enabled for files within `node_modules` (the location where a published module will eventually live) for performance reasons.
:br :br
If you are using the module starter, auto-imports will not be enabled in your playground either. If you are using the module starter, auto-imports will not be enabled in your playground either.
:: ::
@ -638,7 +638,7 @@ Testing helps ensuring your module works as expected given various setup. Find i
::tip ::tip
We're still discussing and exploring how to ease unit and integration testing on Nuxt Modules. We're still discussing and exploring how to ease unit and integration testing on Nuxt Modules.
:br :br
[Check out this RFC to join the conversation](https://github.com/nuxt/nuxt/discussions/18399). [Check out this RFC to join the conversation](https://github.com/nuxt/nuxt/discussions/18399).
:: ::

View File

@ -8,7 +8,7 @@ While Nuxt modules offer extensive functionality, sometimes a specific Vite plug
First, we need to install the Vite plugin, for our example, we'll use `@rollup/plugin-yaml`: First, we need to install the Vite plugin, for our example, we'll use `@rollup/plugin-yaml`:
::code-group ::package-managers
```bash [npm] ```bash [npm]
npm install @rollup/plugin-yaml npm install @rollup/plugin-yaml

View File

@ -124,6 +124,7 @@ When not using `external`, `<NuxtLink>` supports all Vue Router's [`RouterLink`
- `noRel`: If set to `true`, no `rel` attribute will be added to the link - `noRel`: If set to `true`, no `rel` attribute will be added to the link
- `external`: Forces the link to be rendered as an `a` tag instead of a Vue Router `RouterLink`. - `external`: Forces the link to be rendered as an `a` tag instead of a Vue Router `RouterLink`.
- `prefetch`: When enabled will prefetch middleware, layouts and payloads (when using [payloadExtraction](/docs/api/nuxt-config#crossoriginprefetch)) of links in the viewport. Used by the experimental [crossOriginPrefetch](/docs/api/nuxt-config#crossoriginprefetch) config. - `prefetch`: When enabled will prefetch middleware, layouts and payloads (when using [payloadExtraction](/docs/api/nuxt-config#crossoriginprefetch)) of links in the viewport. Used by the experimental [crossOriginPrefetch](/docs/api/nuxt-config#crossoriginprefetch) config.
- `prefetchOn`: Allows custom control of when to prefetch links. Possible options are `interaction` and `visibility` (default). You can also pass an object for full control, for example: `{ interaction: true, visibility: true }`. This prop is only used when `prefetch` is enabled (default) and `noPrefetch` is not set.
- `noPrefetch`: Disables prefetching. - `noPrefetch`: Disables prefetching.
- `prefetchedClass`: A class to apply to links that have been prefetched. - `prefetchedClass`: A class to apply to links that have been prefetched.
@ -185,8 +186,13 @@ interface NuxtLinkOptions {
externalRelAttribute?: string; externalRelAttribute?: string;
activeClass?: string; activeClass?: string;
exactActiveClass?: string; exactActiveClass?: string;
prefetchedClass?: string;
trailingSlash?: 'append' | 'remove' trailingSlash?: 'append' | 'remove'
prefetch?: boolean
prefetchedClass?: string
prefetchOn?: Partial<{
visibility: boolean
interaction: boolean
}>
} }
function defineNuxtLink(options: NuxtLinkOptions): Component {} function defineNuxtLink(options: NuxtLinkOptions): Component {}
``` ```
@ -195,7 +201,9 @@ function defineNuxtLink(options: NuxtLinkOptions): Component {}
- `externalRelAttribute`: A default `rel` attribute value applied on external links. Defaults to `"noopener noreferrer"`. Set it to `""` to disable - `externalRelAttribute`: A default `rel` attribute value applied on external links. Defaults to `"noopener noreferrer"`. Set it to `""` to disable
- `activeClass`: A default class to apply on active links. Works the same as [Vue Router's `linkActiveClass` option](https://router.vuejs.org/api/interfaces/RouterOptions.html#Properties-linkActiveClass). Defaults to Vue Router's default (`"router-link-active"`) - `activeClass`: A default class to apply on active links. Works the same as [Vue Router's `linkActiveClass` option](https://router.vuejs.org/api/interfaces/RouterOptions.html#Properties-linkActiveClass). Defaults to Vue Router's default (`"router-link-active"`)
- `exactActiveClass`: A default class to apply on exact active links. Works the same as [Vue Router's `linkExactActiveClass` option](https://router.vuejs.org/api/interfaces/RouterOptions.html#Properties-linkExactActiveClass). Defaults to Vue Router's default (`"router-link-exact-active"`) - `exactActiveClass`: A default class to apply on exact active links. Works the same as [Vue Router's `linkExactActiveClass` option](https://router.vuejs.org/api/interfaces/RouterOptions.html#Properties-linkExactActiveClass). Defaults to Vue Router's default (`"router-link-exact-active"`)
- `prefetchedClass`: A default class to apply to links that have been prefetched.
- `trailingSlash`: An option to either add or remove trailing slashes in the `href`. If unset or not matching the valid values `append` or `remove`, it will be ignored. - `trailingSlash`: An option to either add or remove trailing slashes in the `href`. If unset or not matching the valid values `append` or `remove`, it will be ignored.
- `prefetch`: Whether or not to prefetch links by default.
- `prefetchOn`: Granular control of which prefetch strategies to apply by default.
- `prefetchedClass`: A default class to apply to links that have been prefetched.
:link-example{to="/docs/examples/routing/pages"} :link-example{to="/docs/examples/routing/pages"}

View File

@ -72,7 +72,7 @@ const { data: posts } = await useAsyncData(
- `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled. - `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled.
- `pick`: only pick specified keys in this array from the `handler` function result - `pick`: only pick specified keys in this array from the `handler` function result
- `watch`: watch reactive sources to auto-refresh - `watch`: watch reactive sources to auto-refresh
- `deep`: return data in a deep ref object (it is `true` by default). It can be set to `false` to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive. - `deep`: return data in a deep ref object. It is `false` by default to return data in a shallow ref object for performance.
- `dedupe`: avoid fetching same key more than once at a time (defaults to `cancel`). Possible options: - `dedupe`: avoid fetching same key more than once at a time (defaults to `cancel`). Possible options:
- `cancel` - cancels existing requests when a new one is made - `cancel` - cancels existing requests when a new one is made
- `defer` - does not make new requests at all if there is a pending request - `defer` - does not make new requests at all if there is a pending request

View File

@ -108,7 +108,7 @@ All fetch options can be given a `computed` or `ref` value. These will be watche
- `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled. - `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled.
- `pick`: only pick specified keys in this array from the `handler` function result - `pick`: only pick specified keys in this array from the `handler` function result
- `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`. (You can [see an example here](/docs/getting-started/data-fetching#watch) of using `watch`.) - `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`. (You can [see an example here](/docs/getting-started/data-fetching#watch) of using `watch`.)
- `deep`: return data in a deep ref object (it is `true` by default). It can be set to `false` to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive. - `deep`: return data in a deep ref object. It is `false` by default to return data in a shallow ref object for performance.
- `dedupe`: avoid fetching same key more than once at a time (defaults to `cancel`). Possible options: - `dedupe`: avoid fetching same key more than once at a time (defaults to `cancel`). Possible options:
- `cancel` - cancels existing requests when a new one is made - `cancel` - cancels existing requests when a new one is made
- `defer` - does not make new requests at all if there is a pending request - `defer` - does not make new requests at all if there is a pending request

View File

@ -4,7 +4,7 @@ description: 'Access runtime config variables with the useRuntimeConfig composab
links: links:
- label: Source - label: Source
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/asyncData.ts to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/nuxt.ts
size: xs size: xs
--- ---

View File

@ -28,16 +28,15 @@ Make sure your dev server (`nuxt dev`) isn't running, remove any package lock fi
Then, reinstall your dependencies: Then, reinstall your dependencies:
::code-group ::package-managers
```bash [yarn]
yarn install
```
```bash [npm] ```bash [npm]
npm install npm install
``` ```
```bash [yarn]
yarn install
```
:: ::
::note ::note
@ -48,16 +47,16 @@ Once the installation is complete, make sure both development and production bui
Install `@nuxt/bridge` and `nuxi` as development dependencies: Install `@nuxt/bridge` and `nuxi` as development dependencies:
::code-group ::package-managers
```bash [Yarn]
yarn add --dev @nuxt/bridge nuxi
```
```bash [npm] ```bash [npm]
npm install -D @nuxt/bridge nuxi npm install -D @nuxt/bridge nuxi
``` ```
```bash [yarn]
yarn add --dev @nuxt/bridge nuxi
```
:: ::
### Update `nuxt.config` ### Update `nuxt.config`

View File

@ -27,16 +27,16 @@ You will also need to update your scripts within your `package.json` to reflect
Install `nuxi` as a development dependency: Install `nuxi` as a development dependency:
::code-group ::package-managers
```bash [yarn]
yarn add --dev nuxi
```
```bash [npm] ```bash [npm]
npm install -D nuxi npm install -D nuxi
``` ```
```bash [yarn]
yarn add --dev nuxi
```
:: ::
### Nuxi ### Nuxi

View File

@ -39,17 +39,17 @@
"@nuxt/ui-templates": "workspace:*", "@nuxt/ui-templates": "workspace:*",
"@nuxt/vite-builder": "workspace:*", "@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*",
"@types/node": "20.14.15", "@types/node": "20.16.1",
"c12": "2.0.0-beta.1", "c12": "2.0.0-beta.1",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"jiti": "2.0.0-beta.3", "jiti": "2.0.0-beta.3",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"nuxt": "workspace:*", "nuxt": "workspace:*",
"rollup": "^4.20.0", "rollup": "^4.21.0",
"typescript": "5.5.4", "typescript": "5.5.4",
"unbuild": "3.0.0-rc.7", "unbuild": "3.0.0-rc.7",
"vite": "5.4.0", "vite": "5.4.1",
"vue": "3.5.0-beta.1", "vue": "3.5.0-beta.1",
"@vue/compiler-core": "3.5.0-beta.1", "@vue/compiler-core": "3.5.0-beta.1",
"@vue/compiler-dom": "3.5.0-beta.1", "@vue/compiler-dom": "3.5.0-beta.1",
@ -59,30 +59,30 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "9.9.0", "@eslint/js": "9.9.0",
"@nuxt/eslint-config": "0.5.0", "@nuxt/eslint-config": "0.5.1",
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@nuxt/test-utils": "3.14.1", "@nuxt/test-utils": "3.14.1",
"@nuxt/webpack-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/eslint__js": "8.42.3", "@types/eslint__js": "8.42.3",
"@types/node": "20.14.15", "@types/node": "20.16.1",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"@unhead/schema": "1.9.16", "@unhead/schema": "1.9.16",
"@vitejs/plugin-vue": "5.1.2", "@vitejs/plugin-vue": "5.1.2",
"@vitest/coverage-v8": "2.0.5", "@vitest/coverage-v8": "2.0.5",
"@vue/test-utils": "2.4.6", "@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.20",
"case-police": "0.6.1", "case-police": "0.7.0",
"changelogen": "0.5.5", "changelogen": "0.5.5",
"consola": "3.2.3", "consola": "3.2.3",
"cssnano": "7.0.5", "cssnano": "7.0.5",
"destr": "2.0.3", "destr": "2.0.3",
"devalue": "5.0.0", "devalue": "5.0.0",
"eslint": "9.9.0", "eslint": "9.9.0",
"eslint-plugin-no-only-tests": "3.1.0", "eslint-plugin-no-only-tests": "3.3.0",
"eslint-plugin-perfectionist": "3.1.3", "eslint-plugin-perfectionist": "3.2.0",
"eslint-typegen": "0.3.0", "eslint-typegen": "0.3.1",
"execa": "9.3.0", "execa": "9.3.1",
"globby": "14.0.2", "globby": "14.0.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"happy-dom": "14.12.3", "happy-dom": "14.12.3",
@ -94,19 +94,19 @@
"nuxt-content-twoslash": "0.1.1", "nuxt-content-twoslash": "0.1.1",
"ofetch": "1.3.4", "ofetch": "1.3.4",
"pathe": "1.1.2", "pathe": "1.1.2",
"playwright-core": "1.46.0", "playwright-core": "1.46.1",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"semver": "7.6.3", "semver": "7.6.3",
"std-env": "3.7.0", "std-env": "3.7.0",
"typescript": "5.5.4", "typescript": "5.5.4",
"ufo": "1.5.4", "ufo": "1.5.4",
"vitest": "2.0.5", "vitest": "2.0.5",
"vitest-environment-nuxt": "1.0.0", "vitest-environment-nuxt": "1.0.1",
"vue": "3.4.37", "vue": "3.4.38",
"vue-router": "4.4.3", "vue-router": "4.4.3",
"vue-tsc": "2.0.29" "vue-tsc": "2.0.29"
}, },
"packageManager": "pnpm@9.7.0", "packageManager": "pnpm@9.7.1",
"engines": { "engines": {
"node": "^16.10.0 || >=18.0.0" "node": "^16.10.0 || >=18.0.0"
}, },

View File

@ -52,7 +52,7 @@
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda", "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"unbuild": "3.0.0-rc.7", "unbuild": "3.0.0-rc.7",
"vite": "5.4.0", "vite": "5.4.1",
"vitest": "2.0.5", "vitest": "2.0.5",
"webpack": "5.93.0" "webpack": "5.93.0"
}, },

View File

@ -68,7 +68,7 @@
"@unhead/dom": "^1.9.16", "@unhead/dom": "^1.9.16",
"@unhead/ssr": "^1.9.16", "@unhead/ssr": "^1.9.16",
"@unhead/vue": "^1.9.16", "@unhead/vue": "^1.9.16",
"@vue/shared": "^3.4.37", "@vue/shared": "^3.4.38",
"acorn": "8.12.1", "acorn": "8.12.1",
"c12": "^2.0.0-beta.1", "c12": "^2.0.0-beta.1",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
@ -79,7 +79,7 @@
"destr": "^2.0.3", "destr": "^2.0.3",
"devalue": "^5.0.0", "devalue": "^5.0.0",
"errx": "^0.1.0", "errx": "^0.1.0",
"esbuild": "^0.23.0", "esbuild": "^0.23.1",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"globby": "^14.0.2", "globby": "^14.0.2",
@ -110,11 +110,11 @@
"unctx": "^2.3.1", "unctx": "^2.3.1",
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unimport": "^3.10.0", "unimport": "^3.10.0",
"unplugin": "^1.12.1", "unplugin": "^1.12.2",
"unplugin-vue-router": "^0.10.3", "unplugin-vue-router": "^0.10.7",
"unstorage": "^1.10.2", "unstorage": "^1.10.2",
"untyped": "^1.4.2", "untyped": "^1.4.2",
"vue": "^3.4.37", "vue": "^3.4.38",
"vue-bundle-renderer": "^2.1.0", "vue-bundle-renderer": "^2.1.0",
"vue-devtools-stub": "^0.1.0", "vue-devtools-stub": "^0.1.0",
"vue-router": "^4.4.3" "vue-router": "^4.4.3"
@ -125,9 +125,9 @@
"@parcel/watcher": "2.4.1", "@parcel/watcher": "2.4.1",
"@types/estree": "1.0.5", "@types/estree": "1.0.5",
"@vitejs/plugin-vue": "5.1.2", "@vitejs/plugin-vue": "5.1.2",
"@vue/compiler-sfc": "3.4.37", "@vue/compiler-sfc": "3.4.38",
"unbuild": "3.0.0-rc.7", "unbuild": "3.0.0-rc.7",
"vite": "5.4.0", "vite": "5.4.1",
"vitest": "2.0.5" "vitest": "2.0.5"
}, },
"peerDependencies": { "peerDependencies": {

View File

@ -3,6 +3,7 @@ import type { ComponentInternalInstance, ComponentOptions, InjectionKey } from '
import { isPromise } from '@vue/shared' import { isPromise } from '@vue/shared'
import { useNuxtApp } from '../nuxt' import { useNuxtApp } from '../nuxt'
import { getFragmentHTML } from './utils' import { getFragmentHTML } from './utils'
import ServerPlaceholder from './server-placeholder'
export const clientOnlySymbol: InjectionKey<boolean> = Symbol.for('nuxt:client-only') export const clientOnlySymbol: InjectionKey<boolean> = Symbol.for('nuxt:client-only')
@ -36,6 +37,9 @@ const cache = new WeakMap()
/* @__NO_SIDE_EFFECTS__ */ /* @__NO_SIDE_EFFECTS__ */
export function createClientOnly<T extends ComponentOptions> (component: T) { export function createClientOnly<T extends ComponentOptions> (component: T) {
if (import.meta.server) {
return ServerPlaceholder
}
if (cache.has(component)) { if (cache.has(component)) {
return cache.get(component) return cache.get(component)
} }
@ -45,15 +49,14 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
if (clone.render) { if (clone.render) {
// override the component render (non script setup component) or dev mode // override the component render (non script setup component) or dev mode
clone.render = (ctx: any, cache: any, $props: any, $setup: any, $data: any, $options: any) => { clone.render = (ctx: any, cache: any, $props: any, $setup: any, $data: any, $options: any) => {
// import.meta.client for server-side treeshakking if ($setup.mounted$ ?? ctx.mounted$) {
if (import.meta.client && ($setup.mounted$ ?? ctx.mounted$)) {
const res = component.render?.bind(ctx)(ctx, cache, $props, $setup, $data, $options) const res = component.render?.bind(ctx)(ctx, cache, $props, $setup, $data, $options)
return (res.children === null || typeof res.children === 'string') return (res.children === null || typeof res.children === 'string')
? cloneVNode(res) ? cloneVNode(res)
: h(res) : h(res)
} else { } else {
const fragment = getFragmentHTML(ctx._.vnode.el ?? null) ?? ['<div></div>'] const fragment = getFragmentHTML(ctx._.vnode.el ?? null) ?? ['<div></div>']
return import.meta.client ? createStaticVNode(fragment.join(''), fragment.length) : h('div', ctx.$attrs ?? ctx._.attrs) return createStaticVNode(fragment.join(''), fragment.length)
} }
} }
} else if (clone.template) { } else if (clone.template) {
@ -66,10 +69,10 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
clone.setup = (props, ctx) => { clone.setup = (props, ctx) => {
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
const mounted$ = ref(import.meta.client && nuxtApp.isHydrating === false) const mounted$ = ref(nuxtApp.isHydrating === false)
const instance = getCurrentInstance()! const instance = getCurrentInstance()!
if (import.meta.server || nuxtApp.isHydrating) { if (nuxtApp.isHydrating) {
const attrs = { ...instance.attrs } const attrs = { ...instance.attrs }
// remove existing directives during hydration // remove existing directives during hydration
const directives = extractDirectives(instance) const directives = extractDirectives(instance)
@ -97,14 +100,14 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
return setupState return setupState
} }
return (...args: any[]) => { return (...args: any[]) => {
if (import.meta.client && (mounted$.value || !nuxtApp.isHydrating)) { if (mounted$.value || !nuxtApp.isHydrating) {
const res = setupState(...args) const res = setupState(...args)
return (res.children === null || typeof res.children === 'string') return (res.children === null || typeof res.children === 'string')
? cloneVNode(res) ? cloneVNode(res)
: h(res) : h(res)
} else { } else {
const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['<div></div>'] const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['<div></div>']
return import.meta.client ? createStaticVNode(fragment.join(''), fragment.length) : h('div', ctx.attrs) return createStaticVNode(fragment.join(''), fragment.length)
} }
} }
}) })
@ -115,9 +118,7 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
return h(setupState(...args), ctx.attrs) return h(setupState(...args), ctx.attrs)
} }
const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['<div></div>'] const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['<div></div>']
return import.meta.client return createStaticVNode(fragment.join(''), fragment.length)
? createStaticVNode(fragment.join(''), fragment.length) :
h('div', ctx.attrs)
} }
} }
return Object.assign(setupState, { mounted$ }) return Object.assign(setupState, { mounted$ })

View File

@ -59,6 +59,13 @@ export interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
* When enabled will prefetch middleware, layouts and payloads of links in the viewport. * When enabled will prefetch middleware, layouts and payloads of links in the viewport.
*/ */
prefetch?: boolean prefetch?: boolean
/**
* Allows controlling when to prefetch links. By default, prefetch is triggered only on visibility.
*/
prefetchOn?: 'visibility' | 'interaction' | Partial<{
visibility: boolean
interaction: boolean
}>
/** /**
* Escape hatch to disable `prefetch` attribute. * Escape hatch to disable `prefetch` attribute.
*/ */
@ -71,7 +78,7 @@ export interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
*/ */
export interface NuxtLinkOptions extends export interface NuxtLinkOptions extends
Partial<Pick<RouterLinkProps, 'activeClass' | 'exactActiveClass'>>, Partial<Pick<RouterLinkProps, 'activeClass' | 'exactActiveClass'>>,
Partial<Pick<NuxtLinkProps, 'prefetchedClass'>> { Partial<Pick<NuxtLinkProps, 'prefetch' | 'prefetchedClass'>> {
/** /**
* The name of the component. * The name of the component.
* @default "NuxtLink" * @default "NuxtLink"
@ -86,6 +93,11 @@ export interface NuxtLinkOptions extends
* If unset or not matching the valid values `append` or `remove`, it will be ignored. * If unset or not matching the valid values `append` or `remove`, it will be ignored.
*/ */
trailingSlash?: 'append' | 'remove' trailingSlash?: 'append' | 'remove'
/**
* Allows controlling default setting for when to prefetch links. By default, prefetch is triggered only on visibility.
*/
prefetchOn?: Exclude<NuxtLinkProps['prefetchOn'], string>
} }
/* @__NO_SIDE_EFFECTS__ */ /* @__NO_SIDE_EFFECTS__ */
@ -239,6 +251,14 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
default: undefined, default: undefined,
required: false, required: false,
}, },
prefetchOn: {
type: [String, Object] as PropType<NuxtLinkProps['prefetchOn']>,
default: options.prefetchOn || {
visibility: true,
interaction: false,
} satisfies NuxtLinkProps['prefetchOn'],
required: false,
},
noPrefetch: { noPrefetch: {
type: Boolean as PropType<NuxtLinkProps['noPrefetch']>, type: Boolean as PropType<NuxtLinkProps['noPrefetch']>,
default: undefined, default: undefined,
@ -299,10 +319,27 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
const el = import.meta.server ? undefined : ref<HTMLElement | null>(null) const el = import.meta.server ? undefined : ref<HTMLElement | null>(null)
const elRef = import.meta.server ? undefined : (ref: any) => { el!.value = props.custom ? ref?.$el?.nextElementSibling : ref?.$el } const elRef = import.meta.server ? undefined : (ref: any) => { el!.value = props.custom ? ref?.$el?.nextElementSibling : ref?.$el }
function shouldPrefetch (mode: 'visibility' | 'interaction') {
return !prefetched.value && (typeof props.prefetchOn === 'string' ? props.prefetchOn === mode : (props.prefetchOn?.[mode] ?? options.prefetchOn?.[mode])) && (props.prefetch ?? options.prefetch) !== false && props.noPrefetch !== true && props.target !== '_blank' && !isSlowConnection()
}
async function prefetch (nuxtApp = useNuxtApp()) {
if (prefetched.value) { return }
prefetched.value = true
const path = typeof to.value === 'string'
? to.value
: isExternal.value ? resolveRouteObject(to.value) : router.resolve(to.value).fullPath
await Promise.all([
nuxtApp.hooks.callHook('link:prefetch', path).catch(() => {}),
!isExternal.value && !hasTarget.value && preloadRouteComponents(to.value as string, router).catch(() => {}),
])
}
if (import.meta.client) { if (import.meta.client) {
checkPropConflicts(props, 'prefetch', 'noPrefetch') checkPropConflicts(props, 'prefetch', 'noPrefetch')
const shouldPrefetch = props.prefetch !== false && props.noPrefetch !== true && props.target !== '_blank' && !isSlowConnection() if (shouldPrefetch('visibility')) {
if (shouldPrefetch) {
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
let idleId: number let idleId: number
let unobserve: (() => void) | null = null let unobserve: (() => void) | null = null
@ -314,15 +351,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
unobserve = observer!.observe(el.value as HTMLElement, async () => { unobserve = observer!.observe(el.value as HTMLElement, async () => {
unobserve?.() unobserve?.()
unobserve = null unobserve = null
await prefetch(nuxtApp)
const path = typeof to.value === 'string'
? to.value
: isExternal.value ? resolveRouteObject(to.value) : router.resolve(to.value).fullPath
await Promise.all([
nuxtApp.hooks.callHook('link:prefetch', path).catch(() => {}),
!isExternal.value && !hasTarget.value && preloadRouteComponents(to.value as string, router).catch(() => {}),
])
prefetched.value = true
}) })
} }
}) })
@ -355,6 +384,8 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
replace: props.replace, replace: props.replace,
ariaCurrentValue: props.ariaCurrentValue, ariaCurrentValue: props.ariaCurrentValue,
custom: props.custom, custom: props.custom,
onPointerenter: shouldPrefetch('interaction') ? prefetch.bind(null, undefined) : undefined,
onFocus: shouldPrefetch('interaction') ? prefetch.bind(null, undefined) : undefined,
} }
// `custom` API cannot support fallthrough attributes as the slot // `custom` API cannot support fallthrough attributes as the slot

View File

@ -84,7 +84,7 @@ export interface AsyncDataOptions<
*/ */
immediate?: boolean immediate?: boolean
/** /**
* Return data in a deep ref object (it is true by default). It can be set to false to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive. * Return data in a deep ref object (it is false by default). It can be set to false to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive.
*/ */
deep?: boolean deep?: boolean
/** /**
@ -350,7 +350,7 @@ export function useAsyncData<
if (import.meta.client) { if (import.meta.client) {
// Setup hook callbacks once per instance // Setup hook callbacks once per instance
const instance = getCurrentInstance() const instance = getCurrentInstance()
if (import.meta.dev && !nuxtApp.isHydrating && (!instance || instance?.isMounted)) { if (import.meta.dev && !nuxtApp.isHydrating && !nuxtApp._processingMiddleware /* internal flag */ && (!instance || instance?.isMounted)) {
// @ts-expect-error private property // @ts-expect-error private property
console.warn(`[nuxt] [${options._functionName || 'useAsyncData'}] Component is already mounted, please use $fetch instead. See https://nuxt.com/docs/getting-started/data-fetching`) console.warn(`[nuxt] [${options._functionName || 'useAsyncData'}] Component is already mounted, please use $fetch instead. See https://nuxt.com/docs/getting-started/data-fetching`)
} }

View File

@ -9,7 +9,7 @@ import { useRoute } from './router'
import { getAppManifest, getRouteRules } from './manifest' import { getAppManifest, getRouteRules } from './manifest'
// @ts-expect-error virtual import // @ts-expect-error virtual import
import { appManifest, payloadExtraction, renderJsonPayloads } from '#build/nuxt.config.mjs' import { appId, appManifest, multiApp, payloadExtraction, renderJsonPayloads } from '#build/nuxt.config.mjs'
interface LoadPayloadOptions { interface LoadPayloadOptions {
fresh?: boolean fresh?: boolean
@ -107,7 +107,7 @@ export async function getNuxtClientPayload () {
return payloadCache return payloadCache
} }
const el = document.getElementById('__NUXT_DATA__') const el = multiApp ? document.querySelector(`[data-nuxt-data="${appId}"]`) as HTMLElement : document.getElementById('__NUXT_DATA__')
if (!el) { if (!el) {
return {} as Partial<NuxtPayload> return {} as Partial<NuxtPayload>
} }
@ -119,7 +119,7 @@ export async function getNuxtClientPayload () {
payloadCache = { payloadCache = {
...inlineData, ...inlineData,
...externalData, ...externalData,
...window.__NUXT__, ...(multiApp ? window.__NUXT__?.[appId] : window.__NUXT__),
} }
if (payloadCache!.config?.public) { if (payloadCache!.config?.public) {

View File

@ -15,7 +15,7 @@ import plugins from '#build/plugins'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import RootComponent from '#build/root-component.mjs' import RootComponent from '#build/root-component.mjs'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import { vueAppRootContainer } from '#build/nuxt.config.mjs' import { appId, multiApp, vueAppRootContainer } from '#build/nuxt.config.mjs'
let entry: (ssrContext?: CreateOptions['ssrContext']) => Promise<App<Element>> let entry: (ssrContext?: CreateOptions['ssrContext']) => Promise<App<Element>>
@ -50,9 +50,10 @@ if (import.meta.client) {
entry = async function initApp () { entry = async function initApp () {
if (vueAppPromise) { return vueAppPromise } if (vueAppPromise) { return vueAppPromise }
const isSSR = Boolean( const isSSR = Boolean(
window.__NUXT__?.serverRendered || (multiApp ? window.__NUXT__?.[appId] : window.__NUXT__)?.serverRendered ??
document.getElementById('__NUXT_DATA__')?.dataset.ssr === 'true', (multiApp ? document.querySelector(`[data-nuxt-data="${appId}"]`) as HTMLElement : document.getElementById('__NUXT_DATA__'))?.dataset.ssr === 'true',
) )
const vueApp = isSSR ? createSSRApp(RootComponent) : createApp(RootComponent) const vueApp = isSSR ? createSSRApp(RootComponent) : createApp(RootComponent)

View File

@ -21,7 +21,7 @@ import type { RouteAnnouncer } from '../app/composables/route-announcer'
import type { ViewTransition } from './plugins/view-transitions.client' import type { ViewTransition } from './plugins/view-transitions.client'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import { appId } from '#build/nuxt.config.mjs' import { appId, multiApp } from '#build/nuxt.config.mjs'
import type { NuxtAppLiterals } from '#app' import type { NuxtAppLiterals } from '#app'
@ -310,19 +310,22 @@ export function createNuxtApp (options: CreateOptions) {
nuxtApp.payload.serverRendered = true nuxtApp.payload.serverRendered = true
} }
// TODO: remove/refactor in https://github.com/nuxt/nuxt/issues/25336 if (import.meta.client) {
if (import.meta.client && window.__NUXT__) { const __NUXT__ = multiApp ? window.__NUXT__?.[nuxtApp._id] : window.__NUXT__
for (const key in window.__NUXT__) { // TODO: remove/refactor in https://github.com/nuxt/nuxt/issues/25336
switch (key) { if (__NUXT__) {
case 'data': for (const key in __NUXT__) {
case 'state': switch (key) {
case '_errors': case 'data':
// Preserve reactivity for non-rich payload support case 'state':
Object.assign(nuxtApp.payload[key], window.__NUXT__[key]) case '_errors':
break // Preserve reactivity for non-rich payload support
Object.assign(nuxtApp.payload[key], __NUXT__[key])
break
default: default:
nuxtApp.payload[key] = window.__NUXT__[key] nuxtApp.payload[key] = __NUXT__[key]
}
} }
} }
} }

View File

@ -40,7 +40,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
} }
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
const content = document.getElementById('__NUXT_LOGS__')?.textContent const nuxtLogsElement = document.querySelector(`[data-nuxt-logs="${nuxtApp._name}"]`)
const content = nuxtLogsElement?.textContent
const logs = content ? parse(content, { ...devRevivers, ...nuxtApp._payloadRevivers }) as LogObject[] : [] const logs = content ? parse(content, { ...devRevivers, ...nuxtApp._payloadRevivers }) as LogObject[] : []
await nuxtApp.hooks.callHook('dev:ssr-logs', logs) await nuxtApp.hooks.callHook('dev:ssr-logs', logs)
} }

View File

@ -26,7 +26,7 @@ declare global {
} }
interface Window { interface Window {
__NUXT__?: Record<string, any> __NUXT__?: Record<string, any> | Record<string, Record<string, any>>
useNuxtApp?: typeof useNuxtApp useNuxtApp?: typeof useNuxtApp
} }
} }
@ -51,23 +51,3 @@ declare module 'vue' {
head?(nuxtApp: NuxtApp): UseHeadInput head?(nuxtApp: NuxtApp): UseHeadInput
} }
} }
declare module '@vue/runtime-core' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface App<HostElement> {
$nuxt: NuxtApp
}
interface ComponentCustomProperties {
$nuxt: NuxtApp
}
}
declare module '@vue/runtime-dom' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface App<HostElement> {
$nuxt: NuxtApp
}
interface ComponentCustomProperties {
$nuxt: NuxtApp
}
}

View File

@ -128,14 +128,6 @@ interface _GlobalComponents {
${componentTypes.map(([pascalName, type]) => ` 'LazyEvent${pascalName}': ${type} & DefineComponent<{hydrate?: Array<keyof HTMLElementEventMap>}>`).join('\n')} ${componentTypes.map(([pascalName, type]) => ` 'LazyEvent${pascalName}': ${type} & DefineComponent<{hydrate?: Array<keyof HTMLElementEventMap>}>`).join('\n')}
} }
declare module '@vue/runtime-core' {
export interface GlobalComponents extends _GlobalComponents { }
}
declare module '@vue/runtime-dom' {
export interface GlobalComponents extends _GlobalComponents { }
}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents extends _GlobalComponents { } export interface GlobalComponents extends _GlobalComponents { }
} }

View File

@ -828,7 +828,7 @@ function createPortalProperties (sourceValue: any, options: NuxtOptions, paths:
} }
const _getDefaultNuxtConfig = () => /* js */ const _getDefaultNuxtConfig = () => /* js */
`// https://nuxt.com/docs/api/configuration/nuxt-config `// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
devtools: { enabled: true } devtools: { enabled: true }
}) })

View File

@ -13,6 +13,8 @@ import type { NitroApp } from 'nitro/types'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import { rootDir } from '#internal/dev-server-logs-options' import { rootDir } from '#internal/dev-server-logs-options'
// @ts-expect-error virtual file
import { appId } from '#internal/nuxt.config.mjs'
const devReducers: Record<string, (data: any) => any> = { const devReducers: Record<string, (data: any) => any> = {
VNode: data => isVNode(data) ? { type: data.type, props: data.props } : undefined, VNode: data => isVNode(data) ? { type: data.type, props: data.props } : undefined,
@ -75,7 +77,7 @@ export default (nitroApp: NitroApp) => {
const ctx = asyncContext.tryUse() const ctx = asyncContext.tryUse()
if (!ctx) { return } if (!ctx) { return }
try { try {
htmlContext.bodyAppend.unshift(`<script type="application/json" id="__NUXT_LOGS__">${stringify(ctx.logs, { ...devReducers, ...ctx.event.context._payloadReducers })}</script>`) htmlContext.bodyAppend.unshift(`<script type="application/json" data-nuxt-logs="${appId}">${stringify(ctx.logs, { ...devReducers, ...ctx.event.context._payloadReducers })}</script>`)
} catch (e) { } catch (e) {
const shortError = e instanceof Error && 'toString' in e ? ` Received \`${e.toString()}\`.` : '' const shortError = e instanceof Error && 'toString' in e ? ` Received \`${e.toString()}\`.` : ''
console.warn(`[nuxt] Failed to stringify dev server logs.${shortError} You can define your own reducer/reviver for rich types following the instructions in https://nuxt.com/docs/api/composables/use-nuxt-app#payload.`) console.warn(`[nuxt] Failed to stringify dev server logs.${shortError} You can define your own reducer/reviver for rich types following the instructions in https://nuxt.com/docs/api/composables/use-nuxt-app#payload.`)

View File

@ -31,7 +31,7 @@ import { renderSSRHeadOptions } from '#internal/unhead.config.mjs'
import type { NuxtPayload, NuxtSSRContext } from '#app' import type { NuxtPayload, NuxtSSRContext } from '#app'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import { appHead, appRootAttrs, appRootTag, appTeleportAttrs, appTeleportTag, componentIslands } from '#internal/nuxt.config.mjs' import { appHead, appId, appRootAttrs, appRootTag, appTeleportAttrs, appTeleportTag, componentIslands, multiApp } from '#internal/nuxt.config.mjs'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import { buildAssetsURL, publicAssetsURL } from '#internal/nuxt/paths' import { buildAssetsURL, publicAssetsURL } from '#internal/nuxt/paths'
@ -427,10 +427,10 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
head.push({ head.push({
script: _PAYLOAD_EXTRACTION script: _PAYLOAD_EXTRACTION
? process.env.NUXT_JSON_PAYLOADS ? process.env.NUXT_JSON_PAYLOADS
? renderPayloadJsonScript({ id: '__NUXT_DATA__', ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL }) ? renderPayloadJsonScript({ ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL })
: renderPayloadScript({ ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL }) : renderPayloadScript({ ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL })
: process.env.NUXT_JSON_PAYLOADS : process.env.NUXT_JSON_PAYLOADS
? renderPayloadJsonScript({ id: '__NUXT_DATA__', ssrContext, data: ssrContext.payload }) ? renderPayloadJsonScript({ ssrContext, data: ssrContext.payload })
: renderPayloadScript({ ssrContext, data: ssrContext.payload }), : renderPayloadScript({ ssrContext, data: ssrContext.payload }),
}, { }, {
...headEntryOptions, ...headEntryOptions,
@ -586,21 +586,27 @@ function renderPayloadResponse (ssrContext: NuxtSSRContext) {
} satisfies RenderResponse } satisfies RenderResponse
} }
function renderPayloadJsonScript (opts: { id: string, ssrContext: NuxtSSRContext, data?: any, src?: string }): Script[] { function renderPayloadJsonScript (opts: { ssrContext: NuxtSSRContext, data?: any, src?: string }): Script[] {
const contents = opts.data ? stringify(opts.data, opts.ssrContext._payloadReducers) : '' const contents = opts.data ? stringify(opts.data, opts.ssrContext._payloadReducers) : ''
const payload: Script = { const payload: Script = {
'type': 'application/json', 'type': 'application/json',
'id': opts.id,
'innerHTML': contents, 'innerHTML': contents,
'data-nuxt-data': appId,
'data-ssr': !(process.env.NUXT_NO_SSR || opts.ssrContext.noSSR), 'data-ssr': !(process.env.NUXT_NO_SSR || opts.ssrContext.noSSR),
} }
if (!multiApp) {
payload.id = '__NUXT_DATA__'
}
if (opts.src) { if (opts.src) {
payload['data-src'] = opts.src payload['data-src'] = opts.src
} }
const config = uneval(opts.ssrContext.config)
return [ return [
payload, payload,
{ {
innerHTML: `window.__NUXT__={};window.__NUXT__.config=${uneval(opts.ssrContext.config)}`, innerHTML: multiApp
? `window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={config:${config}}`
: `window.__NUXT__={};window.__NUXT__.config=${config}`,
}, },
] ]
} }
@ -608,17 +614,22 @@ function renderPayloadJsonScript (opts: { id: string, ssrContext: NuxtSSRContext
function renderPayloadScript (opts: { ssrContext: NuxtSSRContext, data?: any, src?: string }): Script[] { function renderPayloadScript (opts: { ssrContext: NuxtSSRContext, data?: any, src?: string }): Script[] {
opts.data.config = opts.ssrContext.config opts.data.config = opts.ssrContext.config
const _PAYLOAD_EXTRACTION = import.meta.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !opts.ssrContext.noSSR const _PAYLOAD_EXTRACTION = import.meta.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !opts.ssrContext.noSSR
const nuxtData = devalue(opts.data)
if (_PAYLOAD_EXTRACTION) { if (_PAYLOAD_EXTRACTION) {
const singleAppPayload = `import p from "${opts.src}";window.__NUXT__={...p,...(${nuxtData})}`
const multiAppPayload = `import p from "${opts.src}";window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={...p,...(${nuxtData})}`
return [ return [
{ {
type: 'module', type: 'module',
innerHTML: `import p from "${opts.src}";window.__NUXT__={...p,...(${devalue(opts.data)})}`, innerHTML: multiApp ? multiAppPayload : singleAppPayload,
}, },
] ]
} }
const singleAppPayload = `window.__NUXT__=${nuxtData}`
const multiAppPayload = `window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]=${nuxtData}`
return [ return [
{ {
innerHTML: `window.__NUXT__=${devalue(opts.data)}`, innerHTML: multiApp ? multiAppPayload : singleAppPayload,
}, },
] ]
} }

View File

@ -96,6 +96,8 @@ export const serverPluginTemplate: NuxtTemplate = {
}, },
} }
const TS_RE = /\.[cm]?tsx?$/
export const pluginsDeclaration: NuxtTemplate = { export const pluginsDeclaration: NuxtTemplate = {
filename: 'types/plugins.d.ts', filename: 'types/plugins.d.ts',
getContents: async ({ nuxt, app }) => { getContents: async ({ nuxt, app }) => {
@ -124,9 +126,14 @@ export const pluginsDeclaration: NuxtTemplate = {
} }
if (exists(pluginPath)) { if (exists(pluginPath)) {
tsImports.push(relativePath.replace(EXTENSION_RE, '')) if (TS_RE.test(pluginPath)) {
continue tsImports.push(relativePath.replace(EXTENSION_RE, ''))
continue
}
tsImports.push(relativePath)
} }
// No declaration found that TypeScript can use
} }
return `// Generated by Nuxt' return `// Generated by Nuxt'
@ -147,14 +154,6 @@ declare module '#app' {
} }
} }
declare module '@vue/runtime-core' {
interface ComponentCustomProperties extends NuxtAppInjections { }
}
declare module '@vue/runtime-dom' {
interface ComponentCustomProperties extends NuxtAppInjections { }
}
declare module 'vue' { declare module 'vue' {
interface ComponentCustomProperties extends NuxtAppInjections { } interface ComponentCustomProperties extends NuxtAppInjections { }
} }
@ -350,9 +349,16 @@ declare module 'nitropack/types' {
export const clientConfigTemplate: NuxtTemplate = { export const clientConfigTemplate: NuxtTemplate = {
filename: 'nitro.client.mjs', filename: 'nitro.client.mjs',
getContents: () => ` getContents: ({ nuxt }) => {
export const useRuntimeConfig = () => window?.__NUXT__?.config || window?.useNuxtApp?.().payload?.config || {} const appId = JSON.stringify(nuxt.options.appId)
`, return [
'export const useRuntimeConfig = () => ',
(!nuxt.options.future.multiApp
? 'window?.__NUXT__?.config || window?.useNuxtApp?.().payload?.config'
: `window?.__NUXT__?.[${appId}]?.config || window?.useNuxtApp?.(${appId}).payload?.config`)
|| {},
].join('\n')
},
} }
export const appConfigDeclarationTemplate: NuxtTemplate = { export const appConfigDeclarationTemplate: NuxtTemplate = {
@ -497,6 +503,7 @@ export const nuxtConfigTemplate: NuxtTemplate = {
`export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`, `export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`,
`export const appId = ${JSON.stringify(ctx.nuxt.options.appId)}`, `export const appId = ${JSON.stringify(ctx.nuxt.options.appId)}`,
`export const outdatedBuildInterval = ${ctx.nuxt.options.experimental.checkOutdatedBuildInterval}`, `export const outdatedBuildInterval = ${ctx.nuxt.options.experimental.checkOutdatedBuildInterval}`,
`export const multiApp = ${!!ctx.nuxt.options.future.multiApp}`,
].join('\n\n') ].join('\n\n')
}, },
} }

View File

@ -41,9 +41,9 @@
"@types/sass-loader": "8.0.9", "@types/sass-loader": "8.0.9",
"@unhead/schema": "1.9.16", "@unhead/schema": "1.9.16",
"@vitejs/plugin-vue": "5.1.2", "@vitejs/plugin-vue": "5.1.2",
"@vitejs/plugin-vue-jsx": "4.0.0", "@vitejs/plugin-vue-jsx": "4.0.1",
"@vue/compiler-core": "3.4.37", "@vue/compiler-core": "3.4.38",
"@vue/compiler-sfc": "3.4.37", "@vue/compiler-sfc": "3.4.38",
"@vue/language-core": "2.0.29", "@vue/language-core": "2.0.29",
"c12": "2.0.0-beta.1", "c12": "2.0.0-beta.1",
"esbuild-loader": "4.2.2", "esbuild-loader": "4.2.2",
@ -54,13 +54,13 @@
"unbuild": "3.0.0-rc.7", "unbuild": "3.0.0-rc.7",
"unctx": "2.3.1", "unctx": "2.3.1",
"unenv": "1.10.0", "unenv": "1.10.0",
"vite": "5.4.0", "vite": "5.4.1",
"vue": "3.4.37", "vue": "3.4.38",
"vue-bundle-renderer": "2.1.0", "vue-bundle-renderer": "2.1.0",
"vue-loader": "17.4.2", "vue-loader": "17.4.2",
"vue-router": "4.4.3", "vue-router": "4.4.3",
"webpack": "5.93.0", "webpack": "5.93.0",
"webpack-dev-middleware": "7.3.0" "webpack-dev-middleware": "7.4.0"
}, },
"dependencies": { "dependencies": {
"compatx": "^0.1.8", "compatx": "^0.1.8",

View File

@ -175,7 +175,29 @@ export default defineUntypedSchema({
* ``` * ```
*/ */
buildDir: { buildDir: {
$resolve: async (val: string | undefined, get): Promise<string> => resolve(await get('rootDir') as string, val || '.nuxt'), $resolve: async (val: string | undefined, get): Promise<string> => {
const rootDir = await get('rootDir') as string
if (val) {
return resolve(rootDir, val)
}
const defaultBuildDir = resolve(rootDir, '.nuxt')
const isDev = await get('dev') as boolean
if (isDev) {
return defaultBuildDir
}
// TODO: nuxi CLI should ensure .nuxt dir exists
if (!existsSync(defaultBuildDir)) {
// This is to ensure that types continue to work for CI builds
return defaultBuildDir
}
// TODO: handle build caching + using buildId in directory
return resolve(rootDir, 'node_modules/.cache/nuxt/builds', 'production')
},
}, },
/** /**

View File

@ -353,6 +353,10 @@ export default defineUntypedSchema({
/** @type {typeof import('#app/components/nuxt-link')['NuxtLinkOptions']} */ /** @type {typeof import('#app/components/nuxt-link')['NuxtLinkOptions']} */
nuxtLink: { nuxtLink: {
componentName: 'NuxtLink', componentName: 'NuxtLink',
prefetch: true,
prefetchOn: {
visibility: true,
},
}, },
/** /**
* Options that apply to `useAsyncData` (and also therefore `useFetch`) * Options that apply to `useAsyncData` (and also therefore `useFetch`)

View File

@ -45,7 +45,6 @@ export default defineUntypedSchema({
'vue', 'vue',
'@vue/runtime-core', '@vue/runtime-core',
'@vue/compiler-sfc', '@vue/compiler-sfc',
'@vue/runtime-dom',
'vue-router', 'vue-router',
'vue-router/auto-routes', 'vue-router/auto-routes',
'unplugin-vue-router/client', 'unplugin-vue-router/client',

View File

@ -19,9 +19,9 @@
}, },
"devDependencies": { "devDependencies": {
"@types/html-minifier": "4.0.5", "@types/html-minifier": "4.0.5",
"@unocss/reset": "0.62.1", "@unocss/reset": "0.62.2",
"critters": "0.0.24", "critters": "0.0.24",
"execa": "9.3.0", "execa": "9.3.1",
"globby": "14.0.2", "globby": "14.0.2",
"html-minifier": "4.0.0", "html-minifier": "4.0.0",
"html-validate": "8.21.0", "html-validate": "8.21.0",
@ -30,7 +30,7 @@
"pathe": "1.1.2", "pathe": "1.1.2",
"prettier": "3.3.3", "prettier": "3.3.3",
"scule": "1.3.0", "scule": "1.3.0",
"unocss": "0.62.1", "unocss": "0.62.2",
"vite": "5.4.0" "vite": "5.4.1"
} }
} }

View File

@ -27,21 +27,21 @@
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@types/clear": "0.1.4", "@types/clear": "0.1.4",
"@types/estree": "1.0.5", "@types/estree": "1.0.5",
"rollup": "4.20.0", "rollup": "4.21.0",
"unbuild": "3.0.0-rc.7", "unbuild": "3.0.0-rc.7",
"vue": "3.4.37" "vue": "3.4.38"
}, },
"dependencies": { "dependencies": {
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-replace": "^5.0.7",
"@vitejs/plugin-vue": "^5.1.2", "@vitejs/plugin-vue": "^5.1.2",
"@vitejs/plugin-vue-jsx": "^4.0.0", "@vitejs/plugin-vue-jsx": "^4.0.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"clear": "^0.1.0", "clear": "^0.1.0",
"consola": "^3.2.3", "consola": "^3.2.3",
"cssnano": "^7.0.5", "cssnano": "^7.0.5",
"defu": "^6.1.4", "defu": "^6.1.4",
"esbuild": "^0.23.0", "esbuild": "^0.23.1",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"externality": "^1.0.2", "externality": "^1.0.2",
@ -61,8 +61,8 @@
"strip-literal": "^2.1.0", "strip-literal": "^2.1.0",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unplugin": "^1.12.1", "unplugin": "^1.12.2",
"vite": "^5.4.0", "vite": "^5.4.1",
"vite-node": "^2.0.5", "vite-node": "^2.0.5",
"vite-plugin-checker": "^0.7.2", "vite-plugin-checker": "^0.7.2",
"vue-bundle-renderer": "^2.1.0" "vue-bundle-renderer": "^2.1.0"

View File

@ -45,7 +45,7 @@
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",
"memfs": "^4.11.1", "memfs": "^4.11.1",
"mini-css-extract-plugin": "^2.9.0", "mini-css-extract-plugin": "^2.9.1",
"mlly": "^1.7.1", "mlly": "^1.7.1",
"ohash": "^1.1.3", "ohash": "^1.1.3",
"pathe": "^1.1.2", "pathe": "^1.1.2",
@ -60,13 +60,13 @@
"time-fix-plugin": "^2.0.7", "time-fix-plugin": "^2.0.7",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unplugin": "^1.12.1", "unplugin": "^1.12.2",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-bundle-renderer": "^2.1.0", "vue-bundle-renderer": "^2.1.0",
"vue-loader": "^17.4.2", "vue-loader": "^17.4.2",
"webpack": "^5.93.0", "webpack": "^5.93.0",
"webpack-bundle-analyzer": "^4.10.2", "webpack-bundle-analyzer": "^4.10.2",
"webpack-dev-middleware": "^7.3.0", "webpack-dev-middleware": "^7.4.0",
"webpack-hot-middleware": "^2.26.1", "webpack-hot-middleware": "^2.26.1",
"webpack-virtual-modules": "^0.6.2", "webpack-virtual-modules": "^0.6.2",
"webpackbar": "^6.0.1" "webpackbar": "^6.0.1"
@ -78,9 +78,9 @@
"@types/pify": "5.0.4", "@types/pify": "5.0.4",
"@types/webpack-bundle-analyzer": "4.7.0", "@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9", "@types/webpack-hot-middleware": "2.25.9",
"rollup": "4.20.0", "rollup": "4.21.0",
"unbuild": "3.0.0-rc.7", "unbuild": "3.0.0-rc.7",
"vue": "3.4.37" "vue": "3.4.38"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "^3.3.4" "vue": "^3.3.4"

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@ export default defineNuxtConfig({
future: { future: {
typescriptBundlerResolution: process.env.MODULE_RESOLUTION === 'bundler', typescriptBundlerResolution: process.env.MODULE_RESOLUTION === 'bundler',
}, },
buildDir: process.env.NITRO_BUILD_DIR,
builder: process.env.TEST_BUILDER as 'webpack' | 'vite' ?? 'vite', builder: process.env.TEST_BUILDER as 'webpack' | 'vite' ?? 'vite',
theme: './extends/bar', theme: './extends/bar',
extends: [ extends: [

View File

@ -31,7 +31,6 @@ export default defineNuxtConfig({
include: ['keepalive-in-config', 'not-keepalive-in-nuxtpage'], include: ['keepalive-in-config', 'not-keepalive-in-nuxtpage'],
}, },
}, },
buildDir: process.env.NITRO_BUILD_DIR,
builder: process.env.TEST_BUILDER as 'webpack' | 'vite' ?? 'vite', builder: process.env.TEST_BUILDER as 'webpack' | 'vite' ?? 'vite',
appId: 'nuxt-app-basic', appId: 'nuxt-app-basic',
build: { build: {
@ -68,7 +67,6 @@ export default defineNuxtConfig({
'/hydration/spa-redirection/**': { ssr: false }, '/hydration/spa-redirection/**': { ssr: false },
'/no-scripts': { experimentalNoScripts: true }, '/no-scripts': { experimentalNoScripts: true },
}, },
output: { dir: process.env.NITRO_OUTPUT_DIR },
prerender: { prerender: {
routes: [ routes: [
'/random/a', '/random/a',

View File

@ -23,8 +23,6 @@ if (process.env.TEST_ENV !== 'built' && !isWindows) {
setupTimeout: (isWindows ? 360 : 120) * 1000, setupTimeout: (isWindows ? 360 : 120) * 1000,
nuxtConfig: { nuxtConfig: {
builder: isWebpack ? 'webpack' : 'vite', builder: isWebpack ? 'webpack' : 'vite',
buildDir: process.env.NITRO_BUILD_DIR,
nitro: { output: { dir: process.env.NITRO_OUTPUT_DIR } },
}, },
}) })

View File

@ -0,0 +1,95 @@
/// <reference path="../fixtures/basic/.nuxt/nuxt.d.ts" />
import { describe, expect, it, vi } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { nuxtLinkDefaults } from '#build/nuxt.config.mjs'
describe('nuxt-link:prefetch', () => {
it('should prefetch on visibility by default', async () => {
const component = defineNuxtLink(nuxtLinkDefaults)
const { observer } = useMockObserver()
const nuxtApp = useNuxtApp()
nuxtApp.hooks.callHook = vi.fn(() => Promise.resolve())
await mountSuspended(component, { props: { to: '/to' } })
expect(nuxtApp.hooks.callHook).not.toHaveBeenCalled()
await observer.trigger()
expect(nuxtApp.hooks.callHook).toHaveBeenCalledTimes(1)
await observer.trigger()
expect(nuxtApp.hooks.callHook).toHaveBeenCalledTimes(1)
})
it('should prefetch with custom string `prefetchOn`', async () => {
const component = defineNuxtLink(nuxtLinkDefaults)
const nuxtApp = useNuxtApp()
nuxtApp.hooks.callHook = vi.fn(() => Promise.resolve())
const { observer } = useMockObserver()
const wrapper = await mountSuspended(component, { props: { to: '/to', prefetchOn: 'interaction' } })
await observer.trigger()
expect(nuxtApp.hooks.callHook).not.toHaveBeenCalled()
await wrapper.find('a').trigger('focus')
expect(nuxtApp.hooks.callHook).toHaveBeenCalledTimes(1)
await wrapper.find('a').trigger('focus')
expect(nuxtApp.hooks.callHook).toHaveBeenCalledTimes(1)
await wrapper.find('a').trigger('pointerenter')
expect(nuxtApp.hooks.callHook).toHaveBeenCalledTimes(1)
})
it('should prefetch with custom object `prefetchOn`', async () => {
const component = defineNuxtLink(nuxtLinkDefaults)
const nuxtApp = useNuxtApp()
nuxtApp.hooks.callHook = vi.fn(() => Promise.resolve())
const { observer } = useMockObserver()
await mountSuspended(component, { props: { to: '/to', prefetchOn: { interaction: true } } })
await observer.trigger()
expect(nuxtApp.hooks.callHook).toHaveBeenCalled()
})
it('should prefetch with custom object `prefetchOn` overriding default', async () => {
const component = defineNuxtLink(nuxtLinkDefaults)
const nuxtApp = useNuxtApp()
nuxtApp.hooks.callHook = vi.fn(() => Promise.resolve())
const { observer } = useMockObserver()
await mountSuspended(component, { props: { to: '/to', prefetchOn: { interaction: true, visibility: false } } })
await observer.trigger()
expect(nuxtApp.hooks.callHook).not.toHaveBeenCalled()
})
})
function useMockObserver () {
let callback: (entries: Array<{ target: HTMLElement, isIntersecting: boolean }>) => unknown
let el: HTMLElement
const mockObserver = class IntersectionObserver {
el: HTMLElement
constructor (_callback?: (entries: Array<{ target: HTMLElement, isIntersecting: boolean }>) => unknown) {
callback ||= _callback
}
observe = (_el: HTMLElement) => { el = _el }
trigger = () => callback?.([{ target: el, isIntersecting: true }])
unobserve = () => {}
disconnect = () => {}
}
window.IntersectionObserver = mockObserver as any
const observer = new mockObserver()
return { observer }
}

View File

@ -14,8 +14,6 @@ await setup({
setupTimeout: (isWindows ? 360 : 120) * 1000, setupTimeout: (isWindows ? 360 : 120) * 1000,
nuxtConfig: { nuxtConfig: {
builder: isWebpack ? 'webpack' : 'vite', builder: isWebpack ? 'webpack' : 'vite',
buildDir: process.env.NITRO_BUILD_DIR,
nitro: { output: { dir: process.env.NITRO_OUTPUT_DIR } },
}, },
}) })

View File

@ -114,7 +114,9 @@ export function parseData (html: string) {
attrs: {}, attrs: {},
} }
} }
const { script, attrs = '' } = html.match(/<script type="application\/json" id="__NUXT_DATA__"(?<attrs>[^>]+)>(?<script>.*?)<\/script>/)?.groups || {}
const regexp = /<script type="application\/json" data-nuxt-data="[^"]+"(?<attrs>[^>]+)>(?<script>.*?)<\/script>/
const { script, attrs = '' } = html.match(regexp)?.groups || {}
const _attrs: Record<string, string> = {} const _attrs: Record<string, string> = {}
for (const attr of attrs.matchAll(/( |^)(?<key>[\w-]+)="(?<value>[^"]+)"/g)) { for (const attr of attrs.matchAll(/( |^)(?<key>[\w-]+)="(?<value>[^"]+)"/g)) {
_attrs[attr!.groups!.key!] = attr!.groups!.value! _attrs[attr!.groups!.key!] = attr!.groups!.value!