mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-22 16:39:58 +00:00
Merge branch 'main' into add-middleware
This commit is contained in:
commit
be28c16104
2
.github/workflows/autofix-docs.yml
vendored
2
.github/workflows/autofix-docs.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
2
.github/workflows/autofix.yml
vendored
2
.github/workflows/autofix.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
52
.github/workflows/benchmark.yml
vendored
52
.github/workflows/benchmark.yml
vendored
@ -1,52 +0,0 @@
|
||||
name: benchmark
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# pull_request:
|
||||
# paths-ignore:
|
||||
# - "docs/**"
|
||||
# - "*.md"
|
||||
# branches:
|
||||
# - main
|
||||
# - "!v[0-9]*"
|
||||
|
||||
# https://github.com/vitejs/vite/blob/main/.github/workflows/ci.yml
|
||||
env:
|
||||
# 7 GiB by default on GitHub, setting to 6 GiB
|
||||
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
|
||||
# Remove default permissions of GITHUB_TOKEN for security
|
||||
# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name != 'push' }}
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build (stub)
|
||||
run: pnpm dev:prepare
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Run benchmarks
|
||||
uses: CodSpeedHQ/action@513a19673a831f139e8717bf45ead67e47f00044 # v3.2.0
|
||||
with:
|
||||
run: pnpm vitest bench
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
2
.github/workflows/changelog.yml
vendored
2
.github/workflows/changelog.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
50
.github/workflows/ci.yml
vendored
50
.github/workflows/ci.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.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@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||
uses: github/codeql-action/init@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6
|
||||
with:
|
||||
config: |
|
||||
paths:
|
||||
@ -95,7 +95,7 @@ jobs:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||
uses: github/codeql-action/analyze@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
|
||||
@ -113,7 +113,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -144,7 +144,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -171,7 +171,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -196,7 +196,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -213,6 +213,34 @@ jobs:
|
||||
- name: Check bundle size
|
||||
run: pnpm vitest run bundle
|
||||
|
||||
test-benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Restore dist cache
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: dist
|
||||
path: packages
|
||||
|
||||
- name: Run benchmarks
|
||||
uses: CodSpeedHQ/action@513a19673a831f139e8717bf45ead67e47f00044 # v3.2.0
|
||||
with:
|
||||
run: pnpm vitest bench
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
test-fixtures:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs:
|
||||
@ -253,7 +281,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: "pnpm"
|
||||
@ -280,7 +308,7 @@ jobs:
|
||||
TEST_PAYLOAD: ${{ matrix.payload }}
|
||||
SKIP_BUNDLE_SIZE: true
|
||||
|
||||
- uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
|
||||
- uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
|
||||
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
@ -305,7 +333,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
@ -337,7 +365,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
2
.github/workflows/lint-monorepo.yml
vendored
2
.github/workflows/lint-monorepo.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.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@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||
uses: github/codeql-action/upload-sarif@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6
|
||||
if: github.repository == 'nuxt/nuxt' && success()
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
@ -126,6 +126,26 @@ This will produce three files:
|
||||
|
||||
The `200.html` and `404.html` might be useful for the hosting provider you are using.
|
||||
|
||||
#### Skipping Client Fallback Generation
|
||||
|
||||
When prerendering a client-rendered app, Nuxt will generate `index.html`, `200.html` and `404.html` files by default. However, if you need to prevent any (or all) of these files from being generated in your build, you can use the `'prerender:generate'` hook from [Nitro](/docs/getting-started/prerendering#prerendergenerate-nitro-hook).
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
nitro: {
|
||||
hooks: {
|
||||
'prerender:generate'(route) {
|
||||
const routesToSkip = ['/index.html', '/200.html', '/404.html']
|
||||
if (routesToSkip.includes(route.route)) {
|
||||
route.skip = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Hybrid Rendering
|
||||
|
||||
Hybrid rendering allows different caching rules per route using **Route Rules** and decides how the server should respond to a new request on a given URL.
|
||||
|
39
package.json
39
package.json
@ -37,13 +37,15 @@
|
||||
"typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs --languages html"
|
||||
},
|
||||
"resolutions": {
|
||||
"@babel/core": "7.26.7",
|
||||
"@babel/helper-plugin-utils": "7.26.5",
|
||||
"@nuxt/cli": "3.20.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/rspack-builder": "workspace:*",
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@types/node": "22.10.7",
|
||||
"@types/node": "22.12.0",
|
||||
"@unhead/dom": "1.11.18",
|
||||
"@unhead/schema": "1.11.18",
|
||||
"@unhead/shared": "1.11.18",
|
||||
@ -53,27 +55,28 @@
|
||||
"@vue/compiler-dom": "3.5.13",
|
||||
"@vue/shared": "3.5.13",
|
||||
"c12": "2.0.1",
|
||||
"h3": "npm:h3-nightly@1.13.1-20250110-173418-de24917",
|
||||
"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-28968142.ce23e942",
|
||||
"nuxt": "workspace:*",
|
||||
"ohash": "1.1.4",
|
||||
"postcss": "8.5.1",
|
||||
"rollup": "4.31.0",
|
||||
"rollup": "4.32.1",
|
||||
"send": ">=1.1.0",
|
||||
"typescript": "5.7.3",
|
||||
"ufo": "1.5.4",
|
||||
"unbuild": "3.3.1",
|
||||
"unhead": "1.11.18",
|
||||
"unimport": "3.14.6",
|
||||
"unimport": "4.0.0",
|
||||
"vite": "6.0.11",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "0.17.3",
|
||||
"@babel/core": "7.26.0",
|
||||
"@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/kit": "workspace:*",
|
||||
@ -83,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.7",
|
||||
"@types/node": "22.12.0",
|
||||
"@types/semver": "7.5.8",
|
||||
"@unhead/schema": "1.11.18",
|
||||
"@unhead/vue": "1.11.18",
|
||||
"@vitest/coverage-v8": "3.0.3",
|
||||
"@vitest/coverage-v8": "3.0.4",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"acorn": "8.14.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
@ -97,29 +100,29 @@
|
||||
"cssnano": "7.0.6",
|
||||
"destr": "2.0.3",
|
||||
"devalue": "5.1.1",
|
||||
"eslint": "9.18.0",
|
||||
"eslint": "9.19.0",
|
||||
"eslint-plugin-no-only-tests": "3.3.0",
|
||||
"eslint-plugin-perfectionist": "4.7.0",
|
||||
"eslint-typegen": "1.0.0",
|
||||
"estree-walker": "3.0.3",
|
||||
"h3": "npm:h3-nightly@1.13.1-20250110-173418-de24917",
|
||||
"happy-dom": "16.7.1",
|
||||
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||
"happy-dom": "16.7.3",
|
||||
"installed-check": "9.3.0",
|
||||
"jiti": "2.4.2",
|
||||
"knip": "5.42.2",
|
||||
"knip": "5.43.6",
|
||||
"magic-string": "0.30.17",
|
||||
"markdownlint-cli": "0.43.0",
|
||||
"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-28968142.ce23e942",
|
||||
"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.49.1",
|
||||
"rollup": "4.31.0",
|
||||
"playwright-core": "1.50.0",
|
||||
"rollup": "4.32.1",
|
||||
"semver": "7.6.3",
|
||||
"sherif": "1.1.1",
|
||||
"sherif": "1.2.0",
|
||||
"std-env": "3.8.0",
|
||||
"tinyexec": "0.3.2",
|
||||
"tinyglobby": "0.2.10",
|
||||
@ -127,7 +130,7 @@
|
||||
"typescript": "5.7.3",
|
||||
"ufo": "1.5.4",
|
||||
"unbuild": "3.3.1",
|
||||
"vitest": "3.0.3",
|
||||
"vitest": "3.0.4",
|
||||
"vitest-environment-nuxt": "1.0.1",
|
||||
"vue": "3.5.13",
|
||||
"vue-tsc": "2.2.0",
|
||||
|
@ -27,7 +27,6 @@
|
||||
"test:attw": "attw --pack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"c12": "^2.0.1",
|
||||
"consola": "^3.4.0",
|
||||
"defu": "^6.1.4",
|
||||
@ -46,19 +45,20 @@
|
||||
"std-env": "^3.8.0",
|
||||
"ufo": "^1.5.4",
|
||||
"unctx": "^2.4.1",
|
||||
"unimport": "^3.14.6",
|
||||
"unimport": "^4.0.0",
|
||||
"untyped": "^1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rspack/core": "1.2.0",
|
||||
"@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-28968142.ce23e942",
|
||||
"unbuild": "3.3.1",
|
||||
"vite": "6.0.11",
|
||||
"vitest": "3.0.3",
|
||||
"vitest": "3.0.4",
|
||||
"webpack": "5.97.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { kebabCase, pascalCase } from 'scule'
|
||||
import type { Component, ComponentsDir } from '@nuxt/schema'
|
||||
import { useNuxt } from './context'
|
||||
import { checkNuxtVersion } from './compatibility'
|
||||
import { logger } from './logger'
|
||||
import { MODE_RE } from './utils'
|
||||
|
||||
@ -10,9 +9,6 @@ import { MODE_RE } from './utils'
|
||||
*/
|
||||
export function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) {
|
||||
const nuxt = useNuxt()
|
||||
if (!checkNuxtVersion('>=2.13', nuxt)) {
|
||||
throw new Error(`\`addComponentsDir\` requires Nuxt 2.13 or higher.`)
|
||||
}
|
||||
nuxt.options.components ||= []
|
||||
dir.priority ||= 0
|
||||
nuxt.hook('components:dirs', (dirs) => { dirs[opts.prepend ? 'unshift' : 'push'](dir) })
|
||||
@ -27,10 +23,6 @@ export type AddComponentOptions = { name: string, filePath: string } & Partial<E
|
||||
*/
|
||||
export function addComponent (opts: AddComponentOptions) {
|
||||
const nuxt = useNuxt()
|
||||
if (!checkNuxtVersion('>=2.13', nuxt)) {
|
||||
throw new Error(`\`addComponent\` requires Nuxt 2.13 or higher.`)
|
||||
}
|
||||
|
||||
nuxt.options.components ||= []
|
||||
|
||||
if (!opts.mode) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
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) => {
|
||||
|
@ -2,7 +2,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import { bench, describe } from 'vitest'
|
||||
import { join, normalize } from 'pathe'
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import { loadNuxtConfig } from '../src'
|
||||
import { loadNuxtConfig } from '@nuxt/kit'
|
||||
|
||||
const fixtures = {
|
||||
'empty directory': 'node_modules/fixture',
|
||||
@ -16,7 +16,7 @@ describe('loadNuxtConfig', () => {
|
||||
for (const fixture in fixtures) {
|
||||
const relativeDir = join('../../..', fixtures[fixture as keyof typeof fixtures])
|
||||
const path = withoutTrailingSlash(normalize(fileURLToPath(new URL(relativeDir, import.meta.url))))
|
||||
bench(fixture, async () => {
|
||||
bench(`loadNuxtConfig in the ${fixture}`, async () => {
|
||||
await loadNuxtConfig({ cwd: path })
|
||||
})
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import { afterAll, bench, describe } from 'vitest'
|
||||
import { join, normalize } from 'pathe'
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import { loadNuxt, writeTypes } from '../src'
|
||||
import { loadNuxt, writeTypes } from '@nuxt/kit'
|
||||
|
||||
describe('writeTypes', async () => {
|
||||
const relativeDir = join('../../..', 'test/fixtures/basic-types')
|
||||
@ -13,7 +13,7 @@ describe('writeTypes', async () => {
|
||||
await nuxt.close()
|
||||
})
|
||||
|
||||
bench('write types', async () => {
|
||||
bench('writeTypes in the basic-types fixture', async () => {
|
||||
await writeTypes(nuxt)
|
||||
})
|
||||
})
|
||||
|
@ -90,7 +90,7 @@
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"globby": "^14.0.2",
|
||||
"h3": "npm:h3-nightly@1.13.1-20250110-173418-de24917",
|
||||
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||
"hookable": "^5.5.3",
|
||||
"ignore": "^7.0.3",
|
||||
"impound": "^0.2.0",
|
||||
@ -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-28968142.ce23e942",
|
||||
"nypm": "^0.5.2",
|
||||
"ofetch": "^1.4.1",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^2.0.2",
|
||||
@ -119,9 +119,9 @@
|
||||
"unctx": "^2.4.1",
|
||||
"unenv": "^1.10.0",
|
||||
"unhead": "^1.11.18",
|
||||
"unimport": "^3.14.6",
|
||||
"unimport": "^4.0.0",
|
||||
"unplugin": "^2.1.2",
|
||||
"unplugin-vue-router": "^0.11.0",
|
||||
"unplugin-vue-router": "^0.11.2",
|
||||
"unstorage": "^1.14.4",
|
||||
"untyped": "^1.5.2",
|
||||
"vue": "^3.5.13",
|
||||
@ -131,13 +131,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/scripts": "0.9.5",
|
||||
"@parcel/watcher": "2.5.0",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@types/estree": "1.0.6",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vue/compiler-sfc": "3.5.13",
|
||||
"unbuild": "3.3.1",
|
||||
"vite": "6.0.11",
|
||||
"vitest": "3.0.3"
|
||||
"vitest": "3.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@parcel/watcher": "^2.1.0",
|
||||
|
@ -132,10 +132,11 @@ export default defineComponent({
|
||||
ssrHTML.value = getFragmentHTML(instance.vnode.el, true)?.join('') || ''
|
||||
const key = `${props.name}_${hashId.value}`
|
||||
nuxtApp.payload.data[key] ||= {}
|
||||
nuxtApp.payload.data[key].html = ssrHTML.value
|
||||
// clear all data-island-uid to avoid conflicts when saving into payloads
|
||||
nuxtApp.payload.data[key].html = ssrHTML.value.replaceAll(new RegExp(`data-island-uid="${ssrHTML.value.match(SSR_UID_RE)?.[1] || ''}"`, 'g'), `data-island-uid=""`)
|
||||
}
|
||||
|
||||
const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? getId())
|
||||
const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] || getId())
|
||||
const availableSlots = computed(() => [...ssrHTML.value.matchAll(SLOTNAME_RE)].map(m => m[1]))
|
||||
const html = computed(() => {
|
||||
const currentSlots = Object.keys(slots)
|
||||
|
@ -1,12 +1,12 @@
|
||||
import type { DefineComponent, MaybeRef, VNode } from 'vue'
|
||||
import { Suspense, Transition, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, ref, unref } from 'vue'
|
||||
import { Suspense, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, ref, unref } from 'vue'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
import type { PageMeta } from '../../pages/runtime/composables'
|
||||
|
||||
import { useRoute, useRouter } from '../composables/router'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
import { _wrapIf } from './utils'
|
||||
import { _wrapInTransition } from './utils'
|
||||
import { LayoutMetaSymbol, PageRouteSymbol } from './injections'
|
||||
|
||||
// @ts-expect-error virtual file
|
||||
@ -80,7 +80,7 @@ export default defineComponent({
|
||||
const transitionProps = route.meta.layoutTransition ?? defaultLayoutTransition
|
||||
|
||||
// We avoid rendering layout transition if there is no layout to render
|
||||
return _wrapIf(Transition, hasLayout && transitionProps, {
|
||||
return _wrapInTransition(hasLayout && transitionProps, {
|
||||
default: () => h(Suspense, { suspensible: true, onResolve: () => { nextTick(done) } }, {
|
||||
default: () => h(
|
||||
LayoutProvider,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createStaticVNode, h } from 'vue'
|
||||
import type { Component, RendererNode, VNode } from 'vue'
|
||||
import { Transition, createStaticVNode, h } from 'vue'
|
||||
import type { RendererNode, VNode } from 'vue'
|
||||
// eslint-disable-next-line
|
||||
import { isString, isPromise, isArray, isObject } from '@vue/shared'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
@ -10,9 +10,8 @@ import { START_LOCATION } from '#build/pages'
|
||||
* Internal utility
|
||||
* @private
|
||||
*/
|
||||
export const _wrapIf = (component: Component, props: any, slots: any) => {
|
||||
props = props === true ? {} : props
|
||||
return { default: () => props ? h(component, props, slots) : slots.default?.() }
|
||||
export const _wrapInTransition = (props: any, children: any) => {
|
||||
return { default: () => import.meta.client && props ? h(Transition, props === true ? {} : props, children) : children.default?.() }
|
||||
}
|
||||
|
||||
const ROUTE_KEY_PARENTHESES_RE = /(:\w+)\([^)]+\)/g
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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']) {
|
||||
@ -661,7 +662,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 })
|
||||
}
|
||||
|
||||
|
@ -43,8 +43,8 @@ export function walk (ast: Program | Node, callback: Partial<WalkOptions>) {
|
||||
|
||||
export function parseAndWalk (code: string, sourceFilename: string, callback: WalkerCallback): Program
|
||||
export function parseAndWalk (code: string, sourceFilename: string, object: Partial<WalkOptions>): Program
|
||||
export function parseAndWalk (code: string, _sourceFilename: string, callback: Partial<WalkOptions> | WalkerCallback) {
|
||||
const ast = parse (code, { sourceType: 'module', ecmaVersion: 'latest', locations: true })
|
||||
export function parseAndWalk (code: string, sourceFilename: string, callback: Partial<WalkOptions> | WalkerCallback) {
|
||||
const ast = parse (code, { sourceType: 'module', ecmaVersion: 'latest', locations: true, sourceFile: sourceFilename })
|
||||
walk(ast, typeof callback === 'function' ? { enter: callback } : callback)
|
||||
return ast
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Fragment, Suspense, Transition, defineComponent, h, inject, nextTick, ref, watch } from 'vue'
|
||||
import { Fragment, Suspense, defineComponent, h, inject, nextTick, ref, watch } from 'vue'
|
||||
import type { KeepAliveProps, TransitionProps, VNode } from 'vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { defu } from 'defu'
|
||||
@ -9,7 +9,7 @@ import type { RouterViewSlotProps } from './utils'
|
||||
import { RouteProvider } from '#app/components/route-provider'
|
||||
import { useNuxtApp } from '#app/nuxt'
|
||||
import { useRouter } from '#app/composables/router'
|
||||
import { _wrapIf } from '#app/components/utils'
|
||||
import { _wrapInTransition } from '#app/components/utils'
|
||||
import { LayoutMetaSymbol, PageRouteSymbol } from '#app/components/injections'
|
||||
// @ts-expect-error virtual file
|
||||
import { appKeepalive as defaultKeepaliveConfig, appPageTransition as defaultPageTransition } from '#build/nuxt.config.mjs'
|
||||
@ -101,8 +101,30 @@ export default defineComponent({
|
||||
nuxtApp.callHook('page:loading:end')
|
||||
pageLoadingEndHookAlreadyCalled = true
|
||||
}
|
||||
|
||||
previousPageKey = key
|
||||
|
||||
if (import.meta.server) {
|
||||
vnode = h(Suspense, {
|
||||
suspensible: true,
|
||||
}, {
|
||||
default: () => {
|
||||
const providerVNode = h(RouteProvider, {
|
||||
key: key || undefined,
|
||||
vnode: slots.default ? h(Fragment, undefined, slots.default(routeProps)) : routeProps.Component,
|
||||
route: routeProps.route,
|
||||
renderKey: key || undefined,
|
||||
vnodeRef: pageRef,
|
||||
})
|
||||
return providerVNode
|
||||
},
|
||||
})
|
||||
|
||||
return vnode
|
||||
}
|
||||
|
||||
// Client side rendering
|
||||
|
||||
const hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition)
|
||||
const transitionProps = hasTransition && _mergeTransitionProps([
|
||||
props.transition,
|
||||
@ -112,7 +134,7 @@ export default defineComponent({
|
||||
].filter(Boolean))
|
||||
|
||||
const keepaliveConfig = props.keepalive ?? routeProps.route.meta.keepalive ?? (defaultKeepaliveConfig as KeepAliveProps)
|
||||
vnode = _wrapIf(Transition, hasTransition && transitionProps,
|
||||
vnode = _wrapInTransition(hasTransition && transitionProps,
|
||||
wrapInKeepAlive(keepaliveConfig, h(Suspense, {
|
||||
suspensible: true,
|
||||
onPending: () => nuxtApp.callHook('page:start', routeProps.Component),
|
||||
@ -134,7 +156,7 @@ export default defineComponent({
|
||||
trackRootNodes: hasTransition,
|
||||
vnodeRef: pageRef,
|
||||
})
|
||||
if (import.meta.client && keepaliveConfig) {
|
||||
if (keepaliveConfig) {
|
||||
(providerVNode.type as any).name = (routeProps.Component.type as any).name || (routeProps.Component.type as any).__name || 'RouteProvider'
|
||||
}
|
||||
return providerVNode
|
||||
|
@ -11,6 +11,7 @@ import { transform } from 'esbuild'
|
||||
import type { Property } from 'estree'
|
||||
import type { NuxtPage } from 'nuxt/schema'
|
||||
|
||||
import { klona } from 'klona'
|
||||
import { parseAndWalk, withLocations } from '../core/utils/parse'
|
||||
import { getLoader, uniqueBy } from '../core/utils'
|
||||
import { logger, toArray } from '../utils'
|
||||
@ -215,7 +216,7 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr
|
||||
}
|
||||
|
||||
if (absolutePath in metaCache && metaCache[absolutePath]) {
|
||||
return metaCache[absolutePath]
|
||||
return klona(metaCache[absolutePath])
|
||||
}
|
||||
|
||||
const loader = getLoader(absolutePath)
|
||||
@ -314,7 +315,7 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr
|
||||
}
|
||||
|
||||
metaCache[absolutePath] = extractedMeta
|
||||
return extractedMeta
|
||||
return klona(extractedMeta)
|
||||
}
|
||||
|
||||
const COLON_RE = /:/g
|
||||
|
@ -2,13 +2,13 @@ import { fileURLToPath } from 'node:url'
|
||||
import { bench, describe } from 'vitest'
|
||||
import { normalize } from 'pathe'
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import { loadNuxt } from '../src'
|
||||
import { loadNuxt } from 'nuxt'
|
||||
|
||||
const emptyDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../node_modules/fixture', import.meta.url))))
|
||||
const basicTestFixtureDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../test/fixtures/basic', import.meta.url))))
|
||||
|
||||
describe('loadNuxt', () => {
|
||||
bench('empty directory', async () => {
|
||||
bench('loadNuxt in an empty directory', async () => {
|
||||
const nuxt = await loadNuxt({
|
||||
cwd: emptyDir,
|
||||
ready: true,
|
||||
@ -16,7 +16,7 @@ describe('loadNuxt', () => {
|
||||
await nuxt.close()
|
||||
})
|
||||
|
||||
bench('basic test fixture', async () => {
|
||||
bench('loadNuxt in the basic test fixture', async () => {
|
||||
const nuxt = await loadNuxt({
|
||||
cwd: basicTestFixtureDir,
|
||||
ready: true,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { type MockedFunction, describe, expect, it, vi } from 'vitest'
|
||||
import { compileScript, parse } from '@vue/compiler-sfc'
|
||||
import * as Parser from 'acorn'
|
||||
import { klona } from 'klona'
|
||||
import { transform as esbuildTransform } from 'esbuild'
|
||||
import { PageMetaPlugin } from '../src/pages/plugins/page-meta'
|
||||
import { getRouteMeta, normalizeRoutes } from '../src/pages/utils'
|
||||
@ -8,6 +9,8 @@ import type { NuxtPage } from '../schema'
|
||||
|
||||
const filePath = '/app/pages/index.vue'
|
||||
|
||||
vi.mock('klona', { spy: true })
|
||||
|
||||
describe('page metadata', () => {
|
||||
it('should not extract metadata from empty files', async () => {
|
||||
expect(await getRouteMeta('', filePath)).toEqual({})
|
||||
@ -62,11 +65,20 @@ definePageMeta({ name: 'bar' })
|
||||
})
|
||||
|
||||
it('should use and invalidate cache', async () => {
|
||||
const _klona = klona as unknown as MockedFunction<typeof klona>
|
||||
_klona.mockImplementation(obj => obj)
|
||||
const fileContents = `<script setup>definePageMeta({ foo: 'bar' })</script>`
|
||||
const meta = await getRouteMeta(fileContents, filePath)
|
||||
expect(meta === await getRouteMeta(fileContents, filePath)).toBeTruthy()
|
||||
expect(meta === await getRouteMeta(fileContents, '/app/pages/other.vue')).toBeFalsy()
|
||||
expect(meta === await getRouteMeta('<template><div>Hi</div></template>' + fileContents, filePath)).toBeFalsy()
|
||||
_klona.mockReset()
|
||||
})
|
||||
|
||||
it('should not share state between page metadata', async () => {
|
||||
const fileContents = `<script setup>definePageMeta({ foo: 'bar' })</script>`
|
||||
const meta = await getRouteMeta(fileContents, filePath)
|
||||
expect(meta === await getRouteMeta(fileContents, filePath)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should extract serialisable metadata', async () => {
|
||||
|
@ -834,3 +834,41 @@ describe('pages:pathToNitroGlob', () => {
|
||||
expect(pathToNitroGlob(path)).to.equal(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('page:extends', () => {
|
||||
const DYNAMIC_META_KEY = '__nuxt_dynamic_meta_key' as const
|
||||
it('should preserve distinct metadata for multiple routes referencing the same file', async () => {
|
||||
const files: NuxtPage[] = [
|
||||
{ path: 'home', file: `pages/index.vue` },
|
||||
{ path: 'home1', file: `pages/index.vue`, meta: { test: true } },
|
||||
{ path: 'home2', file: `pages/index.vue`, meta: { snap: true } },
|
||||
]
|
||||
const vfs = Object.fromEntries(
|
||||
files.map(file => [file.file, `
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
hello: 'world'
|
||||
})
|
||||
</script>
|
||||
`]),
|
||||
) as Record<string, string>
|
||||
await augmentPages(files, vfs)
|
||||
expect(files).toEqual([
|
||||
{
|
||||
path: 'home',
|
||||
file: `pages/index.vue`,
|
||||
meta: { [DYNAMIC_META_KEY]: new Set(['meta']) },
|
||||
},
|
||||
{
|
||||
path: 'home1',
|
||||
file: `pages/index.vue`,
|
||||
meta: { [DYNAMIC_META_KEY]: new Set(['meta']), test: true },
|
||||
},
|
||||
{
|
||||
path: 'home2',
|
||||
file: `pages/index.vue`,
|
||||
meta: { [DYNAMIC_META_KEY]: new Set(['meta']), snap: true },
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
@ -32,7 +32,7 @@
|
||||
"dependencies": {
|
||||
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@rspack/core": "^1.2.0",
|
||||
"@rspack/core": "^1.2.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
@ -43,7 +43,7 @@
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^9.0.2",
|
||||
"globby": "^14.0.2",
|
||||
"h3": "npm:h3-nightly@1.13.1-20250110-173418-de24917",
|
||||
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||
"jiti": "^2.4.2",
|
||||
"knitwork": "^1.2.0",
|
||||
"magic-string": "^0.30.17",
|
||||
@ -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.31.0",
|
||||
"rollup": "4.32.1",
|
||||
"unbuild": "3.3.1",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
|
@ -48,17 +48,17 @@
|
||||
"compatx": "0.1.8",
|
||||
"esbuild-loader": "4.2.2",
|
||||
"file-loader": "6.2.0",
|
||||
"h3": "npm:h3-nightly@1.13.1-20250110-173418-de24917",
|
||||
"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-28968142.ce23e942",
|
||||
"ofetch": "1.4.1",
|
||||
"pkg-types": "1.3.1",
|
||||
"sass-loader": "16.0.4",
|
||||
"scule": "1.3.0",
|
||||
"unbuild": "3.3.1",
|
||||
"unctx": "2.4.1",
|
||||
"unimport": "3.14.6",
|
||||
"unimport": "4.0.0",
|
||||
"untyped": "1.5.2",
|
||||
"vite": "6.0.11",
|
||||
"vue": "3.5.13",
|
||||
|
@ -39,5 +39,13 @@ export default defineUntypedSchema({
|
||||
* @type {(data: { loading?: string }) => string}
|
||||
*/
|
||||
loadingTemplate,
|
||||
|
||||
/**
|
||||
* Set CORS options for the dev server
|
||||
* @type {typeof import('h3').H3CorsOptions}
|
||||
*/
|
||||
cors: {
|
||||
origin: [/^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -17,7 +17,7 @@
|
||||
"prerender": "pnpm build && jiti ./lib/prerender"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unocss/reset": "65.4.2",
|
||||
"@unocss/reset": "65.4.3",
|
||||
"beasties": "0.2.0",
|
||||
"html-validate": "9.1.3",
|
||||
"htmlnano": "2.1.1",
|
||||
@ -29,7 +29,7 @@
|
||||
"svgo": "3.3.2",
|
||||
"tinyexec": "0.3.2",
|
||||
"tinyglobby": "0.2.10",
|
||||
"unocss": "65.4.2",
|
||||
"unocss": "65.4.3",
|
||||
"vite": "6.0.11"
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"rollup": "4.31.0",
|
||||
"rollup": "4.32.1",
|
||||
"unbuild": "3.3.1",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
@ -41,8 +41,9 @@
|
||||
"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.13.1-20250110-173418-de24917",
|
||||
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||
"jiti": "^2.4.2",
|
||||
"knitwork": "^1.2.0",
|
||||
"magic-string": "^0.30.17",
|
||||
@ -56,7 +57,7 @@
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^2.1.2",
|
||||
"vite": "^6.0.11",
|
||||
"vite-node": "^3.0.3",
|
||||
"vite-node": "^3.0.4",
|
||||
"vite-plugin-checker": "^0.8.0",
|
||||
"vue-bundle-renderer": "^2.1.1"
|
||||
},
|
||||
|
@ -9,7 +9,7 @@ import { getPort } from 'get-port-please'
|
||||
import { joinURL, withoutLeadingSlash } from 'ufo'
|
||||
import { defu } from 'defu'
|
||||
import { env, nodeless } from 'unenv'
|
||||
import { appendCorsHeaders, appendCorsPreflightHeaders, defineEventHandler } from 'h3'
|
||||
import { defineEventHandler, handleCors, setHeader } from 'h3'
|
||||
import type { ViteConfig } from '@nuxt/schema'
|
||||
import type { ViteBuildContext } from './vite'
|
||||
import { devStyleSSRPlugin } from './plugins/dev-ssr-css'
|
||||
@ -255,11 +255,11 @@ export async function buildClient (ctx: ViteBuildContext) {
|
||||
// @ts-expect-error _skip_transform is a private property
|
||||
event.node.req._skip_transform = true
|
||||
} else if (!useViteCors) {
|
||||
if (event.method === 'OPTIONS') {
|
||||
appendCorsPreflightHeaders(event, {})
|
||||
const isPreflight = handleCors(event, ctx.nuxt.options.devServer.cors)
|
||||
if (isPreflight) {
|
||||
return null
|
||||
}
|
||||
appendCorsHeaders(event, {})
|
||||
setHeader(event, 'Vary', 'Origin')
|
||||
}
|
||||
|
||||
// Workaround: vite devmiddleware modifies req.url
|
||||
|
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: {
|
||||
|
@ -42,7 +42,7 @@
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^9.0.2",
|
||||
"globby": "^14.0.2",
|
||||
"h3": "npm:h3-nightly@1.13.1-20250110-173418-de24917",
|
||||
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||
"jiti": "^2.4.2",
|
||||
"knitwork": "^1.2.0",
|
||||
"magic-string": "^0.30.17",
|
||||
@ -73,11 +73,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@rspack/core": "1.2.0",
|
||||
"@rspack/core": "1.2.2",
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
"rollup": "4.31.0",
|
||||
"rollup": "4.32.1",
|
||||
"unbuild": "3.3.1",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import pify from 'pify'
|
||||
import { resolve } from 'pathe'
|
||||
import { defineEventHandler, fromNodeMiddleware } 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'
|
||||
import webpackHotMiddleware from 'webpack-hot-middleware'
|
||||
@ -125,7 +126,7 @@ async function createDevMiddleware (compiler: Compiler) {
|
||||
})
|
||||
|
||||
// Register devMiddleware on server
|
||||
const devHandler = wdmToH3Handler(devMiddleware)
|
||||
const devHandler = wdmToH3Handler(devMiddleware, nuxt.options.devServer.cors)
|
||||
const hotHandler = fromNodeMiddleware(hotMiddleware)
|
||||
await nuxt.callHook('server:devHandler', defineEventHandler(async (event) => {
|
||||
const body = await devHandler(event)
|
||||
@ -139,8 +140,20 @@ async function createDevMiddleware (compiler: Compiler) {
|
||||
}
|
||||
|
||||
// TODO: implement upstream in `webpack-dev-middleware`
|
||||
function wdmToH3Handler (devMiddleware: webpackDevMiddleware.API<IncomingMessage, ServerResponse>) {
|
||||
function wdmToH3Handler (devMiddleware: webpackDevMiddleware.API<IncomingMessage, ServerResponse>, corsOptions: H3CorsOptions) {
|
||||
return defineEventHandler(async (event) => {
|
||||
const isPreflight = handleCors(event, corsOptions)
|
||||
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 = {
|
||||
...event.context.webpack,
|
||||
devMiddleware: devMiddleware.context,
|
||||
|
2425
pnpm-lock.yaml
2425
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -61,7 +61,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`)
|
||||
|
||||
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1398k"`)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1402k"`)
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
@ -127,10 +127,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(pagesRootDir, '.output/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"303k"`)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"301k"`)
|
||||
|
||||
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1398k"`)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1402k"`)
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { resolve } from 'pathe'
|
||||
import { configDefaults, coverageConfigDefaults, defineConfig } from 'vitest/config'
|
||||
import { isWindows } from 'std-env'
|
||||
// import codspeedPlugin from '@codspeed/vitest-plugin'
|
||||
import codspeedPlugin from '@codspeed/vitest-plugin'
|
||||
|
||||
export default defineConfig({
|
||||
// plugins: [codspeedPlugin()],
|
||||
plugins: [codspeedPlugin()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'#build/nuxt.config.mjs': resolve('./test/mocks/nuxt-config'),
|
||||
|
Loading…
Reference in New Issue
Block a user