mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
Merge branch 'main' into main
This commit is contained in:
commit
b30c465a57
2
.github/workflows/check-links.yml
vendored
2
.github/workflows/check-links.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
|
|
||||||
- name: Lychee link checker
|
- name: Lychee link checker
|
||||||
uses: lycheeverse/lychee-action@054a8e8c7a88ada133165c6633a49825a32174e2 # for v1.8.0
|
uses: lycheeverse/lychee-action@25a231001d1723960a301b7d4c82884dc7ef857d # for v1.8.0
|
||||||
with:
|
with:
|
||||||
# arguments with file types to check
|
# arguments with file types to check
|
||||||
args: >-
|
args: >-
|
||||||
|
2
.github/workflows/release-pr.yml
vendored
2
.github/workflows/release-pr.yml
vendored
@ -31,7 +31,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
with:
|
with:
|
||||||
ref: refs/pull/${{ github.event.issue.number }}/merge
|
ref: ${{ github.event.issue.pull_request.head.sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
@ -373,7 +373,7 @@ registerEndpoint('/test/', {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**: If your requests in a component go to external API, you can use `baseURL` and then make it empty using Nuxt Environment Config (`$test`) so all your requests will go to Nitro server.
|
> **Note**: If your requests in a component go to an external API, you can use `baseURL` and then make it empty using [Nuxt Environment Override Config](/docs/getting-started/configuration#environment-overrides) (`$test`) so all your requests will go to Nitro server.
|
||||||
|
|
||||||
#### Conflict with End-To-End Testing
|
#### Conflict with End-To-End Testing
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ export default defineNuxtConfig({
|
|||||||
// experimental: {
|
// experimental: {
|
||||||
// sharedPrerenderData: false,
|
// sharedPrerenderData: false,
|
||||||
// compileTemplate: true,
|
// compileTemplate: true,
|
||||||
|
// resetAsyncDataToUndefined: true,
|
||||||
// templateUtils: true,
|
// templateUtils: true,
|
||||||
// relativeWatchPaths: true,
|
// relativeWatchPaths: true,
|
||||||
// defaults: {
|
// defaults: {
|
||||||
@ -189,6 +190,63 @@ export default defineNuxtConfig({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Default `data` and `error` values in `useAsyncData` and `useFetch`
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Minimal
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
`data` and `error` objects returned from `useAsyncData` will now default to `undefined`.
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
Previously `data` was initialized to `null` but reset in `clearNuxtData` to `undefined`. `error` was initialized to `null`. This change is to bring greater consistency.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
If you encounter any issues you can revert back to the previous behavior with:
|
||||||
|
|
||||||
|
```ts twoslash [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
defaults: {
|
||||||
|
useAsyncData: {
|
||||||
|
value: 'null',
|
||||||
|
errorValue: 'null'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Please report an issue if you are doing this, as we do not plan to keep this as configurable.
|
||||||
|
|
||||||
|
#### Respect defaults when clearing `data` in `useAsyncData` and `useFetch`
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Minimal
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
If you provide a custom `default` value for `useAsyncData`, this will now be used when calling `clear` or `clearNuxtData` and it will be reset to its default value rather than simply unset.
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
Often users set an appropriately empty value, such as an empty array, to avoid the need to check for `null`/`undefined` when iterating over it. This should be respected when resetting/clearing the data.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
If you encounter any issues you can revert back to the previous behavior, for now, with:
|
||||||
|
|
||||||
|
```ts twoslash [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
resetAsyncDataToUndefined: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Please report an issue if you are doing so, as we do not plan to keep this as configurable.
|
||||||
|
|
||||||
#### Shallow Data Reactivity in `useAsyncData` and `useFetch`
|
#### Shallow Data Reactivity in `useAsyncData` and `useFetch`
|
||||||
|
|
||||||
🚦 **Impact Level**: Minimal
|
🚦 **Impact Level**: Minimal
|
||||||
|
@ -259,7 +259,7 @@ Watch Learn Vue video about Nuxt Server Components.
|
|||||||
::
|
::
|
||||||
|
|
||||||
::tip{icon="i-ph-article-duotone" to="https://roe.dev/blog/nuxt-server-components" target="_blank"}
|
::tip{icon="i-ph-article-duotone" to="https://roe.dev/blog/nuxt-server-components" target="_blank"}
|
||||||
Read Daniel Roe's guide to Nuxt server components
|
Read Daniel Roe's guide to Nuxt Server Components.
|
||||||
::
|
::
|
||||||
|
|
||||||
### Standalone server components
|
### Standalone server components
|
||||||
|
@ -59,13 +59,10 @@ Use these options to set the expiration of the cookie.
|
|||||||
The given number will be converted to an integer by rounding down. By default, no maximum age is set.
|
The given number will be converted to an integer by rounding down. By default, no maximum age is set.
|
||||||
|
|
||||||
`expires`: Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1).
|
`expires`: Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1).
|
||||||
By default, no expiration is set. Most clients will consider this a "non-persistent cookie" and
|
By default, no expiration is set. Most clients will consider this a "non-persistent cookie" and will delete it on a condition like exiting a web browser application.
|
||||||
will delete it on a condition like exiting a web browser application.
|
|
||||||
|
|
||||||
::note
|
::note
|
||||||
The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and
|
The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and `maxAge` is set, then `maxAge` takes precedence, but not all clients may obey this, so if both are set, they should point to the same date and time!
|
||||||
`maxAge` is set, then `maxAge` takes precedence, but not all clients may obey this,
|
|
||||||
so if both are set, they should point to the same date and time!
|
|
||||||
::
|
::
|
||||||
|
|
||||||
::note
|
::note
|
||||||
@ -74,22 +71,29 @@ If neither of `expires` and `maxAge` is set, the cookie will be session-only and
|
|||||||
|
|
||||||
### `httpOnly`
|
### `httpOnly`
|
||||||
|
|
||||||
Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6). When truthy,
|
Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6). When truthy, the `HttpOnly` attribute is set; otherwise it is not. By default, the `HttpOnly` attribute is not set.
|
||||||
the `HttpOnly` attribute is set; otherwise it is not. By default, the `HttpOnly` attribute is not set.
|
|
||||||
|
|
||||||
::warning
|
::warning
|
||||||
Be careful when setting this to `true`, as compliant clients will not allow client-side
|
Be careful when setting this to `true`, as compliant clients will not allow client-side JavaScript to see the cookie in `document.cookie`.
|
||||||
JavaScript to see the cookie in `document.cookie`.
|
|
||||||
::
|
::
|
||||||
|
|
||||||
### `secure`
|
### `secure`
|
||||||
|
|
||||||
Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5). When truthy,
|
Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5). When truthy, the `Secure` attribute is set; otherwise it is not. By default, the `Secure` attribute is not set.
|
||||||
the `Secure` attribute is set; otherwise it is not. By default, the `Secure` attribute is not set.
|
|
||||||
|
|
||||||
::warning
|
::warning
|
||||||
Be careful when setting this to `true`, as compliant clients will not send the cookie back to
|
Be careful when setting this to `true`, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection. This can lead to hydration errors.
|
||||||
the server in the future if the browser does not have an HTTPS connection. This can lead to hydration errors.
|
::
|
||||||
|
|
||||||
|
### `partitioned`
|
||||||
|
|
||||||
|
Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set.
|
||||||
|
|
||||||
|
::note
|
||||||
|
This is an attribute that has not yet been fully standardized, and may change in the future.
|
||||||
|
This also means many clients may ignore this attribute until they understand it.
|
||||||
|
|
||||||
|
More information can be found in the [proposal](https://github.com/privacycg/CHIPS).
|
||||||
::
|
::
|
||||||
|
|
||||||
### `domain`
|
### `domain`
|
||||||
@ -114,23 +118,18 @@ More information about the different enforcement levels can be found in [the spe
|
|||||||
|
|
||||||
### `encode`
|
### `encode`
|
||||||
|
|
||||||
Specifies a function that will be used to encode a cookie's value. Since the value of a cookie
|
Specifies a function that will be used to encode a cookie's value. Since the value of a cookie has a limited character set (and must be a simple string), this function can be used to encode a value into a string suited for a cookie's value.
|
||||||
has a limited character set (and must be a simple string), this function can be used to encode
|
|
||||||
a value into a string suited for a cookie's value.
|
|
||||||
|
|
||||||
The default encoder is the `JSON.stringify` + `encodeURIComponent`.
|
The default encoder is the `JSON.stringify` + `encodeURIComponent`.
|
||||||
|
|
||||||
### `decode`
|
### `decode`
|
||||||
|
|
||||||
Specifies a function that will be used to decode a cookie's value. Since the value of a cookie
|
Specifies a function that will be used to decode a cookie's value. Since the value of a cookie has a limited character set (and must be a simple string), this function can be used to decode a previously encoded cookie value into a JavaScript string or other object.
|
||||||
has a limited character set (and must be a simple string), this function can be used to decode
|
|
||||||
a previously encoded cookie value into a JavaScript string or other object.
|
|
||||||
|
|
||||||
The default decoder is `decodeURIComponent` + [destr](https://github.com/unjs/destr).
|
The default decoder is `decodeURIComponent` + [destr](https://github.com/unjs/destr).
|
||||||
|
|
||||||
::note
|
::note
|
||||||
If an error is thrown from this function, the original, non-decoded cookie value will
|
If an error is thrown from this function, the original, non-decoded cookie value will be returned as the cookie's value.
|
||||||
be returned as the cookie's value.
|
|
||||||
::
|
::
|
||||||
|
|
||||||
### `default`
|
### `default`
|
||||||
|
@ -32,6 +32,10 @@ We'll do our best to follow our [internal issue decision making flowchart](https
|
|||||||
|
|
||||||
### Send a Pull Request
|
### Send a Pull Request
|
||||||
|
|
||||||
|
::Tip
|
||||||
|
On windows, you need to clone the repository with `git clone -c core.symlinks=true https://github.com/nuxt/nuxt.git` to make symlinks work.
|
||||||
|
::
|
||||||
|
|
||||||
We always welcome pull requests! ❤️
|
We always welcome pull requests! ❤️
|
||||||
|
|
||||||
#### Before You Start
|
#### Before You Start
|
||||||
|
@ -64,9 +64,9 @@ Each active version has its own nightly releases which are generated automatical
|
|||||||
Release | | Initial release | End Of Life | Docs
|
Release | | Initial release | End Of Life | Docs
|
||||||
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
|
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
|
||||||
**4.x** (scheduled) | | 2024 Q2 | |
|
**4.x** (scheduled) | | 2024 Q2 | |
|
||||||
**3.x** (stable) | <a href="https://npmjs.com/package/nuxt"><img alt="Nuxt latest 3.x version" src="https://flat.badgen.net/npm/v/nuxt?label="></a> | 2022-11-16 | TBA | [nuxt.com](/docs)
|
**3.x** (stable) | <a href="https://npmjs.com/package/nuxt"><img alt="Nuxt latest 3.x version" src="https://flat.badgen.net/npm/v/nuxt?label=" class="not-prose"></a> | 2022-11-16 | TBA | [nuxt.com](/docs)
|
||||||
**2.x** (maintenance) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 2.x version" src="https://flat.badgen.net/npm/v/nuxt/2x?label="></a> | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
|
**2.x** (maintenance) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 2.x version" src="https://flat.badgen.net/npm/v/nuxt/2x?label=" class="not-prose"></a> | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
|
||||||
**1.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 1.x version" src="https://flat.badgen.net/npm/v/nuxt/1x?label="></a> | 2018-01-08 | 2019-09-21 |
|
**1.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 1.x version" src="https://flat.badgen.net/npm/v/nuxt/1x?label=" class="not-prose"></a> | 2018-01-08 | 2019-09-21 |
|
||||||
|
|
||||||
### Support Status
|
### Support Status
|
||||||
|
|
||||||
|
15
package.json
15
package.json
@ -20,6 +20,7 @@
|
|||||||
"lint:knip": "pnpx knip",
|
"lint:knip": "pnpx knip",
|
||||||
"play": "nuxi dev playground",
|
"play": "nuxi dev playground",
|
||||||
"play:build": "nuxi build playground",
|
"play:build": "nuxi build playground",
|
||||||
|
"play:generate": "nuxi generate playground",
|
||||||
"play:preview": "nuxi preview playground",
|
"play:preview": "nuxi preview playground",
|
||||||
"test": "pnpm test:fixtures && pnpm test:fixtures:dev && pnpm test:fixtures:webpack && pnpm test:unit && pnpm test:runtime && pnpm test:types && pnpm typecheck",
|
"test": "pnpm test:fixtures && pnpm test:fixtures:dev && pnpm test:fixtures:webpack && pnpm test:unit && pnpm test:runtime && pnpm test:types && pnpm typecheck",
|
||||||
"test:prepare": "jiti ./test/prepare.ts",
|
"test:prepare": "jiti ./test/prepare.ts",
|
||||||
@ -40,8 +41,8 @@
|
|||||||
"@nuxt/webpack-builder": "workspace:*",
|
"@nuxt/webpack-builder": "workspace:*",
|
||||||
"magic-string": "^0.30.10",
|
"magic-string": "^0.30.10",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"rollup": "^4.17.2",
|
"rollup": "^4.18.0",
|
||||||
"vite": "5.2.11",
|
"vite": "5.2.12",
|
||||||
"vue": "3.4.27"
|
"vue": "3.4.27"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -53,7 +54,7 @@
|
|||||||
"@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/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
"@types/node": "20.12.12",
|
"@types/node": "20.12.13",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@vitest/coverage-v8": "1.6.0",
|
"@vitest/coverage-v8": "1.6.0",
|
||||||
"@vue/test-utils": "2.4.6",
|
"@vue/test-utils": "2.4.6",
|
||||||
@ -69,16 +70,16 @@
|
|||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"globby": "14.0.1",
|
"globby": "14.0.1",
|
||||||
"h3": "1.11.1",
|
"h3": "1.11.1",
|
||||||
"happy-dom": "14.11.0",
|
"happy-dom": "14.12.0",
|
||||||
"jiti": "1.21.0",
|
"jiti": "1.21.0",
|
||||||
"markdownlint-cli": "0.40.0",
|
"markdownlint-cli": "0.41.0",
|
||||||
"nitropack": "2.9.6",
|
"nitropack": "2.9.6",
|
||||||
"nuxi": "3.11.1",
|
"nuxi": "3.11.1",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"nuxt-content-twoslash": "0.0.10",
|
"nuxt-content-twoslash": "0.0.10",
|
||||||
"ofetch": "1.3.4",
|
"ofetch": "1.3.4",
|
||||||
"pathe": "1.1.2",
|
"pathe": "1.1.2",
|
||||||
"playwright-core": "1.44.0",
|
"playwright-core": "1.44.1",
|
||||||
"rimraf": "5.0.7",
|
"rimraf": "5.0.7",
|
||||||
"semver": "7.6.2",
|
"semver": "7.6.2",
|
||||||
"std-env": "3.7.0",
|
"std-env": "3.7.0",
|
||||||
@ -90,7 +91,7 @@
|
|||||||
"vue-router": "4.3.2",
|
"vue-router": "4.3.2",
|
||||||
"vue-tsc": "2.0.19"
|
"vue-tsc": "2.0.19"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.1",
|
"packageManager": "pnpm@9.1.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.10.0 || >=18.0.0"
|
"node": "^16.10.0 || >=18.0.0"
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
"semver": "^7.6.2",
|
"semver": "^7.6.2",
|
||||||
"ufo": "^1.5.3",
|
"ufo": "^1.5.3",
|
||||||
"unctx": "^2.3.1",
|
"unctx": "^2.3.1",
|
||||||
"unimport": "^3.7.1",
|
"unimport": "^3.7.2",
|
||||||
"untyped": "^1.4.2"
|
"untyped": "^1.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -54,7 +54,7 @@
|
|||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"nitropack": "2.9.6",
|
"nitropack": "2.9.6",
|
||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"vite": "5.2.11",
|
"vite": "5.2.12",
|
||||||
"vitest": "1.6.0",
|
"vitest": "1.6.0",
|
||||||
"webpack": "5.91.0"
|
"webpack": "5.91.0"
|
||||||
},
|
},
|
||||||
|
@ -73,8 +73,8 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
|
|||||||
const key = `nuxt:module:${uniqueKey || (Math.round(Math.random() * 10000))}`
|
const key = `nuxt:module:${uniqueKey || (Math.round(Math.random() * 10000))}`
|
||||||
const mark = performance.mark(key)
|
const mark = performance.mark(key)
|
||||||
const res = await module.setup?.call(null as any, _options, nuxt) ?? {}
|
const res = await module.setup?.call(null as any, _options, nuxt) ?? {}
|
||||||
const perf = performance.measure(key, mark?.name) // TODO: remove when Node 14 reaches EOL
|
const perf = performance.measure(key, mark.name)
|
||||||
const setupTime = perf ? Math.round((perf.duration * 100)) / 100 : 0 // TODO: remove when Node 14 reaches EOL
|
const setupTime = Math.round((perf.duration * 100)) / 100
|
||||||
|
|
||||||
// Measure setup time
|
// Measure setup time
|
||||||
if (setupTime > 5000 && uniqueKey !== '@nuxt/telemetry') {
|
if (setupTime > 5000 && uniqueKey !== '@nuxt/telemetry') {
|
||||||
|
6
packages/nuxt/.gitignore
vendored
Normal file
6
packages/nuxt/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
src/app/components/error-404.vue
|
||||||
|
src/app/components/error-500.vue
|
||||||
|
src/app/components/error-dev.vue
|
||||||
|
src/app/components/welcome.vue
|
||||||
|
src/core/runtime/nitro/error-500.ts
|
||||||
|
src/core/runtime/nitro/error-dev.ts
|
@ -60,14 +60,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/devalue": "^2.0.2",
|
"@nuxt/devalue": "^2.0.2",
|
||||||
"@nuxt/devtools": "^1.3.1",
|
"@nuxt/devtools": "^1.3.2",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"@nuxt/telemetry": "^2.5.4",
|
"@nuxt/telemetry": "^2.5.4",
|
||||||
"@nuxt/vite-builder": "workspace:*",
|
"@nuxt/vite-builder": "workspace:*",
|
||||||
"@unhead/dom": "^1.9.10",
|
"@unhead/dom": "^1.9.11",
|
||||||
"@unhead/ssr": "^1.9.10",
|
"@unhead/ssr": "^1.9.11",
|
||||||
"@unhead/vue": "^1.9.10",
|
"@unhead/vue": "^1.9.11",
|
||||||
"@vue/shared": "^3.4.27",
|
"@vue/shared": "^3.4.27",
|
||||||
"acorn": "8.11.3",
|
"acorn": "8.11.3",
|
||||||
"c12": "^1.10.0",
|
"c12": "^1.10.0",
|
||||||
@ -76,7 +76,7 @@
|
|||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"destr": "^2.0.3",
|
"destr": "^2.0.3",
|
||||||
"devalue": "^5.0.0",
|
"devalue": "^5.0.0",
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.4",
|
||||||
"escape-string-regexp": "^5.0.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
"estree-walker": "^3.0.3",
|
"estree-walker": "^3.0.3",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
@ -107,7 +107,7 @@
|
|||||||
"uncrypto": "^0.1.3",
|
"uncrypto": "^0.1.3",
|
||||||
"unctx": "^2.3.1",
|
"unctx": "^2.3.1",
|
||||||
"unenv": "^1.9.0",
|
"unenv": "^1.9.0",
|
||||||
"unimport": "^3.7.1",
|
"unimport": "^3.7.2",
|
||||||
"unplugin": "^1.10.1",
|
"unplugin": "^1.10.1",
|
||||||
"unplugin-vue-router": "^0.7.0",
|
"unplugin-vue-router": "^0.7.0",
|
||||||
"unstorage": "^1.10.2",
|
"unstorage": "^1.10.2",
|
||||||
@ -118,13 +118,13 @@
|
|||||||
"vue-router": "^4.3.2"
|
"vue-router": "^4.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/ui-templates": "1.3.3",
|
"@nuxt/ui-templates": "1.3.4",
|
||||||
"@parcel/watcher": "2.4.1",
|
"@parcel/watcher": "2.4.1",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.5",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"vite": "5.2.11",
|
"vite": "5.2.12",
|
||||||
"vitest": "1.6.0"
|
"vitest": "1.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
../../../../ui-templates/dist/templates/error-404.vue
|
|
@ -1 +0,0 @@
|
|||||||
../../../../ui-templates/dist/templates/error-500.vue
|
|
@ -1 +0,0 @@
|
|||||||
../../../../ui-templates/dist/templates/error-dev.vue
|
|
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<Suspense @resolve="onResolve">
|
<Suspense @resolve="onResolve">
|
||||||
|
<div v-if="abortRender" />
|
||||||
<ErrorComponent
|
<ErrorComponent
|
||||||
v-if="error"
|
v-else-if="error"
|
||||||
:error="error"
|
:error="error"
|
||||||
/>
|
/>
|
||||||
<IslandRenderer
|
<IslandRenderer
|
||||||
@ -53,6 +54,8 @@ if (import.meta.dev && results && results.some(i => i && 'then' in i)) {
|
|||||||
|
|
||||||
// error handling
|
// error handling
|
||||||
const error = useError()
|
const error = useError()
|
||||||
|
// render an empty <div> when plugins have thrown an error but we're not yet rendering the error page
|
||||||
|
const abortRender = import.meta.server && error.value && !nuxtApp.ssrContext.error
|
||||||
onErrorCaptured((err, target, info) => {
|
onErrorCaptured((err, target, info) => {
|
||||||
nuxtApp.hooks.callHook('vue:error', err, target, info).catch(hookError => console.error('[nuxt] Error in `vue:error` hook', hookError))
|
nuxtApp.hooks.callHook('vue:error', err, target, info).catch(hookError => console.error('[nuxt] Error in `vue:error` hook', hookError))
|
||||||
if (import.meta.server || (isNuxtError(err) && (err.fatal || err.unhandled))) {
|
if (import.meta.server || (isNuxtError(err) && (err.fatal || err.unhandled))) {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
../../../../ui-templates/dist/templates/welcome.vue
|
|
@ -8,7 +8,10 @@ import { createError } from './error'
|
|||||||
import { onNuxtReady } from './ready'
|
import { onNuxtReady } from './ready'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { asyncDataDefaults } from '#build/nuxt.config.mjs'
|
import { asyncDataDefaults, resetAsyncDataToUndefined } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||||
|
|
||||||
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||||
|
|
||||||
@ -42,7 +45,7 @@ export interface AsyncDataOptions<
|
|||||||
ResT,
|
ResT,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> {
|
> {
|
||||||
/**
|
/**
|
||||||
* Whether to fetch on the server side.
|
* Whether to fetch on the server side.
|
||||||
@ -117,7 +120,7 @@ export interface _AsyncData<DataT, ErrorT> {
|
|||||||
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
clear: () => void
|
clear: () => void
|
||||||
error: Ref<ErrorT | null>
|
error: Ref<ErrorT | DefaultAsyncDataErrorValue>
|
||||||
status: Ref<AsyncDataRequestStatus>
|
status: Ref<AsyncDataRequestStatus>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,11 +141,11 @@ export function useAsyncData<
|
|||||||
NuxtErrorDataT = unknown,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
||||||
@ -158,7 +161,7 @@ export function useAsyncData<
|
|||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
||||||
@ -171,12 +174,12 @@ export function useAsyncData<
|
|||||||
NuxtErrorDataT = unknown,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
||||||
@ -194,14 +197,14 @@ export function useAsyncData<
|
|||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
export function useAsyncData<
|
export function useAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
NuxtErrorDataT = unknown,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys>, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null> {
|
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys>, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue> {
|
||||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||||
|
|
||||||
@ -226,14 +229,14 @@ export function useAsyncData<
|
|||||||
const value = nuxtApp.ssrContext!._sharedPrerenderCache!.get(key)
|
const value = nuxtApp.ssrContext!._sharedPrerenderCache!.get(key)
|
||||||
if (value) { return value as Promise<ResT> }
|
if (value) { return value as Promise<ResT> }
|
||||||
|
|
||||||
const promise = nuxtApp.runWithContext(_handler)
|
const promise = Promise.resolve().then(() => nuxtApp.runWithContext(_handler))
|
||||||
|
|
||||||
nuxtApp.ssrContext!._sharedPrerenderCache!.set(key, promise)
|
nuxtApp.ssrContext!._sharedPrerenderCache!.set(key, promise)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to get default values
|
// Used to get default values
|
||||||
const getDefault = () => null
|
const getDefault = () => asyncDataDefaults.value
|
||||||
const getDefaultCachedData = () => nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]
|
const getDefaultCachedData = () => nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]
|
||||||
|
|
||||||
// Apply defaults
|
// Apply defaults
|
||||||
@ -250,11 +253,12 @@ export function useAsyncData<
|
|||||||
console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.')
|
console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: make more precise when v4 lands
|
||||||
const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null
|
const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null
|
||||||
|
|
||||||
// Create or use a shared asyncData entity
|
// Create or use a shared asyncData entity
|
||||||
if (!nuxtApp._asyncData[key] || !options.immediate) {
|
if (!nuxtApp._asyncData[key] || !options.immediate) {
|
||||||
nuxtApp.payload._errors[key] ??= null
|
nuxtApp.payload._errors[key] ??= asyncDataDefaults.errorValue
|
||||||
|
|
||||||
const _ref = options.deep ? ref : shallowRef
|
const _ref = options.deep ? ref : shallowRef
|
||||||
|
|
||||||
@ -263,11 +267,15 @@ export function useAsyncData<
|
|||||||
pending: ref(!hasCachedData()),
|
pending: ref(!hasCachedData()),
|
||||||
error: toRef(nuxtApp.payload._errors, key),
|
error: toRef(nuxtApp.payload._errors, key),
|
||||||
status: ref('idle'),
|
status: ref('idle'),
|
||||||
|
_default: options.default!,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher
|
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher
|
||||||
const asyncData = { ...nuxtApp._asyncData[key] } as AsyncData<DataT | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)>
|
const asyncData = { ...nuxtApp._asyncData[key] } as { _default?: unknown } & AsyncData<DataT | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)>
|
||||||
|
|
||||||
|
// Don't expose default function to end user
|
||||||
|
delete asyncData._default
|
||||||
|
|
||||||
asyncData.refresh = asyncData.execute = (opts = {}) => {
|
asyncData.refresh = asyncData.execute = (opts = {}) => {
|
||||||
if (nuxtApp._asyncDataPromises[key]) {
|
if (nuxtApp._asyncDataPromises[key]) {
|
||||||
@ -307,7 +315,7 @@ export function useAsyncData<
|
|||||||
nuxtApp.payload.data[key] = result
|
nuxtApp.payload.data[key] = result
|
||||||
|
|
||||||
asyncData.data.value = result
|
asyncData.data.value = result
|
||||||
asyncData.error.value = null
|
asyncData.error.value = asyncDataDefaults.errorValue
|
||||||
asyncData.status.value = 'success'
|
asyncData.status.value = 'success'
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@ -404,11 +412,11 @@ export function useLazyAsyncData<
|
|||||||
DataE = Error,
|
DataE = Error,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
@ -418,18 +426,18 @@ export function useLazyAsyncData<
|
|||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
@ -440,15 +448,15 @@ export function useLazyAsyncData<
|
|||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
|
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null> {
|
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue> {
|
||||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||||
const [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise<ResT>, AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>]
|
const [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise<ResT>, AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>]
|
||||||
@ -463,12 +471,12 @@ export function useLazyAsyncData<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @since 3.1.0 */
|
/** @since 3.1.0 */
|
||||||
export function useNuxtData<DataT = any> (key: string): { data: Ref<DataT | null> } {
|
export function useNuxtData<DataT = any> (key: string): { data: Ref<DataT | DefaultAsyncDataValue> } {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
|
||||||
// Initialize value when key is not already set
|
// Initialize value when key is not already set
|
||||||
if (!(key in nuxtApp.payload.data)) {
|
if (!(key in nuxtApp.payload.data)) {
|
||||||
nuxtApp.payload.data[key] = null
|
nuxtApp.payload.data[key] = asyncDataDefaults.value
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -520,12 +528,12 @@ function clearNuxtDataByKey (nuxtApp: NuxtApp, key: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key in nuxtApp.payload._errors) {
|
if (key in nuxtApp.payload._errors) {
|
||||||
nuxtApp.payload._errors[key] = null
|
nuxtApp.payload._errors[key] = asyncDataDefaults.errorValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nuxtApp._asyncData[key]) {
|
if (nuxtApp._asyncData[key]) {
|
||||||
nuxtApp._asyncData[key]!.data.value = undefined
|
nuxtApp._asyncData[key]!.data.value = resetAsyncDataToUndefined ? undefined : nuxtApp._asyncData[key]!._default()
|
||||||
nuxtApp._asyncData[key]!.error.value = null
|
nuxtApp._asyncData[key]!.error.value = asyncDataDefaults.errorValue
|
||||||
nuxtApp._asyncData[key]!.pending.value = false
|
nuxtApp._asyncData[key]!.pending.value = false
|
||||||
nuxtApp._asyncData[key]!.status.value = 'idle'
|
nuxtApp._asyncData[key]!.status.value = 'idle'
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@ import { toRef } from 'vue'
|
|||||||
import { useNuxtApp } from '../nuxt'
|
import { useNuxtApp } from '../nuxt'
|
||||||
import { useRouter } from './router'
|
import { useRouter } from './router'
|
||||||
|
|
||||||
|
// @ts-expect-error virtual file
|
||||||
|
import { nuxtDefaultErrorValue } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
|
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
|
||||||
|
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
@ -47,7 +50,7 @@ export const clearError = async (options: { redirect?: string } = {}) => {
|
|||||||
await useRouter().replace(options.redirect)
|
await useRouter().replace(options.redirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
error.value = null
|
error.value = nuxtDefaultErrorValue
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
|
@ -8,6 +8,9 @@ import { useRequestFetch } from './ssr'
|
|||||||
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom } from './asyncData'
|
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom } from './asyncData'
|
||||||
import { useAsyncData } from './asyncData'
|
import { useAsyncData } from './asyncData'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { fetchDefaults } from '#build/nuxt.config.mjs'
|
import { fetchDefaults } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
@ -30,7 +33,7 @@ export interface UseFetchOptions<
|
|||||||
ResT,
|
ResT,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
R extends NitroFetchRequest = string & {},
|
R extends NitroFetchRequest = string & {},
|
||||||
M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>,
|
M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>,
|
||||||
> extends Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'watch'>, ComputedFetchOptions<R, M> {
|
> extends Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'watch'>, ComputedFetchOptions<R, M> {
|
||||||
@ -54,11 +57,11 @@ export function useFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Fetch data from an API endpoint with an SSR-friendly composable.
|
* Fetch data from an API endpoint with an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-fetch}
|
* See {@link https://nuxt.com/docs/api/composables/use-fetch}
|
||||||
@ -77,7 +80,7 @@ export function useFetch<
|
|||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
export function useFetch<
|
export function useFetch<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
ErrorT = FetchError,
|
ErrorT = FetchError,
|
||||||
@ -86,7 +89,7 @@ export function useFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
|
arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
|
||||||
@ -161,8 +164,10 @@ export function useFetch<
|
|||||||
* @see https://github.com/unjs/ofetch/blob/bb2d72baa5d3f332a2185c20fc04e35d2c3e258d/src/fetch.ts#L152
|
* @see https://github.com/unjs/ofetch/blob/bb2d72baa5d3f332a2185c20fc04e35d2c3e258d/src/fetch.ts#L152
|
||||||
*/
|
*/
|
||||||
const timeoutLength = toValue(opts.timeout)
|
const timeoutLength = toValue(opts.timeout)
|
||||||
|
let timeoutId: NodeJS.Timeout
|
||||||
if (timeoutLength) {
|
if (timeoutLength) {
|
||||||
setTimeout(() => controller.abort(), timeoutLength)
|
timeoutId = setTimeout(() => controller.abort(), timeoutLength)
|
||||||
|
controller.signal.onabort = () => clearTimeout(timeoutId)
|
||||||
}
|
}
|
||||||
|
|
||||||
let _$fetch = opts.$fetch || globalThis.$fetch
|
let _$fetch = opts.$fetch || globalThis.$fetch
|
||||||
@ -175,7 +180,7 @@ export function useFetch<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _$fetch(_request.value, { signal: controller.signal, ..._fetchOptions } as any) as Promise<_ResT>
|
return _$fetch(_request.value, { signal: controller.signal, ..._fetchOptions } as any).finally(() => { clearTimeout(timeoutId) }) as Promise<_ResT>
|
||||||
}, _asyncDataOptions)
|
}, _asyncDataOptions)
|
||||||
|
|
||||||
return asyncData
|
return asyncData
|
||||||
@ -190,11 +195,11 @@ export function useLazyFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
export function useLazyFetch<
|
export function useLazyFetch<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
ErrorT = FetchError,
|
ErrorT = FetchError,
|
||||||
@ -207,7 +212,7 @@ export function useLazyFetch<
|
|||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
export function useLazyFetch<
|
export function useLazyFetch<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
ErrorT = FetchError,
|
ErrorT = FetchError,
|
||||||
@ -216,7 +221,7 @@ export function useLazyFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
arg1?: string | Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>,
|
arg1?: string | Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { hasProtocol, joinURL, withoutTrailingSlash } from 'ufo'
|
import { hasProtocol, joinURL, withoutTrailingSlash } from 'ufo'
|
||||||
import { parse } from 'devalue'
|
import { parse } from 'devalue'
|
||||||
import { useHead } from '@unhead/vue'
|
import { useHead } from '@unhead/vue'
|
||||||
import { getCurrentInstance } from 'vue'
|
import { getCurrentInstance, onServerPrefetch } from 'vue'
|
||||||
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
||||||
|
|
||||||
import { useRoute } from './router'
|
import { useRoute } from './router'
|
||||||
@ -16,9 +16,9 @@ interface LoadPayloadOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
export function loadPayload (url: string, opts: LoadPayloadOptions = {}): Record<string, any> | Promise<Record<string, any>> | null {
|
export async function loadPayload (url: string, opts: LoadPayloadOptions = {}): Promise<Record<string, any> | null> {
|
||||||
if (import.meta.server || !payloadExtraction) { return null }
|
if (import.meta.server || !payloadExtraction) { return null }
|
||||||
const payloadURL = _getPayloadURL(url, opts)
|
const payloadURL = await _getPayloadURL(url, opts)
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {}
|
const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {}
|
||||||
if (payloadURL in cache) {
|
if (payloadURL in cache) {
|
||||||
@ -39,26 +39,34 @@ export function loadPayload (url: string, opts: LoadPayloadOptions = {}): Record
|
|||||||
return cache[payloadURL]
|
return cache[payloadURL]
|
||||||
}
|
}
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
export function preloadPayload (url: string, opts: LoadPayloadOptions = {}) {
|
export function preloadPayload (url: string, opts: LoadPayloadOptions = {}): Promise<void> {
|
||||||
const payloadURL = _getPayloadURL(url, opts)
|
const nuxtApp = useNuxtApp()
|
||||||
useHead({
|
const promise = _getPayloadURL(url, opts).then((payloadURL) => {
|
||||||
link: [
|
nuxtApp.runWithContext(() => useHead({
|
||||||
{ rel: 'modulepreload', href: payloadURL },
|
link: [
|
||||||
],
|
{ rel: 'modulepreload', href: payloadURL },
|
||||||
|
],
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
if (import.meta.server) {
|
||||||
|
onServerPrefetch(() => promise)
|
||||||
|
}
|
||||||
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Internal ---
|
// --- Internal ---
|
||||||
|
|
||||||
const filename = renderJsonPayloads ? '_payload.json' : '_payload.js'
|
const filename = renderJsonPayloads ? '_payload.json' : '_payload.js'
|
||||||
function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
|
async function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
|
||||||
const u = new URL(url, 'http://localhost')
|
const u = new URL(url, 'http://localhost')
|
||||||
if (u.host !== 'localhost' || hasProtocol(u.pathname, { acceptRelative: true })) {
|
if (u.host !== 'localhost' || hasProtocol(u.pathname, { acceptRelative: true })) {
|
||||||
throw new Error('Payload URL must not include hostname: ' + url)
|
throw new Error('Payload URL must not include hostname: ' + url)
|
||||||
}
|
}
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const hash = opts.hash || (opts.fresh ? Date.now() : config.app.buildId)
|
const hash = opts.hash || (opts.fresh ? Date.now() : config.app.buildId)
|
||||||
return joinURL(config.app.baseURL, u.pathname, filename + (hash ? `?${hash}` : ''))
|
const cdnURL = config.app.cdnURL
|
||||||
|
const baseOrCdnURL = cdnURL && await isPrerendered(url) ? cdnURL : config.app.baseURL
|
||||||
|
return joinURL(baseOrCdnURL, u.pathname, filename + (hash ? `?${hash}` : ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _importPayload (payloadURL: string) {
|
async function _importPayload (payloadURL: string) {
|
||||||
|
7
packages/nuxt/src/app/defaults.ts
Normal file
7
packages/nuxt/src/app/defaults.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
|
||||||
|
export type DefaultAsyncDataErrorValue = null
|
||||||
|
export type DefaultAsyncDataValue = null
|
||||||
|
export type DefaultErrorValue = null
|
||||||
|
|
||||||
|
export {}
|
@ -23,6 +23,8 @@ 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 } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultErrorValue } from '#app/defaults'
|
||||||
import type { NuxtAppLiterals } from '#app'
|
import type { NuxtAppLiterals } from '#app'
|
||||||
|
|
||||||
function getNuxtAppCtx (appName = appId || 'nuxt-app') {
|
function getNuxtAppCtx (appName = appId || 'nuxt-app') {
|
||||||
@ -92,8 +94,8 @@ export interface NuxtPayload {
|
|||||||
state: Record<string, any>
|
state: Record<string, any>
|
||||||
once: Set<string>
|
once: Set<string>
|
||||||
config?: Pick<RuntimeConfig, 'public' | 'app'>
|
config?: Pick<RuntimeConfig, 'public' | 'app'>
|
||||||
error?: NuxtError | null
|
error?: NuxtError | DefaultErrorValue
|
||||||
_errors: Record<string, NuxtError | null>
|
_errors: Record<string, NuxtError | DefaultAsyncDataErrorValue>
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,10 +122,12 @@ interface _NuxtApp {
|
|||||||
_asyncDataPromises: Record<string, Promise<any> | undefined>
|
_asyncDataPromises: Record<string, Promise<any> | undefined>
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_asyncData: Record<string, {
|
_asyncData: Record<string, {
|
||||||
data: Ref<any>
|
data: Ref<unknown>
|
||||||
pending: Ref<boolean>
|
pending: Ref<boolean>
|
||||||
error: Ref<Error | null>
|
error: Ref<Error | DefaultAsyncDataErrorValue>
|
||||||
status: Ref<AsyncDataRequestStatus>
|
status: Ref<AsyncDataRequestStatus>
|
||||||
|
/** @internal */
|
||||||
|
_default: () => unknown
|
||||||
} | undefined>
|
} | undefined>
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
import { consola, createConsola } from 'consola'
|
import { createConsola } from 'consola'
|
||||||
import type { LogObject } from 'consola'
|
import type { LogObject } from 'consola'
|
||||||
import { parse } from 'devalue'
|
import { parse } from 'devalue'
|
||||||
|
|
||||||
|
import { h } from 'vue'
|
||||||
import { defineNuxtPlugin } from '../nuxt'
|
import { defineNuxtPlugin } from '../nuxt'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { devLogs, devRootDir } from '#build/nuxt.config.mjs'
|
import { devLogs, devRootDir } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
const devRevivers: Record<string, (data: any) => any> = import.meta.server
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
VNode: data => h(data.type, data.props),
|
||||||
|
URL: data => new URL(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(async (nuxtApp) => {
|
||||||
if (import.meta.test) { return }
|
if (import.meta.test) { return }
|
||||||
|
|
||||||
if (import.meta.server) {
|
if (import.meta.server) {
|
||||||
@ -23,42 +31,18 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
date: true,
|
date: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const hydrationLogs = new Set<string>()
|
|
||||||
consola.wrapConsole()
|
|
||||||
consola.addReporter({
|
|
||||||
log (logObj) {
|
|
||||||
try {
|
|
||||||
hydrationLogs.add(JSON.stringify(logObj.args))
|
|
||||||
} catch {
|
|
||||||
// silently ignore - the worst case is a user gets log twice
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
nuxtApp.hook('dev:ssr-logs', (logs) => {
|
nuxtApp.hook('dev:ssr-logs', (logs) => {
|
||||||
for (const log of logs) {
|
for (const log of logs) {
|
||||||
// deduplicate so we don't print out things that are logged on client
|
logger.log(normalizeServerLog({ ...log }))
|
||||||
try {
|
|
||||||
if (!hydrationLogs.size || !hydrationLogs.has(JSON.stringify(log.args))) {
|
|
||||||
logger.log(normalizeServerLog({ ...log }))
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
logger.log(normalizeServerLog({ ...log }))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
nuxtApp.hooks.hook('app:suspense:resolve', () => consola.restoreAll())
|
|
||||||
nuxtApp.hooks.hookOnce('dev:ssr-logs', () => hydrationLogs.clear())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass SSR logs after hydration
|
if (typeof window !== 'undefined') {
|
||||||
nuxtApp.hooks.hook('app:suspense:resolve', async () => {
|
const content = document.getElementById('__NUXT_LOGS__')?.textContent
|
||||||
if (typeof window !== 'undefined') {
|
const logs = content ? parse(content, { ...devRevivers, ...nuxtApp._payloadRevivers }) as LogObject[] : []
|
||||||
const content = document.getElementById('__NUXT_LOGS__')?.textContent
|
await nuxtApp.hooks.callHook('dev:ssr-logs', logs)
|
||||||
const logs = content ? parse(content, nuxtApp._payloadRevivers) as LogObject[] : []
|
}
|
||||||
await nuxtApp.hooks.callHook('dev:ssr-logs', logs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function normalizeFilenames (stack?: string) {
|
function normalizeFilenames (stack?: string) {
|
||||||
|
@ -85,8 +85,8 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
|||||||
changedTemplates.push(template)
|
changedTemplates.push(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
const perf = performance.measure(fullPath, mark?.name) // TODO: remove when Node 14 reaches EOL
|
const perf = performance.measure(fullPath, mark.name)
|
||||||
const setupTime = perf ? Math.round((perf.duration * 100)) / 100 : 0 // TODO: remove when Node 14 reaches EOL
|
const setupTime = Math.round((perf.duration * 100)) / 100
|
||||||
|
|
||||||
if (nuxt.options.debug || setupTime > 500) {
|
if (nuxt.options.debug || setupTime > 500) {
|
||||||
logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`)
|
logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`)
|
||||||
|
@ -161,6 +161,8 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
'nuxt3/dist',
|
'nuxt3/dist',
|
||||||
'nuxt-nightly/dist',
|
'nuxt-nightly/dist',
|
||||||
distDir,
|
distDir,
|
||||||
|
// Ensure app config files have auto-imports injected even if they are pure .js files
|
||||||
|
...nuxt.options._layers.map(layer => resolve(layer.config.srcDir, 'app.config')),
|
||||||
],
|
],
|
||||||
traceInclude: [
|
traceInclude: [
|
||||||
// force include files used in generated code from the runtime-compiler
|
// force include files used in generated code from the runtime-compiler
|
||||||
|
@ -13,7 +13,7 @@ import fse from 'fs-extra'
|
|||||||
import { withTrailingSlash, withoutLeadingSlash } from 'ufo'
|
import { withTrailingSlash, withoutLeadingSlash } from 'ufo'
|
||||||
|
|
||||||
import defu from 'defu'
|
import defu from 'defu'
|
||||||
import { gt } from 'semver'
|
import { gt, satisfies } from 'semver'
|
||||||
import pagesModule from '../pages/module'
|
import pagesModule from '../pages/module'
|
||||||
import metaModule from '../head/module'
|
import metaModule from '../head/module'
|
||||||
import componentsModule from '../components/module'
|
import componentsModule from '../components/module'
|
||||||
@ -129,6 +129,8 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
if (nuxt.options.typescript.shim) {
|
if (nuxt.options.typescript.shim) {
|
||||||
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/vue-shim.d.ts') })
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/vue-shim.d.ts') })
|
||||||
}
|
}
|
||||||
|
// Add shims for `#build/*` imports that do not already have matching types
|
||||||
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/build.d.ts') })
|
||||||
// Add module augmentations directly to NuxtConfig
|
// Add module augmentations directly to NuxtConfig
|
||||||
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/schema.d.ts') })
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/schema.d.ts') })
|
||||||
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/app.config.d.ts') })
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/app.config.d.ts') })
|
||||||
@ -270,6 +272,9 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
...nuxt.options._layers.filter(i => i.cwd.includes('node_modules')).map(i => i.cwd as string),
|
...nuxt.options._layers.filter(i => i.cwd.includes('node_modules')).map(i => i.cwd as string),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Ensure we can resolve dependencies within layers
|
||||||
|
nuxt.options.modulesDir.push(...nuxt.options._layers.map(l => resolve(l.cwd, 'node_modules')))
|
||||||
|
|
||||||
// Init user modules
|
// Init user modules
|
||||||
await nuxt.callHook('modules:before')
|
await nuxt.callHook('modules:before')
|
||||||
const modulesToInstall = []
|
const modulesToInstall = []
|
||||||
@ -557,6 +562,12 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/payload.client'))
|
addPlugin(resolve(nuxt.options.appDir, 'plugins/payload.client'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show compatibility version banner when Nuxt is running with a compatibility version
|
||||||
|
// that is different from the current major version
|
||||||
|
if (!(satisfies(nuxt._version, nuxt.options.future.compatibilityVersion + '.x'))) {
|
||||||
|
console.info(`Running with compatibility version \`${nuxt.options.future.compatibilityVersion}\``)
|
||||||
|
}
|
||||||
|
|
||||||
await nuxt.callHook('ready', nuxt)
|
await nuxt.callHook('ready', nuxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,11 +6,17 @@ import type { H3Event } from 'h3'
|
|||||||
import { withTrailingSlash } from 'ufo'
|
import { withTrailingSlash } from 'ufo'
|
||||||
import { getContext } from 'unctx'
|
import { getContext } from 'unctx'
|
||||||
|
|
||||||
|
import { isVNode } from 'vue'
|
||||||
import type { NitroApp } from '#internal/nitro/app'
|
import type { NitroApp } from '#internal/nitro/app'
|
||||||
|
|
||||||
// @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'
|
||||||
|
|
||||||
|
const devReducers: Record<string, (data: any) => any> = {
|
||||||
|
VNode: data => isVNode(data) ? { type: data.type, props: data.props } : undefined,
|
||||||
|
URL: data => data instanceof URL ? data.toString() : undefined,
|
||||||
|
}
|
||||||
|
|
||||||
interface NuxtDevAsyncContext {
|
interface NuxtDevAsyncContext {
|
||||||
logs: LogObject[]
|
logs: LogObject[]
|
||||||
event: H3Event
|
event: H3Event
|
||||||
@ -54,9 +60,10 @@ 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, ctx.event.context._payloadReducers)}</script>`)
|
htmlContext.bodyAppend.unshift(`<script type="application/json" id="__NUXT_LOGS__">${stringify(ctx.logs, { ...devReducers, ...ctx.event.context._payloadReducers })}</script>`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[nuxt] Failed to stringify dev server logs. You can define your own reducer/reviver for rich types following the instructions in https://nuxt.com/docs/api/composables/use-nuxt-app#payload.', 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.`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
../../../../../ui-templates/dist/templates/error-500.ts
|
|
@ -1 +0,0 @@
|
|||||||
../../../../../ui-templates/dist/templates/error-dev.ts
|
|
@ -327,7 +327,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
|
|
||||||
// Whether we are prerendering route
|
// Whether we are prerendering route
|
||||||
const _PAYLOAD_EXTRACTION = import.meta.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !ssrContext.noSSR && !isRenderingIsland
|
const _PAYLOAD_EXTRACTION = import.meta.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !ssrContext.noSSR && !isRenderingIsland
|
||||||
const payloadURL = _PAYLOAD_EXTRACTION ? joinURL(ssrContext.runtimeConfig.app.baseURL, url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js') + '?' + ssrContext.runtimeConfig.app.buildId : undefined
|
const payloadURL = _PAYLOAD_EXTRACTION ? joinURL(ssrContext.runtimeConfig.app.cdnURL || ssrContext.runtimeConfig.app.baseURL, url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js') + '?' + ssrContext.runtimeConfig.app.buildId : undefined
|
||||||
if (import.meta.prerender) {
|
if (import.meta.prerender) {
|
||||||
ssrContext.payload.prerenderedAt = Date.now()
|
ssrContext.payload.prerenderedAt = Date.now()
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,12 @@ declare module '#app' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '#app/defaults' {
|
||||||
|
type DefaultAsyncDataErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
|
||||||
|
type DefaultAsyncDataValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
|
||||||
|
type DefaultErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
|
||||||
|
}
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
interface ComponentCustomProperties extends NuxtAppInjections { }
|
interface ComponentCustomProperties extends NuxtAppInjections { }
|
||||||
}
|
}
|
||||||
@ -266,10 +272,13 @@ export const useRuntimeConfig = () => window?.__NUXT__?.config || {}
|
|||||||
export const appConfigDeclarationTemplate: NuxtTemplate = {
|
export const appConfigDeclarationTemplate: NuxtTemplate = {
|
||||||
filename: 'types/app.config.d.ts',
|
filename: 'types/app.config.d.ts',
|
||||||
getContents ({ app, nuxt }) {
|
getContents ({ app, nuxt }) {
|
||||||
|
const typesDir = join(nuxt.options.buildDir, 'types')
|
||||||
|
const configPaths = app.configs.map(path => relative(typesDir, path).replace(/\b\.\w+$/g, ''))
|
||||||
|
|
||||||
return `
|
return `
|
||||||
import type { CustomAppConfig } from 'nuxt/schema'
|
import type { CustomAppConfig } from 'nuxt/schema'
|
||||||
import type { Defu } from 'defu'
|
import type { Defu } from 'defu'
|
||||||
${app.configs.map((id: string, index: number) => `import ${`cfg${index}`} from ${JSON.stringify(id.replace(/\b\.\w+$/g, ''))}`).join('\n')}
|
${configPaths.map((id: string, index: number) => `import ${`cfg${index}`} from ${JSON.stringify(id)}`).join('\n')}
|
||||||
|
|
||||||
declare const inlineConfig = ${JSON.stringify(nuxt.options.appConfig, null, 2)}
|
declare const inlineConfig = ${JSON.stringify(nuxt.options.appConfig, null, 2)}
|
||||||
type ResolvedAppConfig = Defu<typeof inlineConfig, [${app.configs.map((_id: string, index: number) => `typeof cfg${index}`).join(', ')}]>
|
type ResolvedAppConfig = Defu<typeof inlineConfig, [${app.configs.map((_id: string, index: number) => `typeof cfg${index}`).join(', ')}]>
|
||||||
@ -393,7 +402,13 @@ export const nuxtConfigTemplate: NuxtTemplate = {
|
|||||||
`export const devRootDir = ${ctx.nuxt.options.dev ? JSON.stringify(ctx.nuxt.options.rootDir) : 'null'}`,
|
`export const devRootDir = ${ctx.nuxt.options.dev ? JSON.stringify(ctx.nuxt.options.rootDir) : 'null'}`,
|
||||||
`export const devLogs = ${JSON.stringify(ctx.nuxt.options.features.devLogs)}`,
|
`export const devLogs = ${JSON.stringify(ctx.nuxt.options.features.devLogs)}`,
|
||||||
`export const nuxtLinkDefaults = ${JSON.stringify(ctx.nuxt.options.experimental.defaults.nuxtLink)}`,
|
`export const nuxtLinkDefaults = ${JSON.stringify(ctx.nuxt.options.experimental.defaults.nuxtLink)}`,
|
||||||
`export const asyncDataDefaults = ${JSON.stringify(ctx.nuxt.options.experimental.defaults.useAsyncData)}`,
|
`export const asyncDataDefaults = ${JSON.stringify({
|
||||||
|
...ctx.nuxt.options.experimental.defaults.useAsyncData,
|
||||||
|
value: ctx.nuxt.options.experimental.defaults.useAsyncData.value === 'null' ? null : undefined,
|
||||||
|
errorValue: ctx.nuxt.options.experimental.defaults.useAsyncData.errorValue === 'null' ? null : undefined,
|
||||||
|
})}`,
|
||||||
|
`export const resetAsyncDataToUndefined = ${ctx.nuxt.options.experimental.resetAsyncDataToUndefined}`,
|
||||||
|
`export const nuxtDefaultErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}`,
|
||||||
`export const fetchDefaults = ${JSON.stringify(fetchDefaults)}`,
|
`export const fetchDefaults = ${JSON.stringify(fetchDefaults)}`,
|
||||||
`export const vueAppRootContainer = ${ctx.nuxt.options.app.rootId ? `'#${ctx.nuxt.options.app.rootId}'` : `'body > ${ctx.nuxt.options.app.rootTag}'`}`,
|
`export const vueAppRootContainer = ${ctx.nuxt.options.app.rootId ? `'#${ctx.nuxt.options.app.rootId}'` : `'body > ${ctx.nuxt.options.app.rootTag}'`}`,
|
||||||
`export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`,
|
`export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`,
|
||||||
@ -401,3 +416,29 @@ export const nuxtConfigTemplate: NuxtTemplate = {
|
|||||||
].join('\n\n')
|
].join('\n\n')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TYPE_FILENAME_RE = /\.([cm])?[jt]s$/
|
||||||
|
const DECLARATION_RE = /\.d\.[cm]?ts$/
|
||||||
|
export const buildTypeTemplate: NuxtTemplate = {
|
||||||
|
filename: 'types/build.d.ts',
|
||||||
|
getContents ({ app }) {
|
||||||
|
let declarations = ''
|
||||||
|
|
||||||
|
for (const file of app.templates) {
|
||||||
|
if (file.write || !file.filename || DECLARATION_RE.test(file.filename)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TYPE_FILENAME_RE.test(file.filename)) {
|
||||||
|
const typeFilenames = new Set([file.filename.replace(TYPE_FILENAME_RE, '.d.$1ts'), file.filename.replace(TYPE_FILENAME_RE, '.d.ts')])
|
||||||
|
if (app.templates.some(f => f.filename && typeFilenames.has(f.filename))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declarations += 'declare module ' + JSON.stringify(join('#build', file.filename)) + ';\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
return declarations
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -135,6 +135,7 @@ export const scriptsStubsPreset = {
|
|||||||
'useScriptGoogleMaps',
|
'useScriptGoogleMaps',
|
||||||
'useScriptNpm',
|
'useScriptNpm',
|
||||||
],
|
],
|
||||||
|
priority: -1,
|
||||||
from: '#app/composables/script-stubs',
|
from: '#app/composables/script-stubs',
|
||||||
} satisfies InlinePreset
|
} satisfies InlinePreset
|
||||||
|
|
||||||
|
@ -422,11 +422,6 @@ export default defineNuxtModule({
|
|||||||
getContents: () => 'export { START_LOCATION, useRoute } from \'vue-router\'',
|
getContents: () => 'export { START_LOCATION, useRoute } from \'vue-router\'',
|
||||||
})
|
})
|
||||||
|
|
||||||
// Optimize vue-router to ensure we share the same injection symbol
|
|
||||||
nuxt.options.vite.optimizeDeps = nuxt.options.vite.optimizeDeps || {}
|
|
||||||
nuxt.options.vite.optimizeDeps.include = nuxt.options.vite.optimizeDeps.include || []
|
|
||||||
nuxt.options.vite.optimizeDeps.include.push('vue-router')
|
|
||||||
|
|
||||||
nuxt.options.vite.resolve = nuxt.options.vite.resolve || {}
|
nuxt.options.vite.resolve = nuxt.options.vite.resolve || {}
|
||||||
nuxt.options.vite.resolve.dedupe = nuxt.options.vite.resolve.dedupe || []
|
nuxt.options.vite.resolve.dedupe = nuxt.options.vite.resolve.dedupe || []
|
||||||
nuxt.options.vite.resolve.dedupe.push('vue-router')
|
nuxt.options.vite.resolve.dedupe.push('vue-router')
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
import type { Nuxt } from '@nuxt/schema'
|
import type { Nuxt } from '@nuxt/schema'
|
||||||
|
import { stripLiteral } from 'strip-literal'
|
||||||
import { isVue } from '../../core/utils'
|
import { isVue } from '../../core/utils'
|
||||||
|
|
||||||
const INJECTION_RE = /\b_ctx\.\$route\b/g
|
const INJECTION_RE_TEMPLATE = /\b_ctx\.\$route\b/g
|
||||||
const INJECTION_SINGLE_RE = /\b_ctx\.\$route\b/
|
const INJECTION_RE_SCRIPT = /\bthis\.\$route\b/g
|
||||||
|
|
||||||
|
const INJECTION_SINGLE_RE = /\bthis\.\$route\b|\b_ctx\.\$route\b/
|
||||||
|
|
||||||
export const RouteInjectionPlugin = (nuxt: Nuxt) => createUnplugin(() => {
|
export const RouteInjectionPlugin = (nuxt: Nuxt) => createUnplugin(() => {
|
||||||
return {
|
return {
|
||||||
@ -14,14 +17,30 @@ export const RouteInjectionPlugin = (nuxt: Nuxt) => createUnplugin(() => {
|
|||||||
return isVue(id, { type: ['template', 'script'] })
|
return isVue(id, { type: ['template', 'script'] })
|
||||||
},
|
},
|
||||||
transform (code) {
|
transform (code) {
|
||||||
if (!INJECTION_SINGLE_RE.test(code) || code.includes('_ctx._.provides[__nuxt_route_symbol')) { return }
|
if (!INJECTION_SINGLE_RE.test(code) || code.includes('_ctx._.provides[__nuxt_route_symbol') || code.includes('this._.provides[__nuxt_route_symbol')) { return }
|
||||||
|
|
||||||
let replaced = false
|
let replaced = false
|
||||||
const s = new MagicString(code)
|
const s = new MagicString(code)
|
||||||
s.replace(INJECTION_RE, () => {
|
const strippedCode = stripLiteral(code)
|
||||||
replaced = true
|
|
||||||
return '(_ctx._.provides[__nuxt_route_symbol] || _ctx.$route)'
|
// Local helper function for regex-based replacements using `strippedCode`
|
||||||
})
|
const replaceMatches = (regExp: RegExp, replacement: string) => {
|
||||||
|
for (const match of strippedCode.matchAll(regExp)) {
|
||||||
|
const start = match.index!
|
||||||
|
const end = start + match[0].length
|
||||||
|
s.overwrite(start, end, replacement)
|
||||||
|
if (!replaced) {
|
||||||
|
replaced = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handles `$route` in template
|
||||||
|
replaceMatches(INJECTION_RE_TEMPLATE, '(_ctx._.provides[__nuxt_route_symbol] || _ctx.$route)')
|
||||||
|
|
||||||
|
// handles `this.$route` in script
|
||||||
|
replaceMatches(INJECTION_RE_SCRIPT, '(this._.provides[__nuxt_route_symbol] || this.$route)')
|
||||||
|
|
||||||
if (replaced) {
|
if (replaced) {
|
||||||
s.prepend('import { PageRouteSymbol as __nuxt_route_symbol } from \'#app/components/injections\';\n')
|
s.prepend('import { PageRouteSymbol as __nuxt_route_symbol } from \'#app/components/injections\';\n')
|
||||||
}
|
}
|
||||||
|
73
packages/nuxt/test/route-injection.test.ts
Normal file
73
packages/nuxt/test/route-injection.test.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { compileScript, compileTemplate, parse } from '@vue/compiler-sfc'
|
||||||
|
import type { Plugin } from 'vite'
|
||||||
|
import type { Nuxt } from '@nuxt/schema'
|
||||||
|
|
||||||
|
import { RouteInjectionPlugin } from '../src/pages/plugins/route-injection'
|
||||||
|
|
||||||
|
describe('route-injection:transform', () => {
|
||||||
|
const injectionPlugin = RouteInjectionPlugin({ options: { sourcemap: { client: false, server: false } } } as Nuxt).raw({}, { framework: 'rollup' }) as Plugin
|
||||||
|
|
||||||
|
const transform = async (source: string) => {
|
||||||
|
const result = await (injectionPlugin.transform! as Function).call({ error: null, warn: null } as any, source, 'test.vue')
|
||||||
|
const code: string = typeof result === 'string' ? result : result?.code
|
||||||
|
let depth = 0
|
||||||
|
return code.split('\n').map((l) => {
|
||||||
|
l = l.trim()
|
||||||
|
if (l.match(/^[}\]]/)) { depth-- }
|
||||||
|
const res = ''.padStart(depth * 2, ' ') + l
|
||||||
|
if (l.match(/[{[]$/)) { depth++ }
|
||||||
|
return res
|
||||||
|
}).join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should correctly inject route in template', async () => {
|
||||||
|
const sfc = `<template>{{ $route.path }}</template>`
|
||||||
|
const res = compileTemplate({
|
||||||
|
filename: 'test.vue',
|
||||||
|
id: 'test.vue',
|
||||||
|
source: sfc,
|
||||||
|
})
|
||||||
|
const transformResult = await transform(res.code)
|
||||||
|
expect(transformResult).toMatchInlineSnapshot(`
|
||||||
|
"import { PageRouteSymbol as __nuxt_route_symbol } from '#app/components/injections';
|
||||||
|
import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("template", null, [
|
||||||
|
_createTextVNode(_toDisplayString((_ctx._.provides[__nuxt_route_symbol] || _ctx.$route).path), 1 /* TEXT */)
|
||||||
|
]))
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly inject route in options api', async () => {
|
||||||
|
const sfc = `
|
||||||
|
<template>{{ thing }}</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
thing () {
|
||||||
|
return this.$route.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
`
|
||||||
|
|
||||||
|
const res = compileScript(parse(sfc).descriptor, { id: 'test.vue' })
|
||||||
|
const transformResult = await transform(res.content)
|
||||||
|
expect(transformResult).toMatchInlineSnapshot(`
|
||||||
|
"import { PageRouteSymbol as __nuxt_route_symbol } from '#app/components/injections';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
thing () {
|
||||||
|
return (this._.provides[__nuxt_route_symbol] || this.$route).path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
@ -35,11 +35,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/telemetry": "2.5.4",
|
"@nuxt/telemetry": "2.5.4",
|
||||||
"@nuxt/ui-templates": "1.3.3",
|
"@nuxt/ui-templates": "1.3.4",
|
||||||
"@types/file-loader": "5.0.4",
|
"@types/file-loader": "5.0.4",
|
||||||
"@types/pug": "2.0.10",
|
"@types/pug": "2.0.10",
|
||||||
"@types/sass-loader": "8.0.8",
|
"@types/sass-loader": "8.0.8",
|
||||||
"@unhead/schema": "1.9.10",
|
"@unhead/schema": "1.9.11",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
"@vitejs/plugin-vue-jsx": "3.1.0",
|
"@vitejs/plugin-vue-jsx": "3.1.0",
|
||||||
"@vue/compiler-core": "3.4.27",
|
"@vue/compiler-core": "3.4.27",
|
||||||
@ -54,7 +54,7 @@
|
|||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"unctx": "2.3.1",
|
"unctx": "2.3.1",
|
||||||
"unenv": "1.9.0",
|
"unenv": "1.9.0",
|
||||||
"vite": "5.2.11",
|
"vite": "5.2.12",
|
||||||
"vue": "3.4.27",
|
"vue": "3.4.27",
|
||||||
"vue-bundle-renderer": "2.1.0",
|
"vue-bundle-renderer": "2.1.0",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
@ -71,7 +71,7 @@
|
|||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"std-env": "^3.7.0",
|
"std-env": "^3.7.0",
|
||||||
"ufo": "^1.5.3",
|
"ufo": "^1.5.3",
|
||||||
"unimport": "^3.7.1",
|
"unimport": "^3.7.2",
|
||||||
"uncrypto": "^0.1.3",
|
"uncrypto": "^0.1.3",
|
||||||
"untyped": "^1.4.2"
|
"untyped": "^1.4.2"
|
||||||
},
|
},
|
||||||
|
@ -261,6 +261,7 @@ export default defineUntypedSchema({
|
|||||||
/**
|
/**
|
||||||
* Boolean or a path to an HTML file with the contents of which will be inserted into any HTML page
|
* Boolean or a path to an HTML file with the contents of which will be inserted into any HTML page
|
||||||
* rendered with `ssr: false`.
|
* rendered with `ssr: false`.
|
||||||
|
*
|
||||||
* - If it is unset, it will use `~/app/spa-loading-template.html` file in one of your layers, if it exists.
|
* - If it is unset, it will use `~/app/spa-loading-template.html` file in one of your layers, if it exists.
|
||||||
* - If it is false, no SPA loading indicator will be loaded.
|
* - If it is false, no SPA loading indicator will be loaded.
|
||||||
* - If true, Nuxt will look for `~/app/spa-loading-template.html` file in one of your layers, or a
|
* - If true, Nuxt will look for `~/app/spa-loading-template.html` file in one of your layers, or a
|
||||||
|
@ -29,6 +29,7 @@ export default defineUntypedSchema({
|
|||||||
* compileTemplate: true,
|
* compileTemplate: true,
|
||||||
* templateUtils: true,
|
* templateUtils: true,
|
||||||
* relativeWatchPaths: true,
|
* relativeWatchPaths: true,
|
||||||
|
* resetAsyncDataToUndefined: true,
|
||||||
* defaults: {
|
* defaults: {
|
||||||
* useAsyncData: {
|
* useAsyncData: {
|
||||||
* deep: true
|
* deep: true
|
||||||
@ -342,8 +343,10 @@ export default defineUntypedSchema({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Use new experimental head optimisations:
|
* Use new experimental head optimisations:
|
||||||
|
*
|
||||||
* - Add the capo.js head plugin in order to render tags in of the head in a more performant way.
|
* - Add the capo.js head plugin in order to render tags in of the head in a more performant way.
|
||||||
* - Uses the hash hydration plugin to reduce initial hydration
|
* - Uses the hash hydration plugin to reduce initial hydration
|
||||||
|
*
|
||||||
* @see [Nuxt Discussion #22632](https://github.com/nuxt/nuxt/discussions/22632]
|
* @see [Nuxt Discussion #22632](https://github.com/nuxt/nuxt/discussions/22632]
|
||||||
*/
|
*/
|
||||||
headNext: true,
|
headNext: true,
|
||||||
@ -392,7 +395,11 @@ export default defineUntypedSchema({
|
|||||||
* })
|
* })
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
sharedPrerenderData: false,
|
sharedPrerenderData: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables CookieStore support to listen for cookie updates (if supported by the browser) and refresh `useCookie` ref values.
|
* Enables CookieStore support to listen for cookie updates (if supported by the browser) and refresh `useCookie` ref values.
|
||||||
@ -415,6 +422,18 @@ export default defineUntypedSchema({
|
|||||||
* Options that apply to `useAsyncData` (and also therefore `useFetch`)
|
* Options that apply to `useAsyncData` (and also therefore `useFetch`)
|
||||||
*/
|
*/
|
||||||
useAsyncData: {
|
useAsyncData: {
|
||||||
|
/** @type {'undefined' | 'null'} */
|
||||||
|
value: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion === 4 ? 'undefined' : 'null')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/** @type {'undefined' | 'null'} */
|
||||||
|
errorValue: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion === 4 ? 'undefined' : 'null')
|
||||||
|
},
|
||||||
|
},
|
||||||
deep: {
|
deep: {
|
||||||
async $resolve (val, get) {
|
async $resolve (val, get) {
|
||||||
return val ?? !((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
|
return val ?? !((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
|
||||||
@ -476,5 +495,15 @@ export default defineUntypedSchema({
|
|||||||
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion !== 4)
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion !== 4)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether `clear` and `clearNuxtData` should reset async data to its _default_ value or update
|
||||||
|
* it to `null`/`undefined`.
|
||||||
|
*/
|
||||||
|
resetAsyncDataToUndefined: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion !== 4)
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -46,11 +46,13 @@ export default defineUntypedSchema({
|
|||||||
* Nitro server handlers.
|
* Nitro server handlers.
|
||||||
*
|
*
|
||||||
* Each handler accepts the following options:
|
* Each handler accepts the following options:
|
||||||
|
*
|
||||||
* - handler: The path to the file defining the handler.
|
* - handler: The path to the file defining the handler.
|
||||||
* - route: The route under which the handler is available. This follows the conventions of https://github.com/unjs/radix3.
|
* - route: The route under which the handler is available. This follows the conventions of https://github.com/unjs/radix3.
|
||||||
* - method: The HTTP method of requests that should be handled.
|
* - method: The HTTP method of requests that should be handled.
|
||||||
* - middleware: Specifies whether it is a middleware handler.
|
* - middleware: Specifies whether it is a middleware handler.
|
||||||
* - lazy: Specifies whether to use lazy loading to import the handler.
|
* - lazy: Specifies whether to use lazy loading to import the handler.
|
||||||
|
*
|
||||||
* @see https://nuxt.com/docs/guide/directory-structure/server
|
* @see https://nuxt.com/docs/guide/directory-structure/server
|
||||||
* @note Files from `server/api`, `server/middleware` and `server/routes` will be automatically registered by Nuxt.
|
* @note Files from `server/api`, `server/middleware` and `server/routes` will be automatically registered by Nuxt.
|
||||||
* @example
|
* @example
|
||||||
|
@ -157,7 +157,11 @@ export default defineUntypedSchema({
|
|||||||
* See https://github.com/esbuild-kit/esbuild-loader
|
* See https://github.com/esbuild-kit/esbuild-loader
|
||||||
* @type {Omit<typeof import('esbuild-loader')['LoaderOptions'], 'loader'>}
|
* @type {Omit<typeof import('esbuild-loader')['LoaderOptions'], 'loader'>}
|
||||||
*/
|
*/
|
||||||
esbuild: {},
|
esbuild: {
|
||||||
|
jsxFactory: 'h',
|
||||||
|
jsxFragment: 'Fragment',
|
||||||
|
tsconfigRaw: '{}',
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See: https://github.com/webpack-contrib/file-loader#options
|
* See: https://github.com/webpack-contrib/file-loader#options
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import { readFileSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs'
|
import { readFileSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs'
|
||||||
|
import { copyFile } from 'node:fs/promises'
|
||||||
import { basename, dirname, join, resolve } from 'pathe'
|
import { basename, dirname, join, resolve } from 'pathe'
|
||||||
import type { Plugin } from 'vite'
|
import type { Plugin } from 'vite'
|
||||||
// @ts-expect-error https://github.com/GoogleChromeLabs/critters/pull/151
|
// @ts-expect-error https://github.com/GoogleChromeLabs/critters/pull/151
|
||||||
@ -167,6 +168,15 @@ export const RenderPlugin = () => {
|
|||||||
unlinkSync(fileName)
|
unlinkSync(fileName)
|
||||||
rmdirSync(dirname(fileName))
|
rmdirSync(dirname(fileName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we manually copy files across rather than using symbolic links for better windows support
|
||||||
|
const nuxtRoot = r('../nuxt')
|
||||||
|
for (const file of ['error-404.vue', 'error-500.vue', 'error-dev.vue', 'welcome.vue']) {
|
||||||
|
await copyFile(r(`dist/templates/${file}`), join(nuxtRoot, 'src/app/components', file))
|
||||||
|
}
|
||||||
|
for (const file of ['error-500.ts', 'error-dev.ts']) {
|
||||||
|
await copyFile(r(`dist/templates/${file}`), join(nuxtRoot, 'src/core/runtime/nitro', file))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,6 @@
|
|||||||
"prettier": "3.2.5",
|
"prettier": "3.2.5",
|
||||||
"scule": "1.3.0",
|
"scule": "1.3.0",
|
||||||
"unocss": "0.60.3",
|
"unocss": "0.60.3",
|
||||||
"vite": "5.2.11"
|
"vite": "5.2.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"cssnano": "^7.0.1",
|
"cssnano": "^7.0.1",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.4",
|
||||||
"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",
|
||||||
@ -62,7 +62,7 @@
|
|||||||
"ufo": "^1.5.3",
|
"ufo": "^1.5.3",
|
||||||
"unenv": "^1.9.0",
|
"unenv": "^1.9.0",
|
||||||
"unplugin": "^1.10.1",
|
"unplugin": "^1.10.1",
|
||||||
"vite": "^5.2.11",
|
"vite": "^5.2.12",
|
||||||
"vite-node": "^1.6.0",
|
"vite-node": "^1.6.0",
|
||||||
"vite-plugin-checker": "^0.6.4",
|
"vite-plugin-checker": "^0.6.4",
|
||||||
"vue-bundle-renderer": "^2.1.0"
|
"vue-bundle-renderer": "^2.1.0"
|
||||||
|
@ -63,6 +63,48 @@ export async function buildClient (ctx: ViteBuildContext) {
|
|||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
entries: [ctx.entry],
|
entries: [ctx.entry],
|
||||||
|
include: [],
|
||||||
|
// We exclude Vue and Nuxt common dependencies from optimization
|
||||||
|
// as they already ship ESM.
|
||||||
|
//
|
||||||
|
// This will help to reduce the chance for users to encounter
|
||||||
|
// common chunk conflicts that causing browser reloads.
|
||||||
|
// We should also encourage module authors to add their deps to
|
||||||
|
// `exclude` if they ships bundled ESM.
|
||||||
|
//
|
||||||
|
// Also since `exclude` is inert, it's safe to always include
|
||||||
|
// all possible deps even if they are not used yet.
|
||||||
|
//
|
||||||
|
// @see https://github.com/antfu/nuxt-better-optimize-deps#how-it-works
|
||||||
|
exclude: [
|
||||||
|
// Vue
|
||||||
|
'vue',
|
||||||
|
'@vue/runtime-core',
|
||||||
|
'@vue/runtime-dom',
|
||||||
|
'@vue/reactivity',
|
||||||
|
'@vue/shared',
|
||||||
|
'@vue/devtools-api',
|
||||||
|
'vue-router',
|
||||||
|
'vue-demi',
|
||||||
|
|
||||||
|
// Nuxt
|
||||||
|
'nuxt',
|
||||||
|
'nuxt/app',
|
||||||
|
|
||||||
|
// Nuxt Deps
|
||||||
|
'@unhead/vue',
|
||||||
|
'consola',
|
||||||
|
'defu',
|
||||||
|
'devalue',
|
||||||
|
'h3',
|
||||||
|
'hookable',
|
||||||
|
'klona',
|
||||||
|
'ofetch',
|
||||||
|
'pathe',
|
||||||
|
'ufo',
|
||||||
|
'unctx',
|
||||||
|
'unenv',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
@ -129,18 +171,22 @@ export async function buildClient (ctx: ViteBuildContext) {
|
|||||||
}) as any
|
}) as any
|
||||||
|
|
||||||
if (clientConfig.server && clientConfig.server.hmr !== false) {
|
if (clientConfig.server && clientConfig.server.hmr !== false) {
|
||||||
const hmrPortDefault = 24678 // Vite's default HMR port
|
const serverDefaults: Omit<ServerOptions, 'hmr'> & { hmr: Exclude<ServerOptions['hmr'], boolean> } = {
|
||||||
const hmrPort = await getPort({
|
|
||||||
port: hmrPortDefault,
|
|
||||||
ports: Array.from({ length: 20 }, (_, i) => hmrPortDefault + 1 + i),
|
|
||||||
})
|
|
||||||
clientConfig.server = defu(clientConfig.server, <ServerOptions> {
|
|
||||||
https: ctx.nuxt.options.devServer.https,
|
|
||||||
hmr: {
|
hmr: {
|
||||||
protocol: ctx.nuxt.options.devServer.https ? 'wss' : 'ws',
|
protocol: ctx.nuxt.options.devServer.https ? 'wss' : 'ws',
|
||||||
port: hmrPort,
|
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
if (typeof clientConfig.server.hmr !== 'object' || !clientConfig.server.hmr.server) {
|
||||||
|
const hmrPortDefault = 24678 // Vite's default HMR port
|
||||||
|
serverDefaults.hmr!.port = await getPort({
|
||||||
|
port: hmrPortDefault,
|
||||||
|
ports: Array.from({ length: 20 }, (_, i) => hmrPortDefault + 1 + i),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (ctx.nuxt.options.devServer.https) {
|
||||||
|
serverDefaults.https = ctx.nuxt.options.devServer.https === true ? {} : ctx.nuxt.options.devServer.https
|
||||||
|
}
|
||||||
|
clientConfig.server = defu(clientConfig.server, serverDefaults as ViteConfig['server'])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add analyze plugin if needed
|
// Add analyze plugin if needed
|
||||||
@ -162,6 +208,10 @@ export async function buildClient (ctx: ViteBuildContext) {
|
|||||||
|
|
||||||
await ctx.nuxt.callHook('vite:configResolved', clientConfig, { isClient: true, isServer: false })
|
await ctx.nuxt.callHook('vite:configResolved', clientConfig, { isClient: true, isServer: false })
|
||||||
|
|
||||||
|
// Prioritize `optimizeDeps.exclude`. If same dep is in `include` and `exclude`, remove it from `include`
|
||||||
|
clientConfig.optimizeDeps!.include = clientConfig.optimizeDeps!.include!
|
||||||
|
.filter(dep => !clientConfig.optimizeDeps!.exclude!.includes(dep))
|
||||||
|
|
||||||
if (ctx.nuxt.options.dev) {
|
if (ctx.nuxt.options.dev) {
|
||||||
// Dev
|
// Dev
|
||||||
const viteServer = await vite.createServer(clientConfig)
|
const viteServer = await vite.createServer(clientConfig)
|
||||||
|
@ -3,21 +3,13 @@ import { useNitro } from '@nuxt/kit'
|
|||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import { withLeadingSlash, withTrailingSlash } from 'ufo'
|
import { withLeadingSlash, withTrailingSlash } from 'ufo'
|
||||||
import { dirname, relative } from 'pathe'
|
import { dirname, relative } from 'pathe'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
|
||||||
const PREFIX = 'virtual:public?'
|
const PREFIX = 'virtual:public?'
|
||||||
|
const CSS_URL_RE = /url\((\/[^)]+)\)/g
|
||||||
|
|
||||||
export const VitePublicDirsPlugin = createUnplugin(() => {
|
export const VitePublicDirsPlugin = createUnplugin((options: { sourcemap?: boolean }) => {
|
||||||
const nitro = useNitro()
|
const { resolveFromPublicAssets } = useResolveFromPublicAssets()
|
||||||
|
|
||||||
function resolveFromPublicAssets (id: string) {
|
|
||||||
for (const dir of nitro.options.publicAssets) {
|
|
||||||
if (!id.startsWith(withTrailingSlash(dir.baseURL || '/'))) { continue }
|
|
||||||
const path = id.replace(withTrailingSlash(dir.baseURL || '/'), withTrailingSlash(dir.dir))
|
|
||||||
if (existsSync(path)) {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'nuxt:vite-public-dir-resolution',
|
name: 'nuxt:vite-public-dir-resolution',
|
||||||
@ -40,14 +32,33 @@ export const VitePublicDirsPlugin = createUnplugin(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
generateBundle (outputOptions, bundle) {
|
renderChunk (code, chunk) {
|
||||||
|
if (!chunk.facadeModuleId?.includes('?inline&used')) { return }
|
||||||
|
|
||||||
|
const s = new MagicString(code)
|
||||||
|
const q = code.match(/(?<= = )['"`]/)?.[0] || '"'
|
||||||
|
for (const [full, url] of code.matchAll(CSS_URL_RE)) {
|
||||||
|
if (resolveFromPublicAssets(url)) {
|
||||||
|
s.replace(full, `url(${q} + publicAssetsURL(${q}${url}${q}) + ${q})`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.hasChanged()) {
|
||||||
|
s.prepend(`import { publicAssetsURL } from '#internal/nuxt/paths';`)
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: options.sourcemap ? s.generateMap({ hires: true }) : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generateBundle (_outputOptions, bundle) {
|
||||||
for (const file in bundle) {
|
for (const file in bundle) {
|
||||||
const chunk = bundle[file]
|
const chunk = bundle[file]
|
||||||
if (!file.endsWith('.css') || chunk.type !== 'asset') { continue }
|
if (!file.endsWith('.css') || chunk.type !== 'asset') { continue }
|
||||||
|
|
||||||
let css = chunk.source.toString()
|
let css = chunk.source.toString()
|
||||||
let wasReplaced = false
|
let wasReplaced = false
|
||||||
for (const [full, url] of css.matchAll(/url\((\/[^)]+)\)/g)) {
|
for (const [full, url] of css.matchAll(CSS_URL_RE)) {
|
||||||
if (resolveFromPublicAssets(url)) {
|
if (resolveFromPublicAssets(url)) {
|
||||||
const relativeURL = relative(withLeadingSlash(dirname(file)), url)
|
const relativeURL = relative(withLeadingSlash(dirname(file)), url)
|
||||||
css = css.replace(full, `url(${relativeURL})`)
|
css = css.replace(full, `url(${relativeURL})`)
|
||||||
@ -62,3 +73,19 @@ export const VitePublicDirsPlugin = createUnplugin(() => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export function useResolveFromPublicAssets () {
|
||||||
|
const nitro = useNitro()
|
||||||
|
|
||||||
|
function resolveFromPublicAssets (id: string) {
|
||||||
|
for (const dir of nitro.options.publicAssets) {
|
||||||
|
if (!id.startsWith(withTrailingSlash(dir.baseURL || '/'))) { continue }
|
||||||
|
const path = id.replace(/[?#].*$/, '').replace(withTrailingSlash(dir.baseURL || '/'), withTrailingSlash(dir.dir))
|
||||||
|
if (existsSync(path)) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { resolveFromPublicAssets }
|
||||||
|
}
|
||||||
|
@ -51,7 +51,7 @@ export async function buildServer (ctx: ViteBuildContext) {
|
|||||||
'XMLHttpRequest': 'undefined',
|
'XMLHttpRequest': 'undefined',
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
entries: ctx.nuxt.options.ssr ? [ctx.entry] : [],
|
noDiscovery: true,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
@ -3,6 +3,7 @@ import { logger } from '@nuxt/kit'
|
|||||||
import { hasTTY, isCI } from 'std-env'
|
import { hasTTY, isCI } from 'std-env'
|
||||||
import clear from 'clear'
|
import clear from 'clear'
|
||||||
import type { NuxtOptions } from '@nuxt/schema'
|
import type { NuxtOptions } from '@nuxt/schema'
|
||||||
|
import { useResolveFromPublicAssets } from '../plugins/public-dirs'
|
||||||
|
|
||||||
let duplicateCount = 0
|
let duplicateCount = 0
|
||||||
let lastType: vite.LogType | null = null
|
let lastType: vite.LogType | null = null
|
||||||
@ -26,11 +27,18 @@ export function createViteLogger (config: vite.InlineConfig): vite.Logger {
|
|||||||
const canClearScreen = hasTTY && !isCI && config.clearScreen
|
const canClearScreen = hasTTY && !isCI && config.clearScreen
|
||||||
const clearScreen = canClearScreen ? clear : () => {}
|
const clearScreen = canClearScreen ? clear : () => {}
|
||||||
|
|
||||||
|
const { resolveFromPublicAssets } = useResolveFromPublicAssets()
|
||||||
|
|
||||||
function output (type: vite.LogType, msg: string, options: vite.LogErrorOptions = {}) {
|
function output (type: vite.LogType, msg: string, options: vite.LogErrorOptions = {}) {
|
||||||
if (typeof msg === 'string' && !process.env.DEBUG) {
|
if (typeof msg === 'string' && !process.env.DEBUG) {
|
||||||
// TODO: resolve upstream in Vite
|
// TODO: resolve upstream in Vite
|
||||||
// Hide sourcemap warnings related to node_modules
|
// Hide sourcemap warnings related to node_modules
|
||||||
if (msg.startsWith('Sourcemap') && msg.includes('node_modules')) { return }
|
if (msg.startsWith('Sourcemap') && msg.includes('node_modules')) { return }
|
||||||
|
// Hide warnings about externals produced by https://github.com/vitejs/vite/blob/v5.2.11/packages/vite/src/node/plugins/css.ts#L350-L355
|
||||||
|
if (msg.includes('didn\'t resolve at build time, it will remain unchanged to be resolved at runtime')) {
|
||||||
|
const id = msg.trim().match(/^([^ ]+) referenced in/m)?.[1]
|
||||||
|
if (id && resolveFromPublicAssets(id)) { return }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sameAsLast = lastType === type && lastMsg === msg
|
const sameAsLast = lastType === type && lastMsg === msg
|
||||||
|
@ -71,10 +71,6 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
|||||||
'abort-controller': 'unenv/runtime/mock/empty',
|
'abort-controller': 'unenv/runtime/mock/empty',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
|
||||||
include: ['vue'],
|
|
||||||
exclude: ['nuxt/app'],
|
|
||||||
},
|
|
||||||
css: resolveCSSOptions(nuxt),
|
css: resolveCSSOptions(nuxt),
|
||||||
define: {
|
define: {
|
||||||
__NUXT_VERSION__: JSON.stringify(nuxt._version),
|
__NUXT_VERSION__: JSON.stringify(nuxt._version),
|
||||||
@ -100,7 +96,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
// add resolver for files in public assets directories
|
// add resolver for files in public assets directories
|
||||||
VitePublicDirsPlugin.vite(),
|
VitePublicDirsPlugin.vite({ sourcemap: !!nuxt.options.sourcemap.server }),
|
||||||
composableKeysPlugin.vite({
|
composableKeysPlugin.vite({
|
||||||
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
|
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
|
||||||
rootDir: nuxt.options.rootDir,
|
rootDir: nuxt.options.rootDir,
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
|
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"css-loader": "^7.1.1",
|
"css-loader": "^7.1.2",
|
||||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||||
"cssnano": "^7.0.1",
|
"cssnano": "^7.0.1",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
|
1094
pnpm-lock.yaml
1094
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1915,11 +1915,24 @@ describe('public directories', () => {
|
|||||||
|
|
||||||
// TODO: dynamic paths in dev
|
// TODO: dynamic paths in dev
|
||||||
describe.skipIf(isDev())('dynamic paths', () => {
|
describe.skipIf(isDev())('dynamic paths', () => {
|
||||||
|
const publicFiles = ['/public.svg', '/css-only-public-asset.svg']
|
||||||
|
const isPublicFile = (base = '/', file: string) => {
|
||||||
|
if (isWebpack) {
|
||||||
|
// TODO: webpack does not yet support dynamic static paths
|
||||||
|
expect(publicFiles).toContain(file)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(file).toMatch(new RegExp(`^${base.replace(/\//g, '\\/')}`))
|
||||||
|
expect(publicFiles).toContain(file.replace(base, '/'))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
it('should work with no overrides', async () => {
|
it('should work with no overrides', async () => {
|
||||||
const html: string = await $fetch<string>('/assets')
|
const html: string = await $fetch<string>('/assets')
|
||||||
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
||||||
const url = match[2] || match[3]
|
const url = match[2] || match[3]
|
||||||
expect(url.startsWith('/_nuxt/') || url === '/public.svg').toBeTruthy()
|
expect(url.startsWith('/_nuxt/') || isPublicFile('/', url)).toBeTruthy()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1929,16 +1942,14 @@ describe.skipIf(isDev())('dynamic paths', () => {
|
|||||||
const urls = Array.from(html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)).map(m => m[2] || m[3])
|
const urls = Array.from(html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)).map(m => m[2] || m[3])
|
||||||
const cssURL = urls.find(u => /_nuxt\/assets.*\.css$/.test(u))
|
const cssURL = urls.find(u => /_nuxt\/assets.*\.css$/.test(u))
|
||||||
expect(cssURL).toBeDefined()
|
expect(cssURL).toBeDefined()
|
||||||
const css: string = await $fetch<string>(cssURL!)
|
const css = await $fetch<string>(cssURL!)
|
||||||
const imageUrls = Array.from(css.matchAll(/url\(([^)]*)\)/g)).map(m => m[1].replace(/[-.]\w{8}\./g, '.'))
|
const imageUrls = new Set(Array.from(css.matchAll(/url\(([^)]*)\)/g)).map(m => m[1].replace(/[-.]\w{8}\./g, '.')))
|
||||||
expect(imageUrls).toMatchInlineSnapshot(`
|
expect([...imageUrls]).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
"./logo.svg",
|
"./logo.svg",
|
||||||
"../public.svg",
|
"../public.svg",
|
||||||
"../public.svg",
|
]
|
||||||
"../public.svg",
|
`)
|
||||||
]
|
|
||||||
`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow setting base URL and build assets directory', async () => {
|
it('should allow setting base URL and build assets directory', async () => {
|
||||||
@ -1952,12 +1963,7 @@ describe.skipIf(isDev())('dynamic paths', () => {
|
|||||||
const html = await $fetch<string>('/foo/assets')
|
const html = await $fetch<string>('/foo/assets')
|
||||||
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
||||||
const url = match[2] || match[3]
|
const url = match[2] || match[3]
|
||||||
expect(
|
expect(url.startsWith('/foo/_other/') || isPublicFile('/foo/', url)).toBeTruthy()
|
||||||
url.startsWith('/foo/_other/') ||
|
|
||||||
url === '/foo/public.svg' ||
|
|
||||||
// TODO: webpack does not yet support dynamic static paths
|
|
||||||
(isWebpack && url === '/public.svg'),
|
|
||||||
).toBeTruthy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(await $fetch<string>('/foo/url')).toContain('path: /foo/url')
|
expect(await $fetch<string>('/foo/url')).toContain('path: /foo/url')
|
||||||
@ -1973,12 +1979,7 @@ describe.skipIf(isDev())('dynamic paths', () => {
|
|||||||
const html = await $fetch<string>('/assets')
|
const html = await $fetch<string>('/assets')
|
||||||
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
||||||
const url = match[2] || match[3]
|
const url = match[2] || match[3]
|
||||||
expect(
|
expect(url.startsWith('./_nuxt/') || isPublicFile('./', url)).toBeTruthy()
|
||||||
url.startsWith('./_nuxt/') ||
|
|
||||||
url === './public.svg' ||
|
|
||||||
// TODO: webpack does not yet support dynamic static paths
|
|
||||||
(isWebpack && url === '/public.svg'),
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(url.startsWith('./_nuxt/_nuxt')).toBeFalsy()
|
expect(url.startsWith('./_nuxt/_nuxt')).toBeFalsy()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -2007,12 +2008,7 @@ describe.skipIf(isDev())('dynamic paths', () => {
|
|||||||
const html = await $fetch<string>('/foo/assets')
|
const html = await $fetch<string>('/foo/assets')
|
||||||
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
||||||
const url = match[2] || match[3]
|
const url = match[2] || match[3]
|
||||||
expect(
|
expect(url.startsWith('https://example.com/_cdn/') || isPublicFile('https://example.com/', url)).toBeTruthy()
|
||||||
url.startsWith('https://example.com/_cdn/') ||
|
|
||||||
url === 'https://example.com/public.svg' ||
|
|
||||||
// TODO: webpack does not yet support dynamic static paths
|
|
||||||
(isWebpack && url === '/public.svg'),
|
|
||||||
).toBeTruthy()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2433,21 +2429,23 @@ describe.skipIf(isWindows)('useAsyncData', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('data is null after navigation when immediate false', async () => {
|
it('data is null after navigation when immediate false', async () => {
|
||||||
|
const defaultValue = isV4 ? 'undefined' : 'null'
|
||||||
|
|
||||||
const { page } = await renderPage('/useAsyncData/immediate-remove-unmounted')
|
const { page } = await renderPage('/useAsyncData/immediate-remove-unmounted')
|
||||||
expect(await page.locator('#immediate-data').getByText('null').textContent()).toBe('null')
|
expect(await page.locator('#immediate-data').getByText(defaultValue).textContent()).toBe(defaultValue)
|
||||||
|
|
||||||
await page.click('#execute-btn')
|
await page.click('#execute-btn')
|
||||||
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain('null')
|
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain(defaultValue)
|
||||||
|
|
||||||
await page.click('#to-index')
|
await page.click('#to-index')
|
||||||
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/')
|
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/')
|
||||||
|
|
||||||
await page.click('#to-immediate-remove-unmounted')
|
await page.click('#to-immediate-remove-unmounted')
|
||||||
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/useAsyncData/immediate-remove-unmounted')
|
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/useAsyncData/immediate-remove-unmounted')
|
||||||
expect(await page.locator('#immediate-data').getByText('null').textContent()).toBe('null')
|
expect(await page.locator('#immediate-data').getByText(defaultValue).textContent()).toBe(defaultValue)
|
||||||
|
|
||||||
await page.click('#execute-btn')
|
await page.click('#execute-btn')
|
||||||
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain('null')
|
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain(defaultValue)
|
||||||
|
|
||||||
await page.close()
|
await page.close()
|
||||||
})
|
})
|
||||||
|
97
test/fixtures/basic-types/types.ts
vendored
97
test/fixtures/basic-types/types.ts
vendored
@ -11,6 +11,9 @@ import type { NavigateToOptions } from '#app/composables/router'
|
|||||||
import { NuxtLayout, NuxtLink, NuxtPage, ServerComponent, WithTypes } from '#components'
|
import { NuxtLayout, NuxtLink, NuxtPage, ServerComponent, WithTypes } from '#components'
|
||||||
import { useRouter } from '#imports'
|
import { useRouter } from '#imports'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||||
|
|
||||||
interface TestResponse { message: string }
|
interface TestResponse { message: string }
|
||||||
|
|
||||||
describe('API routes', () => {
|
describe('API routes', () => {
|
||||||
@ -31,61 +34,61 @@ describe('API routes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('works with useAsyncData', () => {
|
it('works with useAsyncData', () => {
|
||||||
expectTypeOf(useAsyncData('api-hello', () => $fetch('/api/hello')).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useAsyncData('api-hello', () => $fetch('/api/hello')).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useAsyncData('api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-hey-with-pick', () => $fetch('/api/hey'), { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | null>>()
|
expectTypeOf(useAsyncData('api-hey-with-pick', () => $fetch('/api/hey'), { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-union', () => $fetch('/api/union')).data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | null>>()
|
expectTypeOf(useAsyncData('api-union', () => $fetch('/api/union')).data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-union-with-pick', () => $fetch('/api/union'), { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | null>>()
|
expectTypeOf(useAsyncData('api-union-with-pick', () => $fetch('/api/union'), { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useAsyncData('api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
||||||
expectTypeOf(useAsyncData<TestResponse>('api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useAsyncData<TestResponse>('api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useAsyncData('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<unknown> | null>>()
|
expectTypeOf(useAsyncData('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<unknown> | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useAsyncData<any, string>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | null>>()
|
expectTypeOf(useAsyncData<any, string>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | DefaultAsyncDataErrorValue>>()
|
||||||
// backwards compatibility
|
// backwards compatibility
|
||||||
expectTypeOf(useAsyncData<any, Error>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | null>>()
|
expectTypeOf(useAsyncData<any, Error>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useAsyncData<any, NuxtError<string>>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | null>>()
|
expectTypeOf(useAsyncData<any, NuxtError<string>>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | DefaultAsyncDataErrorValue>>()
|
||||||
|
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-hello', () => $fetch('/api/hello')).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-hello', () => $fetch('/api/hello')).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-hey-with-pick', () => $fetch('/api/hey'), { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-hey-with-pick', () => $fetch('/api/hey'), { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-union', () => $fetch('/api/union')).data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-union', () => $fetch('/api/union')).data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-union-with-pick', () => $fetch('/api/union'), { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-union-with-pick', () => $fetch('/api/union'), { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
||||||
expectTypeOf(useLazyAsyncData<TestResponse>('lazy-api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useLazyAsyncData<TestResponse>('lazy-api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useLazyAsyncData('lazy-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useLazyAsyncData<any, string>('lazy-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyAsyncData<any, string>('lazy-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<string | DefaultAsyncDataErrorValue>>()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('works with useFetch', () => {
|
it('works with useFetch', () => {
|
||||||
expectTypeOf(useFetch('/api/hello').data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useFetch('/api/hello').data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey').data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useFetch('/api/hey').data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey', { method: 'GET' }).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useFetch('/api/hey', { method: 'GET' }).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey', { method: 'get' }).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useFetch('/api/hey', { method: 'get' }).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey', { method: 'POST' }).data).toEqualTypeOf<Ref<{ method: 'post' } | null>>()
|
expectTypeOf(useFetch('/api/hey', { method: 'POST' }).data).toEqualTypeOf<Ref<{ method: 'post' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey', { method: 'post' }).data).toEqualTypeOf<Ref<{ method: 'post' } | null>>()
|
expectTypeOf(useFetch('/api/hey', { method: 'post' }).data).toEqualTypeOf<Ref<{ method: 'post' } | DefaultAsyncDataValue>>()
|
||||||
// @ts-expect-error not a valid method
|
// @ts-expect-error not a valid method
|
||||||
useFetch('/api/hey', { method: 'PATCH' })
|
useFetch('/api/hey', { method: 'PATCH' })
|
||||||
expectTypeOf(useFetch('/api/hey', { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | null>>()
|
expectTypeOf(useFetch('/api/hey', { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/union').data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | null>>()
|
expectTypeOf(useFetch('/api/union').data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/union', { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | null>>()
|
expectTypeOf(useFetch('/api/union', { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
|
||||||
expectTypeOf(useFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch<TestResponse>('/test', { method: 'POST' }).data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useFetch<TestResponse>('/test', { method: 'POST' }).data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useFetch('/error').error).toEqualTypeOf<Ref<FetchError | null>>()
|
expectTypeOf(useFetch('/error').error).toEqualTypeOf<Ref<FetchError | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | DefaultAsyncDataErrorValue>>()
|
||||||
|
|
||||||
expectTypeOf(useLazyFetch('/api/hello').data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyFetch('/api/hello').data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/hey').data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useLazyFetch('/api/hey').data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/hey', { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | null>>()
|
expectTypeOf(useLazyFetch('/api/hey', { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/union').data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | null>>()
|
expectTypeOf(useLazyFetch('/api/union').data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/union', { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | null>>()
|
expectTypeOf(useLazyFetch('/api/union', { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useLazyFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
|
||||||
expectTypeOf(useLazyFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useLazyFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useLazyFetch('/error').error).toEqualTypeOf<Ref<FetchError | null>>()
|
expectTypeOf(useLazyFetch('/error').error).toEqualTypeOf<Ref<FetchError | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useLazyFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | DefaultAsyncDataErrorValue>>()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -421,10 +424,10 @@ describe('composables', () => {
|
|||||||
expectTypeOf(useLazyAsyncData<string>(() => $fetch('/test'), { default: () => 'test' }).data).toEqualTypeOf<Ref<string>>()
|
expectTypeOf(useLazyAsyncData<string>(() => $fetch('/test'), { default: () => 'test' }).data).toEqualTypeOf<Ref<string>>()
|
||||||
|
|
||||||
// transform must match the explicit generic because of typescript limitations microsoft/TypeScript#14400
|
// transform must match the explicit generic because of typescript limitations microsoft/TypeScript#14400
|
||||||
expectTypeOf(useFetch<string>('/test', { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useFetch<string>('/test', { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch<string>('/test', { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyFetch<string>('/test', { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData<string>(() => $fetch('/test'), { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useAsyncData<string>(() => $fetch('/test'), { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData<string>(() => $fetch('/test'), { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyAsyncData<string>(() => $fetch('/test'), { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useFetch<string>('/test', { default: () => 'test', transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string>>()
|
expectTypeOf(useFetch<string>('/test', { default: () => 'test', transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string>>()
|
||||||
expectTypeOf(useLazyFetch<string>('/test', { default: () => 'test', transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string>>()
|
expectTypeOf(useLazyFetch<string>('/test', { default: () => 'test', transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string>>()
|
||||||
@ -439,7 +442,7 @@ describe('composables', () => {
|
|||||||
return data.foo
|
return data.foo
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expectTypeOf(data).toEqualTypeOf<Ref<'bar' | null>>()
|
expectTypeOf(data).toEqualTypeOf<Ref<'bar' | DefaultAsyncDataValue>>()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('infer request url string literal from server/api routes', () => {
|
it('infer request url string literal from server/api routes', () => {
|
||||||
@ -448,8 +451,8 @@ describe('composables', () => {
|
|||||||
expectTypeOf(useFetch(dynamicStringUrl).data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useFetch(dynamicStringUrl).data).toEqualTypeOf<Ref<unknown>>()
|
||||||
|
|
||||||
// request param should infer string literal type / show auto-complete hint base on server routes, ex: '/api/hello'
|
// request param should infer string literal type / show auto-complete hint base on server routes, ex: '/api/hello'
|
||||||
expectTypeOf(useFetch('/api/hello').data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useFetch('/api/hello').data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/hello').data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyFetch('/api/hello').data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
// request can accept string literal and Request object type
|
// request can accept string literal and Request object type
|
||||||
expectTypeOf(useFetch('https://example.com/api').data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useFetch('https://example.com/api').data).toEqualTypeOf<Ref<unknown>>()
|
||||||
@ -519,7 +522,7 @@ describe('composables', () => {
|
|||||||
it('correctly types returns when using with getCachedData', () => {
|
it('correctly types returns when using with getCachedData', () => {
|
||||||
expectTypeOf(useAsyncData('test', () => Promise.resolve({ foo: 1 }), {
|
expectTypeOf(useAsyncData('test', () => Promise.resolve({ foo: 1 }), {
|
||||||
getCachedData: key => useNuxtApp().payload.data[key],
|
getCachedData: key => useNuxtApp().payload.data[key],
|
||||||
}).data).toEqualTypeOf<Ref<{ foo: number } | null>>()
|
}).data).toEqualTypeOf<Ref<{ foo: number } | DefaultAsyncDataValue>>()
|
||||||
useAsyncData('test', () => Promise.resolve({ foo: 1 }), {
|
useAsyncData('test', () => Promise.resolve({ foo: 1 }), {
|
||||||
// @ts-expect-error cached data should return the same as value of fetcher
|
// @ts-expect-error cached data should return the same as value of fetcher
|
||||||
getCachedData: () => ({ bar: 2 }),
|
getCachedData: () => ({ bar: 2 }),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// eslint-disable-next-line no-undef
|
||||||
export default defineAppConfig({
|
export default defineAppConfig({
|
||||||
userConfig: 123,
|
userConfig: 123,
|
||||||
nested: {
|
nested: {
|
1
test/fixtures/basic/assets/global.css
vendored
1
test/fixtures/basic/assets/global.css
vendored
@ -1,4 +1,5 @@
|
|||||||
:root {
|
:root {
|
||||||
--global: 'global';
|
--global: 'global';
|
||||||
--asset: url('~/assets/css-only-asset.svg');
|
--asset: url('~/assets/css-only-asset.svg');
|
||||||
|
--public-asset: url('/css-only-public-asset.svg');
|
||||||
}
|
}
|
||||||
|
1
test/fixtures/basic/pages/assets-custom.vue
vendored
1
test/fixtures/basic/pages/assets-custom.vue
vendored
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<img src="/public.svg">
|
<img src="/public.svg">
|
||||||
|
<img src="/public.svg?123">
|
||||||
<img src="/custom/file.svg">
|
<img src="/custom/file.svg">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div>immediate-remove-unmounted.vue</div>
|
<div>immediate-remove-unmounted.vue</div>
|
||||||
<div id="immediate-data">
|
<div id="immediate-data">
|
||||||
{{ data === null ? "null" : data }}
|
{{ data === null ? "null" : (data === undefined ? 'undefined' : data) }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
id="execute-btn"
|
id="execute-btn"
|
||||||
@ -20,9 +20,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { asyncDataDefaults } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
const { data, execute } = await useAsyncData('immediateFalse', () => $fetch('/api/random'), { immediate: false })
|
const { data, execute } = await useAsyncData('immediateFalse', () => $fetch('/api/random'), { immediate: false })
|
||||||
|
|
||||||
if (data.value !== null) {
|
if (data.value !== asyncDataDefaults.errorValue) {
|
||||||
throw new Error('Initial data should be null: ' + data.value)
|
throw new Error(`Initial data should be ${asyncDataDefaults.errorValue}: ` + data.value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
1
test/fixtures/basic/public/css-only-public-asset.svg
vendored
Normal file
1
test/fixtures/basic/public/css-only-public-asset.svg
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"></svg>
|
After Width: | Height: | Size: 67 B |
@ -20,6 +20,9 @@ import { callOnce } from '#app/composables/once'
|
|||||||
import { useLoadingIndicator } from '#app/composables/loading-indicator'
|
import { useLoadingIndicator } from '#app/composables/loading-indicator'
|
||||||
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
||||||
|
|
||||||
|
// @ts-expect-error virtual file
|
||||||
|
import { asyncDataDefaults, nuxtDefaultErrorValue } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
registerEndpoint('/api/test', defineEventHandler(event => ({
|
registerEndpoint('/api/test', defineEventHandler(event => ({
|
||||||
method: event.method,
|
method: event.method,
|
||||||
headers: Object.fromEntries(event.headers.entries()),
|
headers: Object.fromEntries(event.headers.entries()),
|
||||||
@ -126,7 +129,7 @@ describe('useAsyncData', () => {
|
|||||||
]
|
]
|
||||||
`)
|
`)
|
||||||
expect(res instanceof Promise).toBeTruthy()
|
expect(res instanceof Promise).toBeTruthy()
|
||||||
expect(res.data.value).toBe(null)
|
expect(res.data.value).toBe(asyncDataDefaults.value)
|
||||||
await res
|
await res
|
||||||
expect(res.data.value).toBe('test')
|
expect(res.data.value).toBe('test')
|
||||||
})
|
})
|
||||||
@ -138,7 +141,7 @@ describe('useAsyncData', () => {
|
|||||||
expect(immediate.pending.value).toBe(false)
|
expect(immediate.pending.value).toBe(false)
|
||||||
|
|
||||||
const nonimmediate = await useAsyncData(() => Promise.resolve('test'), { immediate: false })
|
const nonimmediate = await useAsyncData(() => Promise.resolve('test'), { immediate: false })
|
||||||
expect(nonimmediate.data.value).toBe(null)
|
expect(nonimmediate.data.value).toBe(asyncDataDefaults.value)
|
||||||
expect(nonimmediate.status.value).toBe('idle')
|
expect(nonimmediate.status.value).toBe('idle')
|
||||||
expect(nonimmediate.pending.value).toBe(true)
|
expect(nonimmediate.pending.value).toBe(true)
|
||||||
})
|
})
|
||||||
@ -163,9 +166,9 @@ describe('useAsyncData', () => {
|
|||||||
// https://github.com/nuxt/nuxt/issues/23411
|
// https://github.com/nuxt/nuxt/issues/23411
|
||||||
it('should initialize with error set to null when immediate: false', async () => {
|
it('should initialize with error set to null when immediate: false', async () => {
|
||||||
const { error, execute } = useAsyncData(() => ({}), { immediate: false })
|
const { error, execute } = useAsyncData(() => ({}), { immediate: false })
|
||||||
expect(error.value).toBe(null)
|
expect(error.value).toBe(asyncDataDefaults.errorValue)
|
||||||
await execute()
|
await execute()
|
||||||
expect(error.value).toBe(null)
|
expect(error.value).toBe(asyncDataDefaults.errorValue)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be accessible with useNuxtData', async () => {
|
it('should be accessible with useNuxtData', async () => {
|
||||||
@ -206,8 +209,9 @@ describe('useAsyncData', () => {
|
|||||||
|
|
||||||
clear()
|
clear()
|
||||||
|
|
||||||
|
// TODO: update to asyncDataDefaults.value in v4
|
||||||
expect(data.value).toBeUndefined()
|
expect(data.value).toBeUndefined()
|
||||||
expect(error.value).toBeNull()
|
expect(error.value).toBe(asyncDataDefaults.errorValue)
|
||||||
expect(pending.value).toBe(false)
|
expect(pending.value).toBe(false)
|
||||||
expect(status.value).toBe('idle')
|
expect(status.value).toBe('idle')
|
||||||
})
|
})
|
||||||
@ -345,13 +349,12 @@ describe('errors', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('global nuxt errors', () => {
|
it('global nuxt errors', () => {
|
||||||
const err = useError()
|
const error = useError()
|
||||||
expect(err.value).toBeUndefined()
|
expect(error.value).toBeUndefined()
|
||||||
showError('new error')
|
showError('new error')
|
||||||
expect(err.value).toMatchInlineSnapshot('[Error: new error]')
|
expect(error.value).toMatchInlineSnapshot('[Error: new error]')
|
||||||
clearError()
|
clearError()
|
||||||
// TODO: should this return to being undefined?
|
expect(error.value).toBe(nuxtDefaultErrorValue)
|
||||||
expect(err.value).toBeNull()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -616,7 +619,7 @@ describe('routing utilities: `abortNavigation`', () => {
|
|||||||
it('should throw an error if one is provided', () => {
|
it('should throw an error if one is provided', () => {
|
||||||
const error = useError()
|
const error = useError()
|
||||||
expect(() => abortNavigation({ message: 'Page not found' })).toThrowErrorMatchingInlineSnapshot('[Error: Page not found]')
|
expect(() => abortNavigation({ message: 'Page not found' })).toThrowErrorMatchingInlineSnapshot('[Error: Page not found]')
|
||||||
expect(error.value).toBeFalsy()
|
expect(error.value).toBe(nuxtDefaultErrorValue)
|
||||||
})
|
})
|
||||||
it('should block navigation if no error is provided', () => {
|
it('should block navigation if no error is provided', () => {
|
||||||
expect(abortNavigation()).toMatchInlineSnapshot('false')
|
expect(abortNavigation()).toMatchInlineSnapshot('false')
|
||||||
|
Loading…
Reference in New Issue
Block a user