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
- name: Initialize CodeQL
uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0
uses: github/codeql-action/init@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3
with:
languages: javascript
queries: +security-and-quality
@ -97,7 +97,7 @@ jobs:
path: packages
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0
uses: github/codeql-action/analyze@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3
with:
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.
- 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()
with:
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
- `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
```bash [yarn]
yarn add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
::package-managers
```bash [npm]
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]
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
::code-group
```bash [yarn]
yarn add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
```
::package-managers
```bash [npm]
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]
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
::code-group
```bash [yarn]
yarn test
```
::package-managers
```bash [npm]
npm run test
```
```bash [yarn]
yarn test
```
```bash [pnpm]
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).
::code-group
```bash [yarn]
yarn add --dev @playwright/test @nuxt/test-utils
```
::package-managers
```bash [npm]
npm i --save-dev @playwright/test @nuxt/test-utils
```
```bash [yarn]
yarn add --dev @playwright/test @nuxt/test-utils
```
```bash [pnpm]
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.
::code-group
::package-managers
```bash [npm]
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:
::code-group
::package-managers
```bash [npm]
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:
::code-group
```bash [yarn]
yarn dev --open
```
::package-managers
```bash [npm]
npm run dev -- -o
```
```bash [yarn]
yarn dev --open
```
```bash [pnpm]
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.
::code-group
::package-managers
```bash [npm]
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.
::code-group
::package-managers
```bash [npm]
npx nuxi generate

View File

@ -203,7 +203,7 @@ You can explore open source examples deployed on some of the platform mentioned
target: _blank
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:
::code-group
```bash [yarn]
yarn add --dev vue-tsc typescript
```
::package-managers
```bash [npm]
npm install --save-dev vue-tsc typescript
```
```bash [yarn]
yarn add --dev vue-tsc typescript
```
```bash [pnpm]
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:
::code-group
```bash [yarn]
yarn add --dev vue-gtag-next
```
::package-managers
```bash [npm]
npm install --save-dev vue-gtag-next
```
```bash [yarn]
yarn add --dev vue-gtag-next
```
```bash [pnpm]
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):
::code-group
::package-managers
```bash [npm]
npx nuxi init -t module my-module
@ -228,9 +228,9 @@ Learn more about asset injection in [the recipes section](#recipes).
::warning
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.
:br :br
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
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).
::

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`:
::code-group
::package-managers
```bash [npm]
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
- `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.
- `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.
- `prefetchedClass`: A class to apply to links that have been prefetched.
@ -185,8 +186,13 @@ interface NuxtLinkOptions {
externalRelAttribute?: string;
activeClass?: string;
exactActiveClass?: string;
prefetchedClass?: string;
trailingSlash?: 'append' | 'remove'
prefetch?: boolean
prefetchedClass?: string
prefetchOn?: Partial<{
visibility: boolean
interaction: boolean
}>
}
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
- `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"`)
- `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.
- `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"}

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.
- `pick`: only pick specified keys in this array from the `handler` function result
- `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:
- `cancel` - cancels existing requests when a new one is made
- `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.
- `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`.)
- `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:
- `cancel` - cancels existing requests when a new one is made
- `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:
- label: Source
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
---

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:
::code-group
```bash [yarn]
yarn install
```
::package-managers
```bash [npm]
npm install
```
```bash [yarn]
yarn install
```
::
::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:
::code-group
```bash [Yarn]
yarn add --dev @nuxt/bridge nuxi
```
::package-managers
```bash [npm]
npm install -D @nuxt/bridge nuxi
```
```bash [yarn]
yarn add --dev @nuxt/bridge nuxi
```
::
### 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:
::code-group
```bash [yarn]
yarn add --dev nuxi
```
::package-managers
```bash [npm]
npm install -D nuxi
```
```bash [yarn]
yarn add --dev nuxi
```
::
### Nuxi

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import type { ComponentInternalInstance, ComponentOptions, InjectionKey } from '
import { isPromise } from '@vue/shared'
import { useNuxtApp } from '../nuxt'
import { getFragmentHTML } from './utils'
import ServerPlaceholder from './server-placeholder'
export const clientOnlySymbol: InjectionKey<boolean> = Symbol.for('nuxt:client-only')
@ -36,6 +37,9 @@ const cache = new WeakMap()
/* @__NO_SIDE_EFFECTS__ */
export function createClientOnly<T extends ComponentOptions> (component: T) {
if (import.meta.server) {
return ServerPlaceholder
}
if (cache.has(component)) {
return cache.get(component)
}
@ -45,15 +49,14 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
if (clone.render) {
// 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) => {
// import.meta.client for server-side treeshakking
if (import.meta.client && ($setup.mounted$ ?? ctx.mounted$)) {
if ($setup.mounted$ ?? ctx.mounted$) {
const res = component.render?.bind(ctx)(ctx, cache, $props, $setup, $data, $options)
return (res.children === null || typeof res.children === 'string')
? cloneVNode(res)
: h(res)
} else {
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) {
@ -66,10 +69,10 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
clone.setup = (props, ctx) => {
const nuxtApp = useNuxtApp()
const mounted$ = ref(import.meta.client && nuxtApp.isHydrating === false)
const mounted$ = ref(nuxtApp.isHydrating === false)
const instance = getCurrentInstance()!
if (import.meta.server || nuxtApp.isHydrating) {
if (nuxtApp.isHydrating) {
const attrs = { ...instance.attrs }
// remove existing directives during hydration
const directives = extractDirectives(instance)
@ -97,14 +100,14 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
return setupState
}
return (...args: any[]) => {
if (import.meta.client && (mounted$.value || !nuxtApp.isHydrating)) {
if (mounted$.value || !nuxtApp.isHydrating) {
const res = setupState(...args)
return (res.children === null || typeof res.children === 'string')
? cloneVNode(res)
: h(res)
} else {
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)
}
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)
}
}
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.
*/
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.
*/
@ -71,7 +78,7 @@ export interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
*/
export interface NuxtLinkOptions extends
Partial<Pick<RouterLinkProps, 'activeClass' | 'exactActiveClass'>>,
Partial<Pick<NuxtLinkProps, 'prefetchedClass'>> {
Partial<Pick<NuxtLinkProps, 'prefetch' | 'prefetchedClass'>> {
/**
* The name of the component.
* @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.
*/
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__ */
@ -239,6 +251,14 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
default: undefined,
required: false,
},
prefetchOn: {
type: [String, Object] as PropType<NuxtLinkProps['prefetchOn']>,
default: options.prefetchOn || {
visibility: true,
interaction: false,
} satisfies NuxtLinkProps['prefetchOn'],
required: false,
},
noPrefetch: {
type: Boolean as PropType<NuxtLinkProps['noPrefetch']>,
default: undefined,
@ -299,10 +319,27 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
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 }
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) {
checkPropConflicts(props, 'prefetch', 'noPrefetch')
const shouldPrefetch = props.prefetch !== false && props.noPrefetch !== true && props.target !== '_blank' && !isSlowConnection()
if (shouldPrefetch) {
if (shouldPrefetch('visibility')) {
const nuxtApp = useNuxtApp()
let idleId: number
let unobserve: (() => void) | null = null
@ -314,15 +351,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
unobserve = observer!.observe(el.value as HTMLElement, async () => {
unobserve?.()
unobserve = null
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
await prefetch(nuxtApp)
})
}
})
@ -355,6 +384,8 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
replace: props.replace,
ariaCurrentValue: props.ariaCurrentValue,
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

View File

@ -84,7 +84,7 @@ export interface AsyncDataOptions<
*/
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
/**
@ -350,7 +350,7 @@ export function useAsyncData<
if (import.meta.client) {
// Setup hook callbacks once per instance
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
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'
// @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 {
fresh?: boolean
@ -107,7 +107,7 @@ export async function getNuxtClientPayload () {
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) {
return {} as Partial<NuxtPayload>
}
@ -119,7 +119,7 @@ export async function getNuxtClientPayload () {
payloadCache = {
...inlineData,
...externalData,
...window.__NUXT__,
...(multiApp ? window.__NUXT__?.[appId] : window.__NUXT__),
}
if (payloadCache!.config?.public) {

View File

@ -15,7 +15,7 @@ import plugins from '#build/plugins'
// @ts-expect-error virtual file
import RootComponent from '#build/root-component.mjs'
// @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>>
@ -50,9 +50,10 @@ if (import.meta.client) {
entry = async function initApp () {
if (vueAppPromise) { return vueAppPromise }
const isSSR = Boolean(
window.__NUXT__?.serverRendered ||
document.getElementById('__NUXT_DATA__')?.dataset.ssr === 'true',
(multiApp ? window.__NUXT__?.[appId] : window.__NUXT__)?.serverRendered ??
(multiApp ? document.querySelector(`[data-nuxt-data="${appId}"]`) as HTMLElement : document.getElementById('__NUXT_DATA__'))?.dataset.ssr === 'true',
)
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'
// @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'
@ -310,19 +310,22 @@ export function createNuxtApp (options: CreateOptions) {
nuxtApp.payload.serverRendered = true
}
if (import.meta.client) {
const __NUXT__ = multiApp ? window.__NUXT__?.[nuxtApp._id] : window.__NUXT__
// TODO: remove/refactor in https://github.com/nuxt/nuxt/issues/25336
if (import.meta.client && window.__NUXT__) {
for (const key in window.__NUXT__) {
if (__NUXT__) {
for (const key in __NUXT__) {
switch (key) {
case 'data':
case 'state':
case '_errors':
// Preserve reactivity for non-rich payload support
Object.assign(nuxtApp.payload[key], window.__NUXT__[key])
Object.assign(nuxtApp.payload[key], __NUXT__[key])
break
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') {
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[] : []
await nuxtApp.hooks.callHook('dev:ssr-logs', logs)
}

View File

@ -26,7 +26,7 @@ declare global {
}
interface Window {
__NUXT__?: Record<string, any>
__NUXT__?: Record<string, any> | Record<string, Record<string, any>>
useNuxtApp?: typeof useNuxtApp
}
}
@ -51,23 +51,3 @@ declare module 'vue' {
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')}
}
declare module '@vue/runtime-core' {
export interface GlobalComponents extends _GlobalComponents { }
}
declare module '@vue/runtime-dom' {
export interface GlobalComponents extends _GlobalComponents { }
}
declare module 'vue' {
export interface GlobalComponents extends _GlobalComponents { }
}

View File

@ -13,6 +13,8 @@ import type { NitroApp } from 'nitro/types'
// @ts-expect-error virtual file
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> = {
VNode: data => isVNode(data) ? { type: data.type, props: data.props } : undefined,
@ -75,7 +77,7 @@ export default (nitroApp: NitroApp) => {
const ctx = asyncContext.tryUse()
if (!ctx) { return }
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) {
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.`)

View File

@ -31,7 +31,7 @@ import { renderSSRHeadOptions } from '#internal/unhead.config.mjs'
import type { NuxtPayload, NuxtSSRContext } from '#app'
// @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
import { buildAssetsURL, publicAssetsURL } from '#internal/nuxt/paths'
@ -427,10 +427,10 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
head.push({
script: _PAYLOAD_EXTRACTION
? 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 })
: process.env.NUXT_JSON_PAYLOADS
? renderPayloadJsonScript({ id: '__NUXT_DATA__', ssrContext, data: ssrContext.payload })
? renderPayloadJsonScript({ ssrContext, data: ssrContext.payload })
: renderPayloadScript({ ssrContext, data: ssrContext.payload }),
}, {
...headEntryOptions,
@ -586,21 +586,27 @@ function renderPayloadResponse (ssrContext: NuxtSSRContext) {
} 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 payload: Script = {
'type': 'application/json',
'id': opts.id,
'innerHTML': contents,
'data-nuxt-data': appId,
'data-ssr': !(process.env.NUXT_NO_SSR || opts.ssrContext.noSSR),
}
if (!multiApp) {
payload.id = '__NUXT_DATA__'
}
if (opts.src) {
payload['data-src'] = opts.src
}
const config = uneval(opts.ssrContext.config)
return [
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[] {
opts.data.config = opts.ssrContext.config
const _PAYLOAD_EXTRACTION = import.meta.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !opts.ssrContext.noSSR
const nuxtData = devalue(opts.data)
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 [
{
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 [
{
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 = {
filename: 'types/plugins.d.ts',
getContents: async ({ nuxt, app }) => {
@ -124,9 +126,14 @@ export const pluginsDeclaration: NuxtTemplate = {
}
if (exists(pluginPath)) {
if (TS_RE.test(pluginPath)) {
tsImports.push(relativePath.replace(EXTENSION_RE, ''))
continue
}
tsImports.push(relativePath)
}
// No declaration found that TypeScript can use
}
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' {
interface ComponentCustomProperties extends NuxtAppInjections { }
}
@ -350,9 +349,16 @@ declare module 'nitropack/types' {
export const clientConfigTemplate: NuxtTemplate = {
filename: 'nitro.client.mjs',
getContents: () => `
export const useRuntimeConfig = () => window?.__NUXT__?.config || window?.useNuxtApp?.().payload?.config || {}
`,
getContents: ({ nuxt }) => {
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 = {
@ -497,6 +503,7 @@ export const nuxtConfigTemplate: NuxtTemplate = {
`export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`,
`export const appId = ${JSON.stringify(ctx.nuxt.options.appId)}`,
`export const outdatedBuildInterval = ${ctx.nuxt.options.experimental.checkOutdatedBuildInterval}`,
`export const multiApp = ${!!ctx.nuxt.options.future.multiApp}`,
].join('\n\n')
},
}

View File

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

View File

@ -175,7 +175,29 @@ export default defineUntypedSchema({
* ```
*/
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']} */
nuxtLink: {
componentName: 'NuxtLink',
prefetch: true,
prefetchOn: {
visibility: true,
},
},
/**
* Options that apply to `useAsyncData` (and also therefore `useFetch`)

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -23,8 +23,6 @@ if (process.env.TEST_ENV !== 'built' && !isWindows) {
setupTimeout: (isWindows ? 360 : 120) * 1000,
nuxtConfig: {
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,
nuxtConfig: {
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: {},
}
}
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> = {}
for (const attr of attrs.matchAll(/( |^)(?<key>[\w-]+)="(?<value>[^"]+)"/g)) {
_attrs[attr!.groups!.key!] = attr!.groups!.value!