mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-21 16:09:52 +00:00
Merge branch 'main' into payload-extraction
This commit is contained in:
commit
66c3549e1e
4
.github/workflows/autofix-docs.yml
vendored
4
.github/workflows/autofix-docs.yml
vendored
@ -18,8 +18,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/autofix.yml
vendored
4
.github/workflows/autofix.yml
vendored
@ -14,8 +14,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/changelog.yml
vendored
4
.github/workflows/changelog.yml
vendored
@ -25,8 +25,8 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
@ -38,8 +38,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -78,7 +78,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5
|
||||
uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
with:
|
||||
config: |
|
||||
paths:
|
||||
@ -95,7 +95,7 @@ jobs:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5
|
||||
uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
|
||||
@ -112,8 +112,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -143,8 +143,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -170,8 +170,8 @@ jobs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -195,8 +195,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -220,8 +220,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -280,8 +280,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: "pnpm"
|
||||
@ -332,8 +332,8 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -364,8 +364,8 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
2
.github/workflows/docs-check-links.yml
vendored
2
.github/workflows/docs-check-links.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Lychee link checker
|
||||
uses: lycheeverse/lychee-action@f796c8b7d468feb9b8c0a46da3fac0af6874d374 # for v1.8.0
|
||||
uses: lycheeverse/lychee-action@f613c4a64e50d792e0b31ec34bbcbba12263c6a6 # for v1.8.0
|
||||
with:
|
||||
# arguments with file types to check
|
||||
args: >-
|
||||
|
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
@ -22,8 +22,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/lint-monorepo.yml
vendored
4
.github/workflows/lint-monorepo.yml
vendored
@ -26,8 +26,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -22,8 +22,8 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
registry-url: "https://registry.npmjs.org/"
|
||||
|
2
.github/workflows/scorecards.yml
vendored
2
.github/workflows/scorecards.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5
|
||||
uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
if: github.repository == 'nuxt/nuxt' && success()
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
@ -176,7 +176,7 @@ Now, before navigation to that page can complete, the `auth` route middleware wi
|
||||
|
||||
:link-example{to="/docs/examples/routing/middleware"}
|
||||
|
||||
## Setting Middleware At Build Time
|
||||
## Setting Middleware at Build Time
|
||||
|
||||
Instead of using `definePageMeta` on each page, you can add named route middleware within the `pages:extend` hook.
|
||||
|
||||
|
@ -190,7 +190,7 @@ This configuration will observe when the element enters the viewport and also li
|
||||
|
||||
### Enable Cross-origin Prefetch
|
||||
|
||||
To enable cross-origin prefetching, you can set the `crossOriginPrefetch` option in your `nuxt.config`. This will enabled cross-origin prefetch using the [Speculation Rules API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API).
|
||||
To enable cross-origin prefetching, you can set the `crossOriginPrefetch` option in your `nuxt.config`. This will enable cross-origin prefetching using the [Speculation Rules API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API).
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
|
@ -34,4 +34,7 @@ exclude = [
|
||||
# single-quotes are required for regexp
|
||||
'(https?:\/\/github\.com\/)(.*\/)(generate)',
|
||||
"https://github.com/nuxt-contrib/vue3-ssr-starter/generate",
|
||||
# excluded URLs from test suite
|
||||
"http://auth.com",
|
||||
"http://example2.com/",
|
||||
]
|
||||
|
@ -1,5 +1,6 @@
|
||||
// For pnpm typecheck:docs to generate correct types
|
||||
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { addPluginTemplate, addRouteMiddleware } from 'nuxt/kit'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
@ -17,6 +18,9 @@ export default defineNuxtConfig({
|
||||
},
|
||||
],
|
||||
pages: process.env.DOCS_TYPECHECK === 'true',
|
||||
dir: {
|
||||
app: fileURLToPath(new URL('./test/runtime/app', import.meta.url)),
|
||||
},
|
||||
typescript: {
|
||||
shim: process.env.DOCS_TYPECHECK === 'true',
|
||||
hoist: ['@vitejs/plugin-vue', 'vue-router'],
|
||||
|
40
package.json
40
package.json
@ -39,13 +39,13 @@
|
||||
"resolutions": {
|
||||
"@babel/core": "7.26.7",
|
||||
"@babel/helper-plugin-utils": "7.26.5",
|
||||
"@nuxt/cli": "3.20.0",
|
||||
"@nuxt/cli": "3.21.1",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/rspack-builder": "workspace:*",
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@types/node": "22.10.10",
|
||||
"@types/node": "22.13.1",
|
||||
"@unhead/dom": "1.11.18",
|
||||
"@unhead/schema": "1.11.18",
|
||||
"@unhead/shared": "1.11.18",
|
||||
@ -58,18 +58,18 @@
|
||||
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||
"jiti": "2.4.2",
|
||||
"magic-string": "^0.30.17",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28969273.f7aa9de6",
|
||||
"nuxt": "workspace:*",
|
||||
"ohash": "1.1.4",
|
||||
"postcss": "8.5.1",
|
||||
"rollup": "4.32.0",
|
||||
"rollup": "4.34.4",
|
||||
"send": ">=1.1.0",
|
||||
"typescript": "5.7.3",
|
||||
"ufo": "1.5.4",
|
||||
"unbuild": "3.3.1",
|
||||
"unhead": "1.11.18",
|
||||
"unimport": "4.0.0",
|
||||
"vite": "6.0.11",
|
||||
"vite": "6.1.0",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -77,8 +77,8 @@
|
||||
"@babel/core": "7.26.7",
|
||||
"@babel/helper-plugin-utils": "7.26.5",
|
||||
"@codspeed/vitest-plugin": "4.0.0",
|
||||
"@nuxt/cli": "3.20.0",
|
||||
"@nuxt/eslint-config": "0.7.5",
|
||||
"@nuxt/cli": "3.21.1",
|
||||
"@nuxt/eslint-config": "1.0.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/rspack-builder": "workspace:*",
|
||||
"@nuxt/test-utils": "3.15.4",
|
||||
@ -86,11 +86,11 @@
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/babel__core": "7.20.5",
|
||||
"@types/babel__helper-plugin-utils": "7.10.3",
|
||||
"@types/node": "22.10.10",
|
||||
"@types/node": "22.13.1",
|
||||
"@types/semver": "7.5.8",
|
||||
"@unhead/schema": "1.11.18",
|
||||
"@unhead/vue": "1.11.18",
|
||||
"@vitest/coverage-v8": "3.0.4",
|
||||
"@vitest/coverage-v8": "3.0.5",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"acorn": "8.14.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
@ -102,40 +102,40 @@
|
||||
"devalue": "5.1.1",
|
||||
"eslint": "9.19.0",
|
||||
"eslint-plugin-no-only-tests": "3.3.0",
|
||||
"eslint-plugin-perfectionist": "4.7.0",
|
||||
"eslint-plugin-perfectionist": "4.8.0",
|
||||
"eslint-typegen": "1.0.0",
|
||||
"estree-walker": "3.0.3",
|
||||
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||
"happy-dom": "16.7.2",
|
||||
"happy-dom": "17.0.0",
|
||||
"installed-check": "9.3.0",
|
||||
"jiti": "2.4.2",
|
||||
"knip": "5.43.3",
|
||||
"knip": "5.43.6",
|
||||
"magic-string": "0.30.17",
|
||||
"markdownlint-cli": "0.44.0",
|
||||
"memfs": "4.17.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28969273.f7aa9de6",
|
||||
"nuxt": "workspace:*",
|
||||
"nuxt-content-twoslash": "0.1.2",
|
||||
"ofetch": "1.4.1",
|
||||
"pathe": "2.0.2",
|
||||
"pkg-pr-new": "0.0.39",
|
||||
"playwright-core": "1.50.0",
|
||||
"rollup": "4.32.0",
|
||||
"semver": "7.6.3",
|
||||
"sherif": "1.2.0",
|
||||
"playwright-core": "1.50.1",
|
||||
"rollup": "4.34.4",
|
||||
"semver": "7.7.1",
|
||||
"sherif": "1.3.0",
|
||||
"std-env": "3.8.0",
|
||||
"tinyexec": "0.3.2",
|
||||
"tinyglobby": "0.2.10",
|
||||
"ts-blank-space": "0.5.0",
|
||||
"ts-blank-space": "0.5.1",
|
||||
"typescript": "5.7.3",
|
||||
"ufo": "1.5.4",
|
||||
"unbuild": "3.3.1",
|
||||
"vitest": "3.0.4",
|
||||
"vitest": "3.0.5",
|
||||
"vitest-environment-nuxt": "1.0.1",
|
||||
"vue": "3.5.13",
|
||||
"vue-tsc": "2.2.0",
|
||||
"webpack": "5.97.1"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.4",
|
||||
"packageManager": "pnpm@10.2.0",
|
||||
"version": ""
|
||||
}
|
||||
|
@ -27,7 +27,6 @@
|
||||
"test:attw": "attw --pack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"c12": "^2.0.1",
|
||||
"consola": "^3.4.0",
|
||||
"defu": "^6.1.4",
|
||||
@ -42,7 +41,7 @@
|
||||
"pathe": "^2.0.2",
|
||||
"pkg-types": "^1.3.1",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.6.3",
|
||||
"semver": "^7.7.1",
|
||||
"std-env": "^3.8.0",
|
||||
"ufo": "^1.5.4",
|
||||
"unctx": "^2.4.1",
|
||||
@ -50,12 +49,13 @@
|
||||
"untyped": "^1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@rspack/core": "1.2.2",
|
||||
"@types/semver": "7.5.8",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28969273.f7aa9de6",
|
||||
"unbuild": "3.3.1",
|
||||
"vite": "6.0.11",
|
||||
"vitest": "3.0.4",
|
||||
"vite": "6.1.0",
|
||||
"vitest": "3.0.5",
|
||||
"webpack": "5.97.1"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1,27 +1,21 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import type { JSValue } from 'untyped'
|
||||
import { applyDefaults } from 'untyped'
|
||||
import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12'
|
||||
import { loadConfig } from 'c12'
|
||||
import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
|
||||
import { NuxtConfigSchema } from '@nuxt/schema'
|
||||
import { globby } from 'globby'
|
||||
import defu from 'defu'
|
||||
import { join } from 'pathe'
|
||||
import { isWindows } from 'std-env'
|
||||
import { tryResolveModule } from '../internal/esm'
|
||||
|
||||
export interface LoadNuxtConfigOptions extends Omit<LoadConfigOptions<NuxtConfig>, 'overrides'> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
overrides?: Exclude<LoadConfigOptions<NuxtConfig>['overrides'], Promise<any> | Function>
|
||||
}
|
||||
|
||||
const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'serverDir', 'dir']
|
||||
const layerSchema = Object.create(null)
|
||||
for (const key of layerSchemaKeys) {
|
||||
if (key in NuxtConfigSchema) {
|
||||
layerSchema[key] = NuxtConfigSchema[key]
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
|
||||
// Automatically detect and import layers from `~~/layers/` directory
|
||||
opts.overrides = defu(opts.overrides, {
|
||||
@ -54,6 +48,16 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
|
||||
nuxtConfig.buildDir = join(nuxtConfig.rootDir!, 'node_modules/.cache/nuxt/.nuxt')
|
||||
}
|
||||
|
||||
const NuxtConfigSchema = await loadNuxtSchema(nuxtConfig.rootDir || cwd || process.cwd())
|
||||
|
||||
const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'serverDir', 'dir']
|
||||
const layerSchema = Object.create(null)
|
||||
for (const key of layerSchemaKeys) {
|
||||
if (key in NuxtConfigSchema) {
|
||||
layerSchema[key] = NuxtConfigSchema[key]
|
||||
}
|
||||
}
|
||||
|
||||
const _layers: ConfigLayer<NuxtConfig, ConfigLayerMeta>[] = []
|
||||
const processedLayers = new Set<string>()
|
||||
for (const layer of layers) {
|
||||
@ -89,3 +93,13 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
|
||||
// Resolve and apply defaults
|
||||
return await applyDefaults(NuxtConfigSchema, nuxtConfig as NuxtConfig & Record<string, JSValue>) as unknown as NuxtOptions
|
||||
}
|
||||
|
||||
async function loadNuxtSchema (cwd: string) {
|
||||
const paths = [cwd]
|
||||
const nuxtPath = await tryResolveModule('nuxt', cwd) ?? await tryResolveModule('nuxt-nightly', cwd)
|
||||
if (nuxtPath) {
|
||||
paths.unshift(nuxtPath)
|
||||
}
|
||||
const schemaPath = await tryResolveModule('@nuxt/schema', paths) ?? '@nuxt/schema'
|
||||
return await import(isWindows ? pathToFileURL(schemaPath).href : schemaPath).then(r => r.NuxtConfigSchema)
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ function _defineNuxtModule<
|
||||
// Measure setup time
|
||||
if (setupTime > 5000 && uniqueKey !== '@nuxt/telemetry') {
|
||||
logger.warn(`Slow module \`${uniqueKey || '<no name>'}\` took \`${setupTime}ms\` to setup.`)
|
||||
} else if (nuxt.options.debug) {
|
||||
} else if (nuxt.options.debug && nuxt.options.debug.modules) {
|
||||
logger.info(`Module \`${uniqueKey || '<no name>'}\` took \`${setupTime}ms\` to setup.`)
|
||||
}
|
||||
|
||||
|
76
packages/kit/src/runtime-config.test.ts
Normal file
76
packages/kit/src/runtime-config.test.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useRuntimeConfig } from './runtime-config'
|
||||
|
||||
const { useNuxt, klona } = vi.hoisted(() => ({ useNuxt: vi.fn(), klona: vi.fn() }))
|
||||
|
||||
vi.mock('./context', () => ({ useNuxt }))
|
||||
vi.mock('klona', () => ({ klona }))
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
description:
|
||||
'should return runtime config with environment variables applied',
|
||||
runtimeConfig: {
|
||||
apiUrl: 'http://localhost',
|
||||
authUrl: 'http://auth.com',
|
||||
},
|
||||
envExpansion: true,
|
||||
env: {
|
||||
NITRO_API_URL: 'http://example.com',
|
||||
},
|
||||
expected: {
|
||||
apiUrl: 'http://example.com',
|
||||
authUrl: 'http://auth.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'should expand environment variables in strings',
|
||||
runtimeConfig: {
|
||||
apiUrl: '{{BASE_URL}}/api',
|
||||
mail: '{{MAIL_SCHEME}}://{{MAIL_HOST}}:{{MAIL_PORT}}',
|
||||
},
|
||||
envExpansion: true,
|
||||
env: {
|
||||
BASE_URL: 'http://example.com',
|
||||
MAIL_SCHEME: 'http',
|
||||
MAIL_HOST: 'localhost',
|
||||
MAIL_PORT: '3366',
|
||||
},
|
||||
expected: {
|
||||
apiUrl: 'http://example.com/api',
|
||||
mail: 'http://localhost:3366',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'should not expand environment variables if envExpansion is false',
|
||||
runtimeConfig: {
|
||||
apiUrl: '{{BASE_URL}}/api',
|
||||
someUrl: '',
|
||||
},
|
||||
envExpansion: false,
|
||||
env: {
|
||||
BASE_URL: 'http://example1.com',
|
||||
NITRO_NOT_API_URL: 'http://example2.com',
|
||||
NUXT_SOME_URL: 'http://example3.com',
|
||||
},
|
||||
expected: {
|
||||
apiUrl: '{{BASE_URL}}/api',
|
||||
someUrl: 'http://example3.com',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
describe('useRuntimeConfig', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
})
|
||||
|
||||
it.each(testCases)('$description', ({ runtimeConfig, envExpansion, env, expected }) => {
|
||||
useNuxt.mockReturnValue({ options: { nitro: { runtimeConfig, experimental: { envExpansion } } } })
|
||||
klona.mockReturnValue(runtimeConfig)
|
||||
Object.entries(env).forEach(([key, value]) => vi.stubEnv(key, value))
|
||||
|
||||
expect(useRuntimeConfig()).toEqual(expected)
|
||||
})
|
||||
})
|
@ -94,7 +94,7 @@ function applyEnv (
|
||||
return obj
|
||||
}
|
||||
|
||||
const envExpandRx = /\{\{(.*?)\}\}/g
|
||||
const envExpandRx = /\{\{([^{}]*)\}\}/g
|
||||
|
||||
function _expandFromEnv (value: string, env: Record<string, any> = process.env) {
|
||||
return value.replace(envExpandRx, (match, key) => {
|
||||
|
@ -1,17 +1,25 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { afterAll, bench, describe } from 'vitest'
|
||||
import { join, normalize } from 'pathe'
|
||||
import { rm } from 'node:fs/promises'
|
||||
import { afterAll, beforeAll, bench, describe } from 'vitest'
|
||||
import { join, normalize, resolve } from 'pathe'
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import { loadNuxt, writeTypes } from '@nuxt/kit'
|
||||
import type { Nuxt } from 'nuxt/schema'
|
||||
|
||||
describe('writeTypes', async () => {
|
||||
describe('writeTypes', () => {
|
||||
const relativeDir = join('../../..', 'test/fixtures/basic-types')
|
||||
const path = withoutTrailingSlash(normalize(fileURLToPath(new URL(relativeDir, import.meta.url))))
|
||||
|
||||
const nuxt = await loadNuxt({ cwd: path })
|
||||
let nuxt: Nuxt
|
||||
|
||||
beforeAll(async () => {
|
||||
nuxt = await loadNuxt({ cwd: path })
|
||||
await rm(resolve(path, '.nuxt'), { recursive: true, force: true })
|
||||
}, 20_000)
|
||||
|
||||
afterAll(async () => {
|
||||
await nuxt.close()
|
||||
})
|
||||
}, 20_000)
|
||||
|
||||
bench('writeTypes in the basic-types fixture', async () => {
|
||||
await writeTypes(nuxt)
|
||||
|
@ -64,9 +64,9 @@
|
||||
"test:attw": "attw --pack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/cli": "^3.20.0",
|
||||
"@nuxt/cli": "^3.21.1",
|
||||
"@nuxt/devalue": "^2.0.2",
|
||||
"@nuxt/devtools": "^1.7.0",
|
||||
"@nuxt/devtools": "^2.0.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/telemetry": "^2.6.4",
|
||||
@ -100,8 +100,8 @@
|
||||
"magic-string": "^0.30.17",
|
||||
"mlly": "^1.7.4",
|
||||
"nanotar": "^0.2.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||
"nypm": "^0.5.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28969273.f7aa9de6",
|
||||
"nypm": "^0.5.2",
|
||||
"ofetch": "^1.4.1",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^2.0.2",
|
||||
@ -109,7 +109,7 @@
|
||||
"pkg-types": "^1.3.1",
|
||||
"radix3": "^1.1.2",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.6.3",
|
||||
"semver": "^7.7.1",
|
||||
"std-env": "^3.8.0",
|
||||
"strip-literal": "^3.0.0",
|
||||
"tinyglobby": "0.2.10",
|
||||
@ -136,8 +136,8 @@
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vue/compiler-sfc": "3.5.13",
|
||||
"unbuild": "3.3.1",
|
||||
"vite": "6.0.11",
|
||||
"vitest": "3.0.4"
|
||||
"vite": "6.1.0",
|
||||
"vitest": "3.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@parcel/watcher": "^2.1.0",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { DefineComponent, MaybeRef, VNode } from 'vue'
|
||||
import type { DefineComponent, ExtractPublicPropTypes, MaybeRef, PropType, VNode } from 'vue'
|
||||
import { Suspense, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, ref, unref } from 'vue'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
@ -30,19 +30,23 @@ const LayoutLoader = defineComponent({
|
||||
},
|
||||
})
|
||||
|
||||
// props are moved outside of defineComponent to later explicitly assert the prop types
|
||||
// this avoids type loss/simplification resulting in things like MaybeRef<string | false>, keeping type hints for layout names
|
||||
const nuxtLayoutProps = {
|
||||
name: {
|
||||
type: [String, Boolean, Object] as PropType<unknown extends PageMeta['layout'] ? MaybeRef<string | false> : PageMeta['layout']>,
|
||||
default: null,
|
||||
},
|
||||
fallback: {
|
||||
type: [String, Object] as PropType<unknown extends PageMeta['layout'] ? MaybeRef<string> : PageMeta['layout']>,
|
||||
default: null,
|
||||
},
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NuxtLayout',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
name: {
|
||||
type: [String, Boolean, Object] as unknown as () => unknown extends PageMeta['layout'] ? MaybeRef<string | false> : PageMeta['layout'],
|
||||
default: null,
|
||||
},
|
||||
fallback: {
|
||||
type: [String, Object] as unknown as () => unknown extends PageMeta['layout'] ? MaybeRef<string> : PageMeta['layout'],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
props: nuxtLayoutProps,
|
||||
setup (props, context) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
// Need to ensure (if we are not a child of `<NuxtPage>`) that we use synchronous route (not deferred)
|
||||
@ -95,9 +99,7 @@ export default defineComponent({
|
||||
}).default()
|
||||
}
|
||||
},
|
||||
}) as unknown as DefineComponent<{
|
||||
name?: (unknown extends PageMeta['layout'] ? MaybeRef<string | false> : PageMeta['layout']) | undefined
|
||||
}>
|
||||
}) as DefineComponent<ExtractPublicPropTypes<typeof nuxtLayoutProps>>
|
||||
|
||||
const LayoutProvider = defineComponent({
|
||||
name: 'NuxtLayoutProvider',
|
||||
|
@ -3,7 +3,8 @@ import type { Ref, VNode } from 'vue'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { PageRouteSymbol } from './injections'
|
||||
|
||||
export const RouteProvider = defineComponent({
|
||||
export const defineRouteProvider = (name = 'RouteProvider') => defineComponent({
|
||||
name,
|
||||
props: {
|
||||
vnode: {
|
||||
type: Object as () => VNode,
|
||||
@ -55,3 +56,5 @@ export const RouteProvider = defineComponent({
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const RouteProvider = defineRouteProvider()
|
||||
|
@ -18,7 +18,7 @@ export const useRouter: typeof _useRouter = () => {
|
||||
|
||||
/** @since 3.0.0 */
|
||||
export const useRoute: typeof _useRoute = () => {
|
||||
if (import.meta.dev && isProcessingMiddleware()) {
|
||||
if (import.meta.dev && !getCurrentInstance() && isProcessingMiddleware()) {
|
||||
console.warn('[nuxt] Calling `useRoute` within middleware may lead to misleading results. Instead, use the (to, from) arguments passed to the middleware to access the new and old routes.')
|
||||
}
|
||||
if (hasInjectionContext()) {
|
||||
|
@ -8,7 +8,7 @@ export { defineNuxtLink } from './components/index'
|
||||
export type { NuxtLinkOptions, NuxtLinkProps } from './components/index'
|
||||
export { _getAppConfig, updateAppConfig, useAppConfig } from './config'
|
||||
export { cancelIdleCallback, requestIdleCallback } from './compat/idle-callback'
|
||||
export type { NuxtAppLiterals, NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext, PageMeta } from './types'
|
||||
export type { NuxtAppLiterals, NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext, PageMeta, NuxtPageProps } from './types'
|
||||
|
||||
export const isVue2 = false
|
||||
export const isVue3 = true
|
||||
|
@ -2,7 +2,7 @@ import { createDebugger } from 'hookable'
|
||||
import { defineNuxtPlugin } from '../nuxt'
|
||||
|
||||
export default defineNuxtPlugin({
|
||||
name: 'nuxt:debug',
|
||||
name: 'nuxt:debug:hooks',
|
||||
enforce: 'pre',
|
||||
setup (nuxtApp) {
|
||||
createDebugger(nuxtApp.hooks, { tag: 'nuxt-app' })
|
@ -1,4 +1,4 @@
|
||||
export type { PageMeta } from '../pages/runtime/index'
|
||||
export type { PageMeta, NuxtPageProps } from '../pages/runtime/index'
|
||||
|
||||
export interface NuxtAppLiterals {
|
||||
[key: string]: string
|
||||
|
@ -88,7 +88,7 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
||||
const perf = performance.now() - start
|
||||
const setupTime = Math.round((perf * 100)) / 100
|
||||
|
||||
if (nuxt.options.debug || setupTime > 500) {
|
||||
if ((nuxt.options.debug && nuxt.options.debug.templates) || setupTime > 500) {
|
||||
logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`)
|
||||
}
|
||||
|
||||
|
@ -108,6 +108,18 @@ function createWatcher () {
|
||||
ignored: [isIgnored, /[\\/]node_modules[\\/]/],
|
||||
})
|
||||
|
||||
const restartPaths = new Set<string>()
|
||||
const srcDir = nuxt.options.srcDir.replace(/\/?$/, '/')
|
||||
for (const pattern of nuxt.options.watch) {
|
||||
if (typeof pattern !== 'string') { continue }
|
||||
const path = resolve(nuxt.options.srcDir, pattern)
|
||||
if (!path.startsWith(srcDir)) {
|
||||
restartPaths.add(path)
|
||||
}
|
||||
}
|
||||
|
||||
watcher.add([...restartPaths])
|
||||
|
||||
watcher.on('all', (event, path) => {
|
||||
if (event === 'all' || event === 'ready' || event === 'error' || event === 'raw') {
|
||||
return
|
||||
@ -121,7 +133,7 @@ function createGranularWatcher () {
|
||||
const nuxt = useNuxt()
|
||||
const isIgnored = createIsIgnored(nuxt)
|
||||
|
||||
if (nuxt.options.debug) {
|
||||
if (nuxt.options.debug && nuxt.options.debug.watchers) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.time('[nuxt] builder:chokidar:watch')
|
||||
}
|
||||
@ -166,7 +178,7 @@ function createGranularWatcher () {
|
||||
})
|
||||
watcher.on('ready', () => {
|
||||
pending--
|
||||
if (nuxt.options.debug && !pending) {
|
||||
if (nuxt.options.debug && nuxt.options.debug.watchers && !pending) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.timeEnd('[nuxt] builder:chokidar:watch')
|
||||
}
|
||||
@ -177,7 +189,7 @@ function createGranularWatcher () {
|
||||
|
||||
async function createParcelWatcher () {
|
||||
const nuxt = useNuxt()
|
||||
if (nuxt.options.debug) {
|
||||
if (nuxt.options.debug && nuxt.options.debug.watchers) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.time('[nuxt] builder:parcel:watch')
|
||||
}
|
||||
@ -203,7 +215,7 @@ async function createParcelWatcher () {
|
||||
],
|
||||
})
|
||||
watcher.then((subscription) => {
|
||||
if (nuxt.options.debug) {
|
||||
if (nuxt.options.debug && nuxt.options.debug.watchers) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.timeEnd('[nuxt] builder:parcel:watch')
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
const isNuxtV4 = nuxt.options.future?.compatibilityVersion === 4
|
||||
|
||||
const nitroConfig: NitroConfig = defu(nuxt.options.nitro, {
|
||||
debug: nuxt.options.debug,
|
||||
debug: nuxt.options.debug ? nuxt.options.debug.nitro : false,
|
||||
rootDir: nuxt.options.rootDir,
|
||||
workspaceDir: nuxt.options.workspaceDir,
|
||||
srcDir: nuxt.options.serverDir,
|
||||
|
@ -83,7 +83,6 @@ const nightlies = {
|
||||
|
||||
export const keyDependencies = [
|
||||
'@nuxt/kit',
|
||||
'@nuxt/schema',
|
||||
]
|
||||
|
||||
let warnedAboutCompatDate = false
|
||||
@ -414,7 +413,7 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
await nuxt.callHook('modules:before')
|
||||
const modulesToInstall = new Map<string | NuxtModule, Record<string, any>>()
|
||||
|
||||
const watchedPaths = new Set<string>()
|
||||
const modulePaths = new Set<string>()
|
||||
const specifiedModules = new Set<string>()
|
||||
|
||||
for (const _mod of nuxt.options.modules) {
|
||||
@ -432,13 +431,15 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
`${modulesDir}/*/index{${nuxt.options.extensions.join(',')}}`,
|
||||
])
|
||||
for (const mod of layerModules) {
|
||||
watchedPaths.add(mod)
|
||||
modulePaths.add(mod)
|
||||
if (specifiedModules.has(mod)) { continue }
|
||||
specifiedModules.add(mod)
|
||||
modulesToInstall.set(mod, {})
|
||||
}
|
||||
}
|
||||
|
||||
nuxt.options.watch.push(...modulePaths)
|
||||
|
||||
// Register user and then ad-hoc modules
|
||||
for (const key of ['modules', '_modules'] as const) {
|
||||
for (const item of nuxt.options[key as 'modules']) {
|
||||
@ -554,9 +555,13 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/preload.server'))
|
||||
}
|
||||
|
||||
// Add nuxt app debugger
|
||||
if (nuxt.options.debug) {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/debug'))
|
||||
// Add nuxt app hooks debugger
|
||||
if (
|
||||
nuxt.options.debug
|
||||
&& nuxt.options.debug.hooks
|
||||
&& (nuxt.options.debug.hooks === true || nuxt.options.debug.hooks.client)
|
||||
) {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/debug-hooks'))
|
||||
}
|
||||
|
||||
// Add experimental Chrome devtools timings support
|
||||
@ -661,7 +666,7 @@ export default defineNuxtPlugin({
|
||||
nuxt.hooks.hook('builder:watch', (event, relativePath) => {
|
||||
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||
// Local module patterns
|
||||
if (watchedPaths.has(path)) {
|
||||
if (modulePaths.has(path)) {
|
||||
return nuxt.callHook('restart', { hard: true })
|
||||
}
|
||||
|
||||
@ -739,7 +744,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
||||
: options.devtools?.enabled !== false // enabled by default unless explicitly disabled
|
||||
|
||||
if (isDevToolsEnabled) {
|
||||
if (!options._modules.some(m => m === '@nuxt/devtools' || m === '@nuxt/devtools-edge')) {
|
||||
if (!options._modules.some(m => m === '@nuxt/devtools' || m === '@nuxt/devtools-nightly' || m === '@nuxt/devtools-edge')) {
|
||||
options._modules.push('@nuxt/devtools')
|
||||
}
|
||||
}
|
||||
@ -815,7 +820,11 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
||||
nuxt.hooks.addHooks(opts.overrides.hooks)
|
||||
}
|
||||
|
||||
if (nuxt.options.debug) {
|
||||
if (
|
||||
nuxt.options.debug
|
||||
&& nuxt.options.debug.hooks
|
||||
&& (nuxt.options.debug.hooks === true || nuxt.options.debug.hooks.server)
|
||||
) {
|
||||
createDebugger(nuxt.hooks, { tag: 'nuxt' })
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,18 @@ export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
|
||||
name: 'nuxt:resolve-bare-imports',
|
||||
enforce: 'post',
|
||||
configResolved (config) {
|
||||
conditions = config.mode === 'test' ? [...config.resolve.conditions, 'import', 'require'] : config.resolve.conditions
|
||||
const resolvedConditions = new Set([nuxt.options.dev ? 'development' : 'production', ...config.resolve.conditions])
|
||||
if (resolvedConditions.has('browser')) {
|
||||
resolvedConditions.add('web')
|
||||
resolvedConditions.add('import')
|
||||
resolvedConditions.add('module')
|
||||
resolvedConditions.add('default')
|
||||
}
|
||||
if (config.mode === 'test') {
|
||||
resolvedConditions.add('import')
|
||||
resolvedConditions.add('require')
|
||||
}
|
||||
conditions = [...resolvedConditions]
|
||||
},
|
||||
async resolveId (id, importer) {
|
||||
if (!importer || isAbsolute(id) || (!isAbsolute(importer) && !importer.startsWith('virtual:') && !importer.startsWith('\0virtual:')) || exclude.some(e => id.startsWith(e))) {
|
||||
|
@ -674,7 +674,7 @@ function getServerComponentHTML (body: string): string {
|
||||
|
||||
const SSR_SLOT_TELEPORT_MARKER = /^uid=([^;]*);slot=(.*)$/
|
||||
const SSR_CLIENT_TELEPORT_MARKER = /^uid=([^;]*);client=(.*)$/
|
||||
const SSR_CLIENT_SLOT_MARKER = /^island-slot=[^;]*;(.*)$/
|
||||
const SSR_CLIENT_SLOT_MARKER = /^island-slot=([^;]*);(.*)$/
|
||||
|
||||
function getSlotIslandResponse (ssrContext: NuxtSSRContext): NuxtIslandResponse['slots'] {
|
||||
if (!ssrContext.islandContext || !Object.keys(ssrContext.islandContext.slots).length) { return undefined }
|
||||
@ -698,21 +698,21 @@ function getClientIslandResponse (ssrContext: NuxtSSRContext): NuxtIslandRespons
|
||||
response[clientUid] = {
|
||||
...component,
|
||||
html,
|
||||
slots: getComponentSlotTeleport(ssrContext.teleports ?? {}),
|
||||
slots: getComponentSlotTeleport(clientUid, ssrContext.teleports ?? {}),
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
function getComponentSlotTeleport (teleports: Record<string, string>) {
|
||||
function getComponentSlotTeleport (clientUid: string, teleports: Record<string, string>) {
|
||||
const entries = Object.entries(teleports)
|
||||
const slots: Record<string, string> = {}
|
||||
|
||||
for (const [key, value] of entries) {
|
||||
const match = key.match(SSR_CLIENT_SLOT_MARKER)
|
||||
if (match) {
|
||||
const [, slot] = match
|
||||
if (!slot) { continue }
|
||||
const [, id, slot] = match
|
||||
if (!slot || clientUid !== id) { continue }
|
||||
slots[slot] = value
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ export default defineNuxtModule({
|
||||
const options: TypedRouterOptions = {
|
||||
routesFolder: [],
|
||||
dts: resolve(nuxt.options.buildDir, declarationFile),
|
||||
logs: nuxt.options.debug,
|
||||
logs: nuxt.options.debug && nuxt.options.debug.router,
|
||||
async beforeWriteFiles (rootPage) {
|
||||
rootPage.children.forEach(child => child.delete())
|
||||
const pages = nuxt.apps.default?.pages || await resolvePagesRoutes(nuxt)
|
||||
|
@ -1,2 +1,3 @@
|
||||
export { definePageMeta, defineRouteRules } from './composables'
|
||||
export type { PageMeta } from './composables'
|
||||
export type { NuxtPageProps } from './page'
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Fragment, Suspense, defineComponent, h, inject, nextTick, ref, watch } from 'vue'
|
||||
import type { KeepAliveProps, TransitionProps, VNode } from 'vue'
|
||||
import type { AllowedComponentProps, Component, ComponentCustomProps, ComponentPublicInstance, KeepAliveProps, Slot, TransitionProps, VNode, VNodeProps } from 'vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { defu } from 'defu'
|
||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouterViewProps } from 'vue-router'
|
||||
|
||||
import { generateRouteKey, toArray, wrapInKeepAlive } from './utils'
|
||||
import type { RouterViewSlotProps } from './utils'
|
||||
import { RouteProvider } from '#app/components/route-provider'
|
||||
import { RouteProvider, defineRouteProvider } from '#app/components/route-provider'
|
||||
import { useNuxtApp } from '#app/nuxt'
|
||||
import { useRouter } from '#app/composables/router'
|
||||
import { _wrapInTransition } from '#app/components/utils'
|
||||
@ -14,6 +14,23 @@ import { LayoutMetaSymbol, PageRouteSymbol } from '#app/components/injections'
|
||||
// @ts-expect-error virtual file
|
||||
import { appKeepalive as defaultKeepaliveConfig, appPageTransition as defaultPageTransition } from '#build/nuxt.config.mjs'
|
||||
|
||||
export interface NuxtPageProps extends RouterViewProps {
|
||||
/**
|
||||
* Define global transitions for all pages rendered with the `NuxtPage` component.
|
||||
*/
|
||||
transition?: boolean | TransitionProps
|
||||
|
||||
/**
|
||||
* Control state preservation of pages rendered with the `NuxtPage` component.
|
||||
*/
|
||||
keepalive?: boolean | KeepAliveProps
|
||||
|
||||
/**
|
||||
* Control when the `NuxtPage` component is re-rendered.
|
||||
*/
|
||||
pageKey?: string | ((route: RouteLocationNormalizedLoaded) => string)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NuxtPage',
|
||||
inheritAttrs: false,
|
||||
@ -66,6 +83,9 @@ export default defineComponent({
|
||||
nuxtApp._isNuxtPageUsed = true
|
||||
}
|
||||
let pageLoadingEndHookAlreadyCalled = false
|
||||
|
||||
const routerProviderLookup = new WeakMap<Component, ReturnType<typeof defineRouteProvider> | undefined>()
|
||||
|
||||
return () => {
|
||||
return h(RouterView, { name: props.name, route: props.route, ...attrs }, {
|
||||
default: (routeProps: RouterViewSlotProps) => {
|
||||
@ -111,7 +131,7 @@ export default defineComponent({
|
||||
default: () => {
|
||||
const providerVNode = h(RouteProvider, {
|
||||
key: key || undefined,
|
||||
vnode: slots.default ? h(Fragment, undefined, slots.default(routeProps)) : routeProps.Component,
|
||||
vnode: slots.default ? normalizeSlot(slots.default, routeProps) : routeProps.Component,
|
||||
route: routeProps.route,
|
||||
renderKey: key || undefined,
|
||||
vnodeRef: pageRef,
|
||||
@ -124,7 +144,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// Client side rendering
|
||||
|
||||
const hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition)
|
||||
const transitionProps = hasTransition && _mergeTransitionProps([
|
||||
props.transition,
|
||||
@ -148,18 +167,28 @@ export default defineComponent({
|
||||
},
|
||||
}, {
|
||||
default: () => {
|
||||
const providerVNode = h(RouteProvider, {
|
||||
const routeProviderProps = {
|
||||
key: key || undefined,
|
||||
vnode: slots.default ? h(Fragment, undefined, slots.default(routeProps)) : routeProps.Component,
|
||||
vnode: slots.default ? normalizeSlot(slots.default, routeProps) : routeProps.Component,
|
||||
route: routeProps.route,
|
||||
renderKey: key || undefined,
|
||||
trackRootNodes: hasTransition,
|
||||
vnodeRef: pageRef,
|
||||
})
|
||||
if (keepaliveConfig) {
|
||||
(providerVNode.type as any).name = (routeProps.Component.type as any).name || (routeProps.Component.type as any).__name || 'RouteProvider'
|
||||
}
|
||||
return providerVNode
|
||||
|
||||
if (!keepaliveConfig) {
|
||||
return h(RouteProvider, routeProviderProps)
|
||||
}
|
||||
|
||||
const routerComponentType = routeProps.Component.type as any
|
||||
let PageRouteProvider = routerProviderLookup.get(routerComponentType)
|
||||
|
||||
if (!PageRouteProvider) {
|
||||
PageRouteProvider = defineRouteProvider(routerComponentType.name || routerComponentType.__name)
|
||||
routerProviderLookup.set(routerComponentType, PageRouteProvider)
|
||||
}
|
||||
|
||||
return h(PageRouteProvider, routeProviderProps)
|
||||
},
|
||||
}),
|
||||
)).default()
|
||||
@ -169,7 +198,24 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}) as unknown as {
|
||||
new(): {
|
||||
$props: AllowedComponentProps &
|
||||
ComponentCustomProps &
|
||||
VNodeProps &
|
||||
NuxtPageProps
|
||||
|
||||
$slots: {
|
||||
default?: (routeProps: RouterViewSlotProps) => VNode[]
|
||||
}
|
||||
|
||||
// expose
|
||||
/**
|
||||
* Reference to the page component instance
|
||||
*/
|
||||
pageRef: Element | ComponentPublicInstance | null
|
||||
}
|
||||
}
|
||||
|
||||
function _mergeTransitionProps (routeProps: TransitionProps[]): TransitionProps {
|
||||
const _props: TransitionProps[] = routeProps.map(prop => ({
|
||||
@ -198,3 +244,8 @@ function hasChildrenRoutes (fork: RouteLocationNormalizedLoaded | null, newRoute
|
||||
const index = newRoute.matched.findIndex(m => m.components?.default === Component?.type)
|
||||
return index < newRoute.matched.length - 1
|
||||
}
|
||||
|
||||
function normalizeSlot (slot: Slot, data: RouterViewSlotProps) {
|
||||
const slotContent = slot(data)
|
||||
return slotContent.length === 1 ? h(slotContent[0]!) : h(Fragment, undefined, slotContent)
|
||||
}
|
||||
|
33
packages/nuxt/test/build.bench.ts
Normal file
33
packages/nuxt/test/build.bench.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { rm } from 'node:fs/promises'
|
||||
import { beforeAll, bench, describe } from 'vitest'
|
||||
import { join, normalize } from 'pathe'
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import { build, loadNuxt } from 'nuxt'
|
||||
|
||||
const basicTestFixtureDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../test/fixtures/basic', import.meta.url))))
|
||||
|
||||
describe('build', () => {
|
||||
beforeAll(async () => {
|
||||
await rm(join(basicTestFixtureDir, '.nuxt'), { recursive: true, force: true })
|
||||
})
|
||||
|
||||
bench('initial dev server build in the basic test fixture', async () => {
|
||||
const nuxt = await loadNuxt({
|
||||
cwd: basicTestFixtureDir,
|
||||
ready: true,
|
||||
overrides: {
|
||||
dev: true,
|
||||
sourcemap: false,
|
||||
builder: {
|
||||
bundle: () => Promise.resolve(),
|
||||
},
|
||||
},
|
||||
})
|
||||
await new Promise<void>((resolve) => {
|
||||
nuxt.hook('build:done', () => resolve())
|
||||
build(nuxt)
|
||||
})
|
||||
await nuxt.close()
|
||||
})
|
||||
})
|
@ -57,8 +57,8 @@ describe('islandTransform - server and island components', () => {
|
||||
<script setup lang="ts">
|
||||
const someData = 'some data'
|
||||
|
||||
</script>`
|
||||
, 'hello.server.vue')
|
||||
</script>`,
|
||||
'hello.server.vue')
|
||||
|
||||
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
||||
"<template>
|
||||
@ -130,8 +130,8 @@ withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), {
|
||||
<script setup lang="ts">
|
||||
const someData = 'some data'
|
||||
|
||||
</script>`
|
||||
, 'hello.server.vue')
|
||||
</script>`,
|
||||
'hello.server.vue')
|
||||
|
||||
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
||||
"<template>
|
||||
@ -182,8 +182,8 @@ withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), {
|
||||
|
||||
const message = "Hello World";
|
||||
</script>
|
||||
`
|
||||
, 'hello.server.vue')
|
||||
`,
|
||||
'hello.server.vue')
|
||||
|
||||
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
||||
"<template>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { bench, describe } from 'vitest'
|
||||
import { normalize } from 'pathe'
|
||||
import { rm } from 'node:fs/promises'
|
||||
import { beforeAll, bench, describe } from 'vitest'
|
||||
import { join, normalize } from 'pathe'
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import { loadNuxt } from 'nuxt'
|
||||
|
||||
@ -8,6 +9,13 @@ const emptyDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../
|
||||
const basicTestFixtureDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../test/fixtures/basic', import.meta.url))))
|
||||
|
||||
describe('loadNuxt', () => {
|
||||
beforeAll(async () => {
|
||||
await Promise.all([
|
||||
rm(join(emptyDir, '.nuxt'), { recursive: true, force: true }),
|
||||
rm(join(basicTestFixtureDir, '.nuxt'), { recursive: true, force: true }),
|
||||
])
|
||||
})
|
||||
|
||||
bench('loadNuxt in an empty directory', async () => {
|
||||
const nuxt = await loadNuxt({
|
||||
cwd: emptyDir,
|
||||
|
@ -75,7 +75,7 @@
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
"rollup": "4.32.0",
|
||||
"rollup": "4.34.4",
|
||||
"unbuild": "3.3.1",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
|
@ -51,7 +51,7 @@
|
||||
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||
"hookable": "5.5.3",
|
||||
"ignore": "7.0.3",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28969273.f7aa9de6",
|
||||
"ofetch": "1.4.1",
|
||||
"pkg-types": "1.3.1",
|
||||
"sass-loader": "16.0.4",
|
||||
@ -60,7 +60,7 @@
|
||||
"unctx": "2.4.1",
|
||||
"unimport": "4.0.0",
|
||||
"untyped": "1.5.2",
|
||||
"vite": "6.0.11",
|
||||
"vite": "6.1.0",
|
||||
"vue": "3.5.13",
|
||||
"vue-bundle-renderer": "2.1.1",
|
||||
"vue-loader": "17.4.2",
|
||||
|
@ -314,7 +314,7 @@ export default defineUntypedSchema({
|
||||
* animation: loader 400ms linear infinite;
|
||||
* }
|
||||
*
|
||||
* \@-webkit-keyframes loader {
|
||||
* @-webkit-keyframes loader {
|
||||
* 0% {
|
||||
* -webkit-transform: translate(-50%, -50%) rotate(0deg);
|
||||
* }
|
||||
@ -322,7 +322,7 @@ export default defineUntypedSchema({
|
||||
* -webkit-transform: translate(-50%, -50%) rotate(360deg);
|
||||
* }
|
||||
* }
|
||||
* \@keyframes loader {
|
||||
* @keyframes loader {
|
||||
* 0% {
|
||||
* transform: translate(-50%, -50%) rotate(0deg);
|
||||
* }
|
||||
|
@ -8,6 +8,7 @@ import { defu } from 'defu'
|
||||
import { findWorkspaceDir } from 'pkg-types'
|
||||
|
||||
import type { RuntimeConfig } from '../types/config'
|
||||
import type { NuxtDebugOptions } from '../types/debug'
|
||||
|
||||
export default defineUntypedSchema({
|
||||
/**
|
||||
@ -264,9 +265,32 @@ export default defineUntypedSchema({
|
||||
* At the moment, it prints out hook names and timings on the server, and
|
||||
* logs hook arguments as well in the browser.
|
||||
*
|
||||
* You can also set this to an object to enable specific debug options.
|
||||
*
|
||||
* @type {boolean | (typeof import('../src/types/debug').NuxtDebugOptions) | undefined}
|
||||
*/
|
||||
debug: {
|
||||
$resolve: val => val ?? isDebug,
|
||||
$resolve: (val: boolean | NuxtDebugOptions | undefined) => {
|
||||
val ??= isDebug
|
||||
if (val === false) {
|
||||
return val
|
||||
}
|
||||
if (val === true) {
|
||||
return {
|
||||
templates: true,
|
||||
modules: true,
|
||||
watchers: true,
|
||||
hooks: {
|
||||
client: true,
|
||||
server: true,
|
||||
},
|
||||
nitro: true,
|
||||
router: true,
|
||||
hydration: true,
|
||||
} satisfies Required<NuxtDebugOptions>
|
||||
}
|
||||
return val
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@ import { consola } from 'consola'
|
||||
import { resolve } from 'pathe'
|
||||
import { isTest } from 'std-env'
|
||||
import { defineUntypedSchema } from 'untyped'
|
||||
import type { NuxtDebugOptions } from '../types/debug'
|
||||
|
||||
export default defineUntypedSchema({
|
||||
/**
|
||||
@ -20,9 +21,10 @@ export default defineUntypedSchema({
|
||||
},
|
||||
define: {
|
||||
$resolve: async (val: Record<string, any> | undefined, get) => {
|
||||
const [isDev, isDebug] = await Promise.all([get('dev'), get('debug')]) as [boolean, boolean]
|
||||
const [isDev, debug] = await Promise.all([get('dev'), get('debug')]) as [boolean, boolean | NuxtDebugOptions]
|
||||
|
||||
return {
|
||||
'__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': isDebug,
|
||||
'__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': Boolean(debug && (debug === true || debug.hydration)),
|
||||
'process.dev': isDev,
|
||||
'import.meta.dev': isDev,
|
||||
'process.test': isTest,
|
||||
|
@ -8,6 +8,7 @@ export type { AppHeadMetaObject, MetaObject, MetaObjectRaw, HeadAugmentations }
|
||||
export type { ModuleDefinition, ModuleMeta, ModuleOptions, ModuleSetupInstallResult, ModuleSetupReturn, NuxtModule, ResolvedModuleOptions } from './types/module'
|
||||
export type { Nuxt, NuxtApp, NuxtPlugin, NuxtPluginTemplate, NuxtTemplate, NuxtTypeTemplate, NuxtServerTemplate, ResolvedNuxtTemplate } from './types/nuxt'
|
||||
export type { RouterConfig, RouterConfigSerializable, RouterOptions } from './types/router'
|
||||
export type { NuxtDebugOptions } from './types/debug'
|
||||
|
||||
// Schema
|
||||
export { default as NuxtConfigSchema } from './config/index'
|
||||
|
@ -76,9 +76,10 @@ export interface NuxtBuilder {
|
||||
}
|
||||
|
||||
// Normalized Nuxt options available as `nuxt.options.*`
|
||||
export interface NuxtOptions extends Omit<ConfigSchema, 'vue' | 'sourcemap' | 'builder' | 'postcss' | 'webpack'> {
|
||||
export interface NuxtOptions extends Omit<ConfigSchema, 'vue' | 'sourcemap' | 'debug' | 'builder' | 'postcss' | 'webpack'> {
|
||||
vue: Omit<ConfigSchema['vue'], 'config'> & { config?: Partial<Filter<VueAppConfig, string | boolean>> }
|
||||
sourcemap: Required<Exclude<ConfigSchema['sourcemap'], boolean>>
|
||||
debug: Required<Exclude<ConfigSchema['debug'], true>>
|
||||
builder: '@nuxt/vite-builder' | '@nuxt/webpack-builder' | '@nuxt/rspack-builder' | NuxtBuilder
|
||||
postcss: Omit<ConfigSchema['postcss'], 'order'> & { order: Exclude<ConfigSchema['postcss']['order'], string> }
|
||||
webpack: ConfigSchema['webpack'] & {
|
||||
|
21
packages/schema/src/types/debug.ts
Normal file
21
packages/schema/src/types/debug.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type { NitroOptions } from 'nitro/types'
|
||||
|
||||
export interface NuxtDebugOptions {
|
||||
/** Debug for Nuxt templates */
|
||||
templates?: boolean
|
||||
/** Debug for modules setup timings */
|
||||
modules?: boolean
|
||||
/** Debug for file watchers */
|
||||
watchers?: boolean
|
||||
/** Debug options for Nitro */
|
||||
nitro?: NitroOptions['debug']
|
||||
/** Debug for production hydration mismatch */
|
||||
hydration?: boolean
|
||||
/** Debug for Vue Router */
|
||||
router?: boolean
|
||||
/** Debug for hooks, can be set to `true` or an object with `server` and `client` keys */
|
||||
hooks?: boolean | {
|
||||
server?: boolean
|
||||
client?: boolean
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
"devDependencies": {
|
||||
"@unocss/reset": "65.4.3",
|
||||
"beasties": "0.2.0",
|
||||
"html-validate": "9.1.3",
|
||||
"html-validate": "9.2.1",
|
||||
"htmlnano": "2.1.1",
|
||||
"jiti": "2.4.2",
|
||||
"knitwork": "1.2.0",
|
||||
@ -30,6 +30,6 @@
|
||||
"tinyexec": "0.3.2",
|
||||
"tinyglobby": "0.2.10",
|
||||
"unocss": "65.4.3",
|
||||
"vite": "6.0.11"
|
||||
"vite": "6.1.0"
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@
|
||||
<h2 class="font-semibold text-base mt-1">Modules</h2>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-200 group-hover:dark:text-gray-100">Discover our list of modules to supercharge your Nuxt project.</p>
|
||||
</a>
|
||||
<a href="https://nuxt.com/modules?utm_source=nuxt-welcome" target="_blank" class="relative flex flex-col gap-1 border p-6 rounded-lg border-gray-200 dark:border-white/10 dark:bg-white/5 bg-gray-50/10 group hover:dark:border-[#00DC82] hover:border-[#00DC82] transition-all">
|
||||
<a href="https://nuxt.com/docs/examples?utm_source=nuxt-welcome" target="_blank" class="relative flex flex-col gap-1 border p-6 rounded-lg border-gray-200 dark:border-white/10 dark:bg-white/5 bg-gray-50/10 group hover:dark:border-[#00DC82] hover:border-[#00DC82] transition-all">
|
||||
<div class="w-[32px] h-[32px] bg-[#00DC82]/5 flex items-center justify-center border rounded border-[#00DC82] transition-all dark:border-[#00DC82]/50 group-hover:dark:border-[#00DC82]/80 dark:bg-[#020420] text-[#00DC82] dark:text-[#00DC82]">
|
||||
<svg class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M224 56v144a8 8 0 0 1-8 8H40a8 8 0 0 1-8-8V56a8 8 0 0 1 8-8h176a8 8 0 0 1 8 8Z" opacity=".2"/><path fill="currentColor" d="M216 40H40a16 16 0 0 0-16 16v144a16 16 0 0 0 16 16h176a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16Zm0 160H40V56h176v144ZM80 84a12 12 0 1 1-12-12a12 12 0 0 1 12 12Zm40 0a12 12 0 1 1-12-12a12 12 0 0 1 12 12Z"/></svg>
|
||||
</div>
|
||||
@ -93,6 +93,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584l-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"/></svg>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://go.nuxt.com/bluesky"
|
||||
target="_blank"
|
||||
class="focus-visible:ring-2 text-gray-500 hover:text-[#020420] dark:text-gray-400 dark:hover:text-white"
|
||||
>
|
||||
<span class="sr-only">Nuxt Bluesky</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565C.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479c.815 2.736 3.713 3.66 6.383 3.364q.204-.03.415-.056q-.207.033-.415.056c-3.912.58-7.387 2.005-2.83 7.078c5.013 5.19 6.87-1.113 7.823-4.308c.953 3.195 2.05 9.271 7.733 4.308c4.267-4.308 1.172-6.498-2.74-7.078a9 9 0 0 1-.415-.056q.21.026.415.056c2.67.297 5.568-.628 6.383-3.364c.246-.828.624-5.79.624-6.478c0-.69-.139-1.861-.902-2.206c-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8"/></svg>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://go.nuxt.com/linkedin"
|
||||
|
@ -26,7 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"rollup": "4.32.0",
|
||||
"rollup": "4.34.4",
|
||||
"unbuild": "3.3.1",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
@ -41,6 +41,7 @@
|
||||
"defu": "^6.1.4",
|
||||
"esbuild": "^0.24.2",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"externality": "^1.0.2",
|
||||
"get-port-please": "^3.1.2",
|
||||
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||
"jiti": "^2.4.2",
|
||||
@ -50,13 +51,13 @@
|
||||
"pathe": "^2.0.2",
|
||||
"pkg-types": "^1.3.1",
|
||||
"postcss": "^8.5.1",
|
||||
"rollup-plugin-visualizer": "^5.13.1",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"std-env": "^3.8.0",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^2.1.2",
|
||||
"vite": "^6.0.11",
|
||||
"vite-node": "^3.0.4",
|
||||
"vite": "^6.1.0",
|
||||
"vite-node": "^3.0.5",
|
||||
"vite-plugin-checker": "^0.8.0",
|
||||
"vue-bundle-renderer": "^2.1.1"
|
||||
},
|
||||
|
@ -131,19 +131,6 @@ export async function buildClient (ctx: ViteBuildContext) {
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: 'nuxt:import-conditions',
|
||||
enforce: 'post',
|
||||
config (_config, env) {
|
||||
if (env.mode !== 'test') {
|
||||
return {
|
||||
resolve: {
|
||||
conditions: [ctx.nuxt.options.dev ? 'development' : 'production', 'web', 'browser', 'import', 'module', 'default'],
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
devStyleSSRPlugin({
|
||||
srcDir: ctx.nuxt.options.srcDir,
|
||||
buildAssetsURL: joinURL(ctx.nuxt.options.app.baseURL, ctx.nuxt.options.app.buildAssetsDir),
|
||||
|
36
packages/vite/src/utils/external.ts
Normal file
36
packages/vite/src/utils/external.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import type { ExternalsOptions } from 'externality'
|
||||
import { ExternalsDefaults, isExternal } from 'externality'
|
||||
import type { ViteDevServer } from 'vite'
|
||||
import escapeStringRegexp from 'escape-string-regexp'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import type { Nuxt } from 'nuxt/schema'
|
||||
import { resolve } from 'pathe'
|
||||
import { toArray } from '.'
|
||||
|
||||
export function createIsExternal (viteServer: ViteDevServer, nuxt: Nuxt) {
|
||||
const externalOpts: ExternalsOptions = {
|
||||
inline: [
|
||||
/virtual:/,
|
||||
/\.ts$/,
|
||||
...ExternalsDefaults.inline || [],
|
||||
...(
|
||||
viteServer.config.ssr.noExternal && viteServer.config.ssr.noExternal !== true
|
||||
? toArray(viteServer.config.ssr.noExternal)
|
||||
: []
|
||||
),
|
||||
],
|
||||
external: [
|
||||
'#shared',
|
||||
new RegExp('^' + escapeStringRegexp(withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared)))),
|
||||
...(viteServer.config.ssr.external as string[]) || [],
|
||||
/node_modules/,
|
||||
],
|
||||
resolve: {
|
||||
modules: nuxt.options.modulesDir,
|
||||
type: 'module',
|
||||
extensions: ['.ts', '.js', '.json', '.vue', '.mjs', '.jsx', '.tsx', '.wasm'],
|
||||
},
|
||||
}
|
||||
|
||||
return (id: string) => isExternal(id, nuxt.options.rootDir, externalOpts)
|
||||
}
|
@ -8,9 +8,11 @@ import { isFileServingAllowed } from 'vite'
|
||||
import type { ModuleNode, Plugin as VitePlugin } from 'vite'
|
||||
import { getQuery } from 'ufo'
|
||||
import { normalizeViteManifest } from 'vue-bundle-renderer'
|
||||
import { resolve as resolveModule } from 'mlly'
|
||||
import { distDir } from './dirs'
|
||||
import type { ViteBuildContext } from './vite'
|
||||
import { isCSS } from './utils'
|
||||
import { createIsExternal } from './utils/external'
|
||||
|
||||
// TODO: Remove this in favor of registerViteNodeMiddleware
|
||||
// after Nitropack or h3 allows adding middleware after setup
|
||||
@ -126,6 +128,15 @@ function createViteNodeApp (ctx: ViteBuildContext, invalidates: Set<string> = ne
|
||||
},
|
||||
})
|
||||
|
||||
const isExternal = createIsExternal(viteServer, ctx.nuxt)
|
||||
node.shouldExternalize = async (id: string) => {
|
||||
const result = await isExternal(id)
|
||||
if (result?.external) {
|
||||
return resolveModule(result.id, { url: ctx.nuxt.options.modulesDir }).catch(() => false)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return eventHandler(async (event) => {
|
||||
const moduleId = decodeURI(event.path).substring(1)
|
||||
if (moduleId === '/') {
|
||||
|
@ -85,7 +85,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
||||
// https://github.com/vitejs/vite/tree/main/packages/vite/src/node/build.ts#L464-L478
|
||||
assetFileNames: nuxt.options.dev
|
||||
? undefined
|
||||
: chunk => withoutLeadingSlash(join(nuxt.options.app.buildAssetsDir, `${sanitizeFilePath(filename(chunk.name!))}.[hash].[ext]`)),
|
||||
: chunk => withoutLeadingSlash(join(nuxt.options.app.buildAssetsDir, `${sanitizeFilePath(filename(chunk.names[0]!))}.[hash].[ext]`)),
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
@ -77,7 +77,7 @@
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
"rollup": "4.32.0",
|
||||
"rollup": "4.34.4",
|
||||
"unbuild": "3.3.1",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
|
@ -33,6 +33,6 @@ export function vue (ctx: WebpackConfigContext) {
|
||||
ctx.config.plugins!.push(new webpack.DefinePlugin({
|
||||
'__VUE_OPTIONS_API__': 'true',
|
||||
'__VUE_PROD_DEVTOOLS__': 'false',
|
||||
'__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': ctx.nuxt.options.debug,
|
||||
'__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': ctx.nuxt.options.debug && ctx.nuxt.options.debug.hydration,
|
||||
}))
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import pify from 'pify'
|
||||
import { resolve } from 'pathe'
|
||||
import { defineEventHandler, fromNodeMiddleware, handleCors, setHeader } from 'h3'
|
||||
import { createError, defineEventHandler, fromNodeMiddleware, getRequestHeader, handleCors, setHeader } from 'h3'
|
||||
import type { H3CorsOptions } from 'h3'
|
||||
import type { IncomingMessage, MultiWatching, ServerResponse } from 'webpack-dev-middleware'
|
||||
import webpackDevMiddleware from 'webpack-dev-middleware'
|
||||
@ -146,6 +146,12 @@ function wdmToH3Handler (devMiddleware: webpackDevMiddleware.API<IncomingMessage
|
||||
if (isPreflight) {
|
||||
return null
|
||||
}
|
||||
|
||||
// disallow cross-site requests in no-cors mode
|
||||
if (getRequestHeader(event, 'sec-fetch-mode') === 'no-cors' && getRequestHeader(event, 'sec-fetch-site') === 'cross-site') {
|
||||
throw createError({ statusCode: 403 })
|
||||
}
|
||||
|
||||
setHeader(event, 'Vary', 'Origin')
|
||||
|
||||
event.context.webpack = {
|
||||
|
2211
pnpm-lock.yaml
2211
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1943,6 +1943,7 @@ describe('server components/islands', () => {
|
||||
// test islands mounted client side with slot
|
||||
await page.locator('#show-island').click()
|
||||
expect(await page.locator('#island-mounted-client-side').innerHTML()).toContain('Interactive testing slot post SSR')
|
||||
expect(await page.locator('#island-mounted-client-side').innerHTML()).toContain('Sugar Counter')
|
||||
|
||||
// test islands wrapped with client-only
|
||||
expect(await page.locator('#wrapped-client-only').innerHTML()).toContain('Was router enabled')
|
||||
|
@ -23,8 +23,8 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const [clientStats, clientStatsInlined] = await Promise.all((['.output', '.output-inline'])
|
||||
.map(outputDir => analyzeSizes(['**/*.js'], join(rootDir, outputDir, 'public'))))
|
||||
|
||||
expect.soft(roundToKilobytes(clientStats!.totalBytes)).toMatchInlineSnapshot(`"116k"`)
|
||||
expect.soft(roundToKilobytes(clientStatsInlined!.totalBytes)).toMatchInlineSnapshot(`"116k"`)
|
||||
expect.soft(roundToKilobytes(clientStats!.totalBytes)).toMatchInlineSnapshot(`"115k"`)
|
||||
expect.soft(roundToKilobytes(clientStatsInlined!.totalBytes)).toMatchInlineSnapshot(`"115k"`)
|
||||
|
||||
const files = new Set([...clientStats!.files, ...clientStatsInlined!.files].map(f => f.replace(/\..*\.js/, '.js')))
|
||||
|
||||
|
4
test/fixtures/basic-types/package.json
vendored
4
test/fixtures/basic-types/package.json
vendored
@ -10,8 +10,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"ofetch": "latest",
|
||||
"unplugin-vue-router": "^0.10.7",
|
||||
"vitest": "1.6.0",
|
||||
"unplugin-vue-router": "latest",
|
||||
"vitest": "latest",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest"
|
||||
},
|
||||
|
9
test/fixtures/basic-types/types.ts
vendored
9
test/fixtures/basic-types/types.ts
vendored
@ -258,7 +258,7 @@ describe('typed router integration', () => {
|
||||
})
|
||||
|
||||
describe('layouts', () => {
|
||||
it('recognizes named layouts', () => {
|
||||
it('definePageMeta recognizes named layouts', () => {
|
||||
definePageMeta({ layout: 'custom' })
|
||||
definePageMeta({ layout: 'pascal-case' })
|
||||
definePageMeta({ layout: 'override' })
|
||||
@ -266,11 +266,14 @@ describe('layouts', () => {
|
||||
definePageMeta({ layout: 'invalid-layout' })
|
||||
})
|
||||
|
||||
it('allows typing layouts', () => {
|
||||
it('NuxtLayout recognizes named layouts', () => {
|
||||
h(NuxtLayout, { name: 'custom' })
|
||||
|
||||
// @ts-expect-error Invalid layout
|
||||
h(NuxtLayout, { name: 'invalid-layout' })
|
||||
|
||||
h(NuxtLayout, { fallback: 'custom' })
|
||||
// @ts-expect-error Invalid layout
|
||||
h(NuxtLayout, { fallback: 'invalid-layout' })
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -661,14 +661,13 @@ describe('routing utilities: `encodeURL`', () => {
|
||||
})
|
||||
|
||||
describe('routing utilities: `useRoute`', () => {
|
||||
it('should show provide a mock route', () => {
|
||||
it('should provide a route', () => {
|
||||
expect(useRoute()).toMatchObject({
|
||||
fullPath: '/',
|
||||
hash: '',
|
||||
href: '/',
|
||||
matched: [],
|
||||
matched: expect.arrayContaining([]),
|
||||
meta: {},
|
||||
name: undefined,
|
||||
name: 'catchall',
|
||||
params: {},
|
||||
path: '/',
|
||||
query: {},
|
||||
|
97
test/nuxt/nuxt-page.test.ts
Normal file
97
test/nuxt/nuxt-page.test.ts
Normal file
@ -0,0 +1,97 @@
|
||||
/// <reference path="../fixtures/basic/.nuxt/nuxt.d.ts" />
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { mountSuspended } from '@nuxt/test-utils/runtime'
|
||||
import { NuxtLayout, NuxtPage } from '#components'
|
||||
|
||||
describe('NuxtPage should work with keepalive options', () => {
|
||||
let visits = 0
|
||||
const router = useRouter()
|
||||
beforeEach(() => {
|
||||
visits = 0
|
||||
router.addRoute({
|
||||
name: 'home',
|
||||
path: '/home',
|
||||
component: defineComponent({
|
||||
name: 'home',
|
||||
setup () {
|
||||
visits++
|
||||
return () => h('div', 'home')
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
afterEach(() => {
|
||||
router.removeRoute('home')
|
||||
})
|
||||
// include/exclude/boolean
|
||||
it('should reload setup every time a page is visited, without keepalive', async () => {
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(2)
|
||||
el.unmount()
|
||||
})
|
||||
|
||||
it('should not remount a page when keepalive is enabled', async () => {
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage, { keepalive: true }) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(1)
|
||||
el.unmount()
|
||||
})
|
||||
|
||||
it('should not remount a page when keepalive is granularly enabled (with include)', async () => {
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage, { keepalive: { include: ['home'] } }) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(1)
|
||||
el.unmount()
|
||||
})
|
||||
|
||||
it('should not remount a page when keepalive is granularly enabled (with exclude)', async () => {
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage, { keepalive: { exclude: ['catchall'] } }) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(1)
|
||||
el.unmount()
|
||||
})
|
||||
|
||||
it('should not remount a page when keepalive options are modified', async () => {
|
||||
const pages = ref('home')
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage, { keepalive: { include: pages.value } }) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
pages.value = 'home,catchall'
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(1)
|
||||
el.unmount()
|
||||
})
|
||||
})
|
17
test/runtime/app/router.options.ts
Normal file
17
test/runtime/app/router.options.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import type { RouterOptions } from 'nuxt/schema'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default <RouterOptions> {
|
||||
routes (_routes) {
|
||||
return [
|
||||
{
|
||||
name: 'catchall',
|
||||
path: '/:catchAll(.*)*',
|
||||
component: defineComponent({
|
||||
name: 'catchall',
|
||||
setup: () => () => ({}),
|
||||
}),
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
@ -13,6 +13,7 @@ export default defineVitestConfig({
|
||||
environmentOptions: {
|
||||
nuxt: {
|
||||
overrides: {
|
||||
pages: true,
|
||||
runtimeConfig: {
|
||||
app: {
|
||||
buildId: 'override',
|
||||
|
Loading…
Reference in New Issue
Block a user