Merge branch 'main' into add-middleware

This commit is contained in:
Saeid Zareie 2025-01-29 10:27:36 +03:30 committed by GitHub
commit be28c16104
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 1725 additions and 1290 deletions

View File

@ -19,7 +19,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"

View File

@ -15,7 +15,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"

View File

@ -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 }}

View File

@ -26,7 +26,7 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"

View File

@ -39,7 +39,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"
@ -78,7 +78,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 uses: github/codeql-action/init@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6
with: with:
config: | config: |
paths: paths:
@ -95,7 +95,7 @@ jobs:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 uses: github/codeql-action/analyze@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6
with: with:
category: "/language:${{ matrix.language }}" category: "/language:${{ matrix.language }}"
@ -113,7 +113,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"
@ -144,7 +144,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"
@ -171,7 +171,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"
@ -196,7 +196,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"
@ -213,6 +213,34 @@ jobs:
- name: Check bundle size - name: Check bundle size
run: pnpm vitest run bundle 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: test-fixtures:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: needs:
@ -253,7 +281,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: ${{ matrix.node }} node-version: ${{ matrix.node }}
cache: "pnpm" cache: "pnpm"
@ -280,7 +308,7 @@ jobs:
TEST_PAYLOAD: ${{ matrix.payload }} TEST_PAYLOAD: ${{ matrix.payload }}
SKIP_BUNDLE_SIZE: true 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' if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
@ -305,7 +333,7 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"
@ -337,7 +365,7 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"

View File

@ -23,7 +23,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"

View File

@ -27,7 +27,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
cache: "pnpm" cache: "pnpm"

View File

@ -23,7 +23,7 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: lts/* node-version: lts/*
registry-url: "https://registry.npmjs.org/" registry-url: "https://registry.npmjs.org/"

View File

@ -68,7 +68,7 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 uses: github/codeql-action/upload-sarif@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6
if: github.repository == 'nuxt/nuxt' && success() if: github.repository == 'nuxt/nuxt' && success()
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -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. 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
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. 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.

View File

@ -37,13 +37,15 @@
"typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs --languages html" "typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs --languages html"
}, },
"resolutions": { "resolutions": {
"@babel/core": "7.26.7",
"@babel/helper-plugin-utils": "7.26.5",
"@nuxt/cli": "3.20.0", "@nuxt/cli": "3.20.0",
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@nuxt/rspack-builder": "workspace:*", "@nuxt/rspack-builder": "workspace:*",
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@nuxt/vite-builder": "workspace:*", "@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*",
"@types/node": "22.10.7", "@types/node": "22.12.0",
"@unhead/dom": "1.11.18", "@unhead/dom": "1.11.18",
"@unhead/schema": "1.11.18", "@unhead/schema": "1.11.18",
"@unhead/shared": "1.11.18", "@unhead/shared": "1.11.18",
@ -53,27 +55,28 @@
"@vue/compiler-dom": "3.5.13", "@vue/compiler-dom": "3.5.13",
"@vue/shared": "3.5.13", "@vue/shared": "3.5.13",
"c12": "2.0.1", "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", "jiti": "2.4.2",
"magic-string": "^0.30.17", "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:*", "nuxt": "workspace:*",
"ohash": "1.1.4", "ohash": "1.1.4",
"postcss": "8.5.1", "postcss": "8.5.1",
"rollup": "4.31.0", "rollup": "4.32.1",
"send": ">=1.1.0", "send": ">=1.1.0",
"typescript": "5.7.3", "typescript": "5.7.3",
"ufo": "1.5.4", "ufo": "1.5.4",
"unbuild": "3.3.1", "unbuild": "3.3.1",
"unhead": "1.11.18", "unhead": "1.11.18",
"unimport": "3.14.6", "unimport": "4.0.0",
"vite": "6.0.11", "vite": "6.0.11",
"vue": "3.5.13" "vue": "3.5.13"
}, },
"devDependencies": { "devDependencies": {
"@arethetypeswrong/cli": "0.17.3", "@arethetypeswrong/cli": "0.17.3",
"@babel/core": "7.26.0", "@babel/core": "7.26.7",
"@babel/helper-plugin-utils": "7.26.5", "@babel/helper-plugin-utils": "7.26.5",
"@codspeed/vitest-plugin": "4.0.0",
"@nuxt/cli": "3.20.0", "@nuxt/cli": "3.20.0",
"@nuxt/eslint-config": "0.7.5", "@nuxt/eslint-config": "0.7.5",
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
@ -83,11 +86,11 @@
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/babel__core": "7.20.5", "@types/babel__core": "7.20.5",
"@types/babel__helper-plugin-utils": "7.10.3", "@types/babel__helper-plugin-utils": "7.10.3",
"@types/node": "22.10.7", "@types/node": "22.12.0",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"@unhead/schema": "1.11.18", "@unhead/schema": "1.11.18",
"@unhead/vue": "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", "@vue/test-utils": "2.4.6",
"acorn": "8.14.0", "acorn": "8.14.0",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.20",
@ -97,29 +100,29 @@
"cssnano": "7.0.6", "cssnano": "7.0.6",
"destr": "2.0.3", "destr": "2.0.3",
"devalue": "5.1.1", "devalue": "5.1.1",
"eslint": "9.18.0", "eslint": "9.19.0",
"eslint-plugin-no-only-tests": "3.3.0", "eslint-plugin-no-only-tests": "3.3.0",
"eslint-plugin-perfectionist": "4.7.0", "eslint-plugin-perfectionist": "4.7.0",
"eslint-typegen": "1.0.0", "eslint-typegen": "1.0.0",
"estree-walker": "3.0.3", "estree-walker": "3.0.3",
"h3": "npm:h3-nightly@1.13.1-20250110-173418-de24917", "h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
"happy-dom": "16.7.1", "happy-dom": "16.7.3",
"installed-check": "9.3.0", "installed-check": "9.3.0",
"jiti": "2.4.2", "jiti": "2.4.2",
"knip": "5.42.2", "knip": "5.43.6",
"magic-string": "0.30.17", "magic-string": "0.30.17",
"markdownlint-cli": "0.43.0", "markdownlint-cli": "0.44.0",
"memfs": "4.17.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": "workspace:*",
"nuxt-content-twoslash": "0.1.2", "nuxt-content-twoslash": "0.1.2",
"ofetch": "1.4.1", "ofetch": "1.4.1",
"pathe": "2.0.2", "pathe": "2.0.2",
"pkg-pr-new": "0.0.39", "pkg-pr-new": "0.0.39",
"playwright-core": "1.49.1", "playwright-core": "1.50.0",
"rollup": "4.31.0", "rollup": "4.32.1",
"semver": "7.6.3", "semver": "7.6.3",
"sherif": "1.1.1", "sherif": "1.2.0",
"std-env": "3.8.0", "std-env": "3.8.0",
"tinyexec": "0.3.2", "tinyexec": "0.3.2",
"tinyglobby": "0.2.10", "tinyglobby": "0.2.10",
@ -127,7 +130,7 @@
"typescript": "5.7.3", "typescript": "5.7.3",
"ufo": "1.5.4", "ufo": "1.5.4",
"unbuild": "3.3.1", "unbuild": "3.3.1",
"vitest": "3.0.3", "vitest": "3.0.4",
"vitest-environment-nuxt": "1.0.1", "vitest-environment-nuxt": "1.0.1",
"vue": "3.5.13", "vue": "3.5.13",
"vue-tsc": "2.2.0", "vue-tsc": "2.2.0",

View File

@ -27,7 +27,6 @@
"test:attw": "attw --pack" "test:attw": "attw --pack"
}, },
"dependencies": { "dependencies": {
"@nuxt/schema": "workspace:*",
"c12": "^2.0.1", "c12": "^2.0.1",
"consola": "^3.4.0", "consola": "^3.4.0",
"defu": "^6.1.4", "defu": "^6.1.4",
@ -46,19 +45,20 @@
"std-env": "^3.8.0", "std-env": "^3.8.0",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"unctx": "^2.4.1", "unctx": "^2.4.1",
"unimport": "^3.14.6", "unimport": "^4.0.0",
"untyped": "^1.5.2" "untyped": "^1.5.2"
}, },
"devDependencies": { "devDependencies": {
"@rspack/core": "1.2.0", "@nuxt/schema": "workspace:*",
"@rspack/core": "1.2.2",
"@types/semver": "7.5.8", "@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", "unbuild": "3.3.1",
"vite": "6.0.11", "vite": "6.0.11",
"vitest": "3.0.3", "vitest": "3.0.4",
"webpack": "5.97.1" "webpack": "5.97.1"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.12.0"
} }
} }

View File

@ -1,7 +1,6 @@
import { kebabCase, pascalCase } from 'scule' import { kebabCase, pascalCase } from 'scule'
import type { Component, ComponentsDir } from '@nuxt/schema' import type { Component, ComponentsDir } from '@nuxt/schema'
import { useNuxt } from './context' import { useNuxt } from './context'
import { checkNuxtVersion } from './compatibility'
import { logger } from './logger' import { logger } from './logger'
import { MODE_RE } from './utils' import { MODE_RE } from './utils'
@ -10,9 +9,6 @@ import { MODE_RE } from './utils'
*/ */
export function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) { export function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) {
const nuxt = useNuxt() const nuxt = useNuxt()
if (!checkNuxtVersion('>=2.13', nuxt)) {
throw new Error(`\`addComponentsDir\` requires Nuxt 2.13 or higher.`)
}
nuxt.options.components ||= [] nuxt.options.components ||= []
dir.priority ||= 0 dir.priority ||= 0
nuxt.hook('components:dirs', (dirs) => { dirs[opts.prepend ? 'unshift' : 'push'](dir) }) 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) { export function addComponent (opts: AddComponentOptions) {
const nuxt = useNuxt() const nuxt = useNuxt()
if (!checkNuxtVersion('>=2.13', nuxt)) {
throw new Error(`\`addComponent\` requires Nuxt 2.13 or higher.`)
}
nuxt.options.components ||= [] nuxt.options.components ||= []
if (!opts.mode) { if (!opts.mode) {

View File

@ -1,27 +1,21 @@
import { existsSync } from 'node:fs' import { existsSync } from 'node:fs'
import { pathToFileURL } from 'node:url'
import type { JSValue } from 'untyped' import type { JSValue } from 'untyped'
import { applyDefaults } from 'untyped' import { applyDefaults } from 'untyped'
import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12' import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12'
import { loadConfig } from 'c12' import { loadConfig } from 'c12'
import type { NuxtConfig, NuxtOptions } from '@nuxt/schema' import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
import { NuxtConfigSchema } from '@nuxt/schema'
import { globby } from 'globby' import { globby } from 'globby'
import defu from 'defu' import defu from 'defu'
import { join } from 'pathe' import { join } from 'pathe'
import { isWindows } from 'std-env'
import { tryResolveModule } from '../internal/esm'
export interface LoadNuxtConfigOptions extends Omit<LoadConfigOptions<NuxtConfig>, 'overrides'> { export interface LoadNuxtConfigOptions extends Omit<LoadConfigOptions<NuxtConfig>, 'overrides'> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
overrides?: Exclude<LoadConfigOptions<NuxtConfig>['overrides'], Promise<any> | Function> 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> { export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
// Automatically detect and import layers from `~~/layers/` directory // Automatically detect and import layers from `~~/layers/` directory
opts.overrides = defu(opts.overrides, { 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') 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 _layers: ConfigLayer<NuxtConfig, ConfigLayerMeta>[] = []
const processedLayers = new Set<string>() const processedLayers = new Set<string>()
for (const layer of layers) { for (const layer of layers) {
@ -89,3 +93,13 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
// Resolve and apply defaults // Resolve and apply defaults
return await applyDefaults(NuxtConfigSchema, nuxtConfig as NuxtConfig & Record<string, JSValue>) as unknown as NuxtOptions 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)
}

View 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)
})
})

View File

@ -94,7 +94,7 @@ function applyEnv (
return obj return obj
} }
const envExpandRx = /\{\{(.*?)\}\}/g const envExpandRx = /\{\{([^{}]*)\}\}/g
function _expandFromEnv (value: string, env: Record<string, any> = process.env) { function _expandFromEnv (value: string, env: Record<string, any> = process.env) {
return value.replace(envExpandRx, (match, key) => { return value.replace(envExpandRx, (match, key) => {

View File

@ -2,7 +2,7 @@ import { fileURLToPath } from 'node:url'
import { bench, describe } from 'vitest' import { bench, describe } from 'vitest'
import { join, normalize } from 'pathe' import { join, normalize } from 'pathe'
import { withoutTrailingSlash } from 'ufo' import { withoutTrailingSlash } from 'ufo'
import { loadNuxtConfig } from '../src' import { loadNuxtConfig } from '@nuxt/kit'
const fixtures = { const fixtures = {
'empty directory': 'node_modules/fixture', 'empty directory': 'node_modules/fixture',
@ -16,7 +16,7 @@ describe('loadNuxtConfig', () => {
for (const fixture in fixtures) { for (const fixture in fixtures) {
const relativeDir = join('../../..', fixtures[fixture as keyof typeof fixtures]) const relativeDir = join('../../..', fixtures[fixture as keyof typeof fixtures])
const path = withoutTrailingSlash(normalize(fileURLToPath(new URL(relativeDir, import.meta.url)))) const path = withoutTrailingSlash(normalize(fileURLToPath(new URL(relativeDir, import.meta.url))))
bench(fixture, async () => { bench(`loadNuxtConfig in the ${fixture}`, async () => {
await loadNuxtConfig({ cwd: path }) await loadNuxtConfig({ cwd: path })
}) })
} }

View File

@ -2,7 +2,7 @@ import { fileURLToPath } from 'node:url'
import { afterAll, bench, describe } from 'vitest' import { afterAll, bench, describe } from 'vitest'
import { join, normalize } from 'pathe' import { join, normalize } from 'pathe'
import { withoutTrailingSlash } from 'ufo' import { withoutTrailingSlash } from 'ufo'
import { loadNuxt, writeTypes } from '../src' import { loadNuxt, writeTypes } from '@nuxt/kit'
describe('writeTypes', async () => { describe('writeTypes', async () => {
const relativeDir = join('../../..', 'test/fixtures/basic-types') const relativeDir = join('../../..', 'test/fixtures/basic-types')
@ -13,7 +13,7 @@ describe('writeTypes', async () => {
await nuxt.close() await nuxt.close()
}) })
bench('write types', async () => { bench('writeTypes in the basic-types fixture', async () => {
await writeTypes(nuxt) await writeTypes(nuxt)
}) })
}) })

View File

@ -90,7 +90,7 @@
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"globby": "^14.0.2", "globby": "^14.0.2",
"h3": "npm:h3-nightly@1.13.1-20250110-173418-de24917", "h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
"hookable": "^5.5.3", "hookable": "^5.5.3",
"ignore": "^7.0.3", "ignore": "^7.0.3",
"impound": "^0.2.0", "impound": "^0.2.0",
@ -100,8 +100,8 @@
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"mlly": "^1.7.4", "mlly": "^1.7.4",
"nanotar": "^0.2.0", "nanotar": "^0.2.0",
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395", "nitro": "npm:nitro-nightly@3.0.0-beta-28968142.ce23e942",
"nypm": "^0.5.0", "nypm": "^0.5.2",
"ofetch": "^1.4.1", "ofetch": "^1.4.1",
"ohash": "^1.1.4", "ohash": "^1.1.4",
"pathe": "^2.0.2", "pathe": "^2.0.2",
@ -119,9 +119,9 @@
"unctx": "^2.4.1", "unctx": "^2.4.1",
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unhead": "^1.11.18", "unhead": "^1.11.18",
"unimport": "^3.14.6", "unimport": "^4.0.0",
"unplugin": "^2.1.2", "unplugin": "^2.1.2",
"unplugin-vue-router": "^0.11.0", "unplugin-vue-router": "^0.11.2",
"unstorage": "^1.14.4", "unstorage": "^1.14.4",
"untyped": "^1.5.2", "untyped": "^1.5.2",
"vue": "^3.5.13", "vue": "^3.5.13",
@ -131,13 +131,13 @@
}, },
"devDependencies": { "devDependencies": {
"@nuxt/scripts": "0.9.5", "@nuxt/scripts": "0.9.5",
"@parcel/watcher": "2.5.0", "@parcel/watcher": "2.5.1",
"@types/estree": "1.0.6", "@types/estree": "1.0.6",
"@vitejs/plugin-vue": "5.2.1", "@vitejs/plugin-vue": "5.2.1",
"@vue/compiler-sfc": "3.5.13", "@vue/compiler-sfc": "3.5.13",
"unbuild": "3.3.1", "unbuild": "3.3.1",
"vite": "6.0.11", "vite": "6.0.11",
"vitest": "3.0.3" "vitest": "3.0.4"
}, },
"peerDependencies": { "peerDependencies": {
"@parcel/watcher": "^2.1.0", "@parcel/watcher": "^2.1.0",

View File

@ -132,10 +132,11 @@ export default defineComponent({
ssrHTML.value = getFragmentHTML(instance.vnode.el, true)?.join('') || '' ssrHTML.value = getFragmentHTML(instance.vnode.el, true)?.join('') || ''
const key = `${props.name}_${hashId.value}` const key = `${props.name}_${hashId.value}`
nuxtApp.payload.data[key] ||= {} 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 availableSlots = computed(() => [...ssrHTML.value.matchAll(SLOTNAME_RE)].map(m => m[1]))
const html = computed(() => { const html = computed(() => {
const currentSlots = Object.keys(slots) const currentSlots = Object.keys(slots)

View File

@ -1,12 +1,12 @@
import type { DefineComponent, MaybeRef, VNode } from 'vue' 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 { RouteLocationNormalizedLoaded } from 'vue-router'
import type { PageMeta } from '../../pages/runtime/composables' import type { PageMeta } from '../../pages/runtime/composables'
import { useRoute, useRouter } from '../composables/router' import { useRoute, useRouter } from '../composables/router'
import { useNuxtApp } from '../nuxt' import { useNuxtApp } from '../nuxt'
import { _wrapIf } from './utils' import { _wrapInTransition } from './utils'
import { LayoutMetaSymbol, PageRouteSymbol } from './injections' import { LayoutMetaSymbol, PageRouteSymbol } from './injections'
// @ts-expect-error virtual file // @ts-expect-error virtual file
@ -80,7 +80,7 @@ export default defineComponent({
const transitionProps = route.meta.layoutTransition ?? defaultLayoutTransition const transitionProps = route.meta.layoutTransition ?? defaultLayoutTransition
// We avoid rendering layout transition if there is no layout to render // 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(Suspense, { suspensible: true, onResolve: () => { nextTick(done) } }, {
default: () => h( default: () => h(
LayoutProvider, LayoutProvider,

View File

@ -1,5 +1,5 @@
import { createStaticVNode, h } from 'vue' import { Transition, createStaticVNode, h } from 'vue'
import type { Component, RendererNode, VNode } from 'vue' import type { RendererNode, VNode } from 'vue'
// eslint-disable-next-line // eslint-disable-next-line
import { isString, isPromise, isArray, isObject } from '@vue/shared' import { isString, isPromise, isArray, isObject } from '@vue/shared'
import type { RouteLocationNormalized } from 'vue-router' import type { RouteLocationNormalized } from 'vue-router'
@ -10,9 +10,8 @@ import { START_LOCATION } from '#build/pages'
* Internal utility * Internal utility
* @private * @private
*/ */
export const _wrapIf = (component: Component, props: any, slots: any) => { export const _wrapInTransition = (props: any, children: any) => {
props = props === true ? {} : props return { default: () => import.meta.client && props ? h(Transition, props === true ? {} : props, children) : children.default?.() }
return { default: () => props ? h(component, props, slots) : slots.default?.() }
} }
const ROUTE_KEY_PARENTHESES_RE = /(:\w+)\([^)]+\)/g const ROUTE_KEY_PARENTHESES_RE = /(:\w+)\([^)]+\)/g

View File

@ -18,7 +18,7 @@ export const useRouter: typeof _useRouter = () => {
/** @since 3.0.0 */ /** @since 3.0.0 */
export const useRoute: typeof _useRoute = () => { 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.') 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()) { if (hasInjectionContext()) {

View File

@ -108,6 +108,18 @@ function createWatcher () {
ignored: [isIgnored, /[\\/]node_modules[\\/]/], 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) => { watcher.on('all', (event, path) => {
if (event === 'all' || event === 'ready' || event === 'error' || event === 'raw') { if (event === 'all' || event === 'ready' || event === 'error' || event === 'raw') {
return return

View File

@ -83,7 +83,6 @@ const nightlies = {
export const keyDependencies = [ export const keyDependencies = [
'@nuxt/kit', '@nuxt/kit',
'@nuxt/schema',
] ]
let warnedAboutCompatDate = false let warnedAboutCompatDate = false
@ -414,7 +413,7 @@ async function initNuxt (nuxt: Nuxt) {
await nuxt.callHook('modules:before') await nuxt.callHook('modules:before')
const modulesToInstall = new Map<string | NuxtModule, Record<string, any>>() const modulesToInstall = new Map<string | NuxtModule, Record<string, any>>()
const watchedPaths = new Set<string>() const modulePaths = new Set<string>()
const specifiedModules = new Set<string>() const specifiedModules = new Set<string>()
for (const _mod of nuxt.options.modules) { for (const _mod of nuxt.options.modules) {
@ -432,13 +431,15 @@ async function initNuxt (nuxt: Nuxt) {
`${modulesDir}/*/index{${nuxt.options.extensions.join(',')}}`, `${modulesDir}/*/index{${nuxt.options.extensions.join(',')}}`,
]) ])
for (const mod of layerModules) { for (const mod of layerModules) {
watchedPaths.add(mod) modulePaths.add(mod)
if (specifiedModules.has(mod)) { continue } if (specifiedModules.has(mod)) { continue }
specifiedModules.add(mod) specifiedModules.add(mod)
modulesToInstall.set(mod, {}) modulesToInstall.set(mod, {})
} }
} }
nuxt.options.watch.push(...modulePaths)
// Register user and then ad-hoc modules // Register user and then ad-hoc modules
for (const key of ['modules', '_modules'] as const) { for (const key of ['modules', '_modules'] as const) {
for (const item of nuxt.options[key as 'modules']) { for (const item of nuxt.options[key as 'modules']) {
@ -661,7 +662,7 @@ export default defineNuxtPlugin({
nuxt.hooks.hook('builder:watch', (event, relativePath) => { nuxt.hooks.hook('builder:watch', (event, relativePath) => {
const path = resolve(nuxt.options.srcDir, relativePath) const path = resolve(nuxt.options.srcDir, relativePath)
// Local module patterns // Local module patterns
if (watchedPaths.has(path)) { if (modulePaths.has(path)) {
return nuxt.callHook('restart', { hard: true }) return nuxt.callHook('restart', { hard: true })
} }

View File

@ -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, callback: WalkerCallback): Program
export function parseAndWalk (code: string, sourceFilename: string, object: Partial<WalkOptions>): Program export function parseAndWalk (code: string, sourceFilename: string, object: Partial<WalkOptions>): Program
export function parseAndWalk (code: string, _sourceFilename: string, callback: Partial<WalkOptions> | WalkerCallback) { export function parseAndWalk (code: string, sourceFilename: string, callback: Partial<WalkOptions> | WalkerCallback) {
const ast = parse (code, { sourceType: 'module', ecmaVersion: 'latest', locations: true }) const ast = parse (code, { sourceType: 'module', ecmaVersion: 'latest', locations: true, sourceFile: sourceFilename })
walk(ast, typeof callback === 'function' ? { enter: callback } : callback) walk(ast, typeof callback === 'function' ? { enter: callback } : callback)
return ast return ast
} }

View File

@ -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 type { KeepAliveProps, TransitionProps, VNode } from 'vue'
import { RouterView } from 'vue-router' import { RouterView } from 'vue-router'
import { defu } from 'defu' import { defu } from 'defu'
@ -9,7 +9,7 @@ import type { RouterViewSlotProps } from './utils'
import { RouteProvider } from '#app/components/route-provider' import { RouteProvider } from '#app/components/route-provider'
import { useNuxtApp } from '#app/nuxt' import { useNuxtApp } from '#app/nuxt'
import { useRouter } from '#app/composables/router' 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' import { LayoutMetaSymbol, PageRouteSymbol } from '#app/components/injections'
// @ts-expect-error virtual file // @ts-expect-error virtual file
import { appKeepalive as defaultKeepaliveConfig, appPageTransition as defaultPageTransition } from '#build/nuxt.config.mjs' import { appKeepalive as defaultKeepaliveConfig, appPageTransition as defaultPageTransition } from '#build/nuxt.config.mjs'
@ -101,8 +101,30 @@ export default defineComponent({
nuxtApp.callHook('page:loading:end') nuxtApp.callHook('page:loading:end')
pageLoadingEndHookAlreadyCalled = true pageLoadingEndHookAlreadyCalled = true
} }
previousPageKey = key 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 hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition)
const transitionProps = hasTransition && _mergeTransitionProps([ const transitionProps = hasTransition && _mergeTransitionProps([
props.transition, props.transition,
@ -112,7 +134,7 @@ export default defineComponent({
].filter(Boolean)) ].filter(Boolean))
const keepaliveConfig = props.keepalive ?? routeProps.route.meta.keepalive ?? (defaultKeepaliveConfig as KeepAliveProps) const keepaliveConfig = props.keepalive ?? routeProps.route.meta.keepalive ?? (defaultKeepaliveConfig as KeepAliveProps)
vnode = _wrapIf(Transition, hasTransition && transitionProps, vnode = _wrapInTransition(hasTransition && transitionProps,
wrapInKeepAlive(keepaliveConfig, h(Suspense, { wrapInKeepAlive(keepaliveConfig, h(Suspense, {
suspensible: true, suspensible: true,
onPending: () => nuxtApp.callHook('page:start', routeProps.Component), onPending: () => nuxtApp.callHook('page:start', routeProps.Component),
@ -134,7 +156,7 @@ export default defineComponent({
trackRootNodes: hasTransition, trackRootNodes: hasTransition,
vnodeRef: pageRef, 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' (providerVNode.type as any).name = (routeProps.Component.type as any).name || (routeProps.Component.type as any).__name || 'RouteProvider'
} }
return providerVNode return providerVNode

View File

@ -11,6 +11,7 @@ import { transform } from 'esbuild'
import type { Property } from 'estree' import type { Property } from 'estree'
import type { NuxtPage } from 'nuxt/schema' import type { NuxtPage } from 'nuxt/schema'
import { klona } from 'klona'
import { parseAndWalk, withLocations } from '../core/utils/parse' import { parseAndWalk, withLocations } from '../core/utils/parse'
import { getLoader, uniqueBy } from '../core/utils' import { getLoader, uniqueBy } from '../core/utils'
import { logger, toArray } from '../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]) { if (absolutePath in metaCache && metaCache[absolutePath]) {
return metaCache[absolutePath] return klona(metaCache[absolutePath])
} }
const loader = getLoader(absolutePath) const loader = getLoader(absolutePath)
@ -314,7 +315,7 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr
} }
metaCache[absolutePath] = extractedMeta metaCache[absolutePath] = extractedMeta
return extractedMeta return klona(extractedMeta)
} }
const COLON_RE = /:/g const COLON_RE = /:/g

View File

@ -2,13 +2,13 @@ import { fileURLToPath } from 'node:url'
import { bench, describe } from 'vitest' import { bench, describe } from 'vitest'
import { normalize } from 'pathe' import { normalize } from 'pathe'
import { withoutTrailingSlash } from 'ufo' 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 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)))) const basicTestFixtureDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../test/fixtures/basic', import.meta.url))))
describe('loadNuxt', () => { describe('loadNuxt', () => {
bench('empty directory', async () => { bench('loadNuxt in an empty directory', async () => {
const nuxt = await loadNuxt({ const nuxt = await loadNuxt({
cwd: emptyDir, cwd: emptyDir,
ready: true, ready: true,
@ -16,7 +16,7 @@ describe('loadNuxt', () => {
await nuxt.close() await nuxt.close()
}) })
bench('basic test fixture', async () => { bench('loadNuxt in the basic test fixture', async () => {
const nuxt = await loadNuxt({ const nuxt = await loadNuxt({
cwd: basicTestFixtureDir, cwd: basicTestFixtureDir,
ready: true, ready: true,

View File

@ -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 { compileScript, parse } from '@vue/compiler-sfc'
import * as Parser from 'acorn' import * as Parser from 'acorn'
import { klona } from 'klona'
import { transform as esbuildTransform } from 'esbuild' import { transform as esbuildTransform } from 'esbuild'
import { PageMetaPlugin } from '../src/pages/plugins/page-meta' import { PageMetaPlugin } from '../src/pages/plugins/page-meta'
import { getRouteMeta, normalizeRoutes } from '../src/pages/utils' import { getRouteMeta, normalizeRoutes } from '../src/pages/utils'
@ -8,6 +9,8 @@ import type { NuxtPage } from '../schema'
const filePath = '/app/pages/index.vue' const filePath = '/app/pages/index.vue'
vi.mock('klona', { spy: true })
describe('page metadata', () => { describe('page metadata', () => {
it('should not extract metadata from empty files', async () => { it('should not extract metadata from empty files', async () => {
expect(await getRouteMeta('', filePath)).toEqual({}) expect(await getRouteMeta('', filePath)).toEqual({})
@ -62,11 +65,20 @@ definePageMeta({ name: 'bar' })
}) })
it('should use and invalidate cache', async () => { 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 fileContents = `<script setup>definePageMeta({ foo: 'bar' })</script>`
const meta = await getRouteMeta(fileContents, filePath) const meta = await getRouteMeta(fileContents, filePath)
expect(meta === await getRouteMeta(fileContents, filePath)).toBeTruthy() expect(meta === await getRouteMeta(fileContents, filePath)).toBeTruthy()
expect(meta === await getRouteMeta(fileContents, '/app/pages/other.vue')).toBeFalsy() expect(meta === await getRouteMeta(fileContents, '/app/pages/other.vue')).toBeFalsy()
expect(meta === await getRouteMeta('<template><div>Hi</div></template>' + fileContents, filePath)).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 () => { it('should extract serialisable metadata', async () => {

View File

@ -834,3 +834,41 @@ describe('pages:pathToNitroGlob', () => {
expect(pathToNitroGlob(path)).to.equal(expected) 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 },
},
])
})
})

View File

@ -32,7 +32,7 @@
"dependencies": { "dependencies": {
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0", "@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@rspack/core": "^1.2.0", "@rspack/core": "^1.2.2",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0", "css-minimizer-webpack-plugin": "^7.0.0",
@ -43,7 +43,7 @@
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^9.0.2", "fork-ts-checker-webpack-plugin": "^9.0.2",
"globby": "^14.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", "jiti": "^2.4.2",
"knitwork": "^1.2.0", "knitwork": "^1.2.0",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
@ -75,7 +75,7 @@
"@types/pify": "5.0.4", "@types/pify": "5.0.4",
"@types/webpack-bundle-analyzer": "4.7.0", "@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9", "@types/webpack-hot-middleware": "2.25.9",
"rollup": "4.31.0", "rollup": "4.32.1",
"unbuild": "3.3.1", "unbuild": "3.3.1",
"vue": "3.5.13" "vue": "3.5.13"
}, },

View File

@ -48,17 +48,17 @@
"compatx": "0.1.8", "compatx": "0.1.8",
"esbuild-loader": "4.2.2", "esbuild-loader": "4.2.2",
"file-loader": "6.2.0", "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", "hookable": "5.5.3",
"ignore": "7.0.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", "ofetch": "1.4.1",
"pkg-types": "1.3.1", "pkg-types": "1.3.1",
"sass-loader": "16.0.4", "sass-loader": "16.0.4",
"scule": "1.3.0", "scule": "1.3.0",
"unbuild": "3.3.1", "unbuild": "3.3.1",
"unctx": "2.4.1", "unctx": "2.4.1",
"unimport": "3.14.6", "unimport": "4.0.0",
"untyped": "1.5.2", "untyped": "1.5.2",
"vite": "6.0.11", "vite": "6.0.11",
"vue": "3.5.13", "vue": "3.5.13",

View File

@ -39,5 +39,13 @@ export default defineUntypedSchema({
* @type {(data: { loading?: string }) => string} * @type {(data: { loading?: string }) => string}
*/ */
loadingTemplate, loadingTemplate,
/**
* Set CORS options for the dev server
* @type {typeof import('h3').H3CorsOptions}
*/
cors: {
origin: [/^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/],
},
}, },
}) })

View File

@ -17,7 +17,7 @@
"prerender": "pnpm build && jiti ./lib/prerender" "prerender": "pnpm build && jiti ./lib/prerender"
}, },
"devDependencies": { "devDependencies": {
"@unocss/reset": "65.4.2", "@unocss/reset": "65.4.3",
"beasties": "0.2.0", "beasties": "0.2.0",
"html-validate": "9.1.3", "html-validate": "9.1.3",
"htmlnano": "2.1.1", "htmlnano": "2.1.1",
@ -29,7 +29,7 @@
"svgo": "3.3.2", "svgo": "3.3.2",
"tinyexec": "0.3.2", "tinyexec": "0.3.2",
"tinyglobby": "0.2.10", "tinyglobby": "0.2.10",
"unocss": "65.4.2", "unocss": "65.4.3",
"vite": "6.0.11" "vite": "6.0.11"
} }
} }

View File

@ -26,7 +26,7 @@
}, },
"devDependencies": { "devDependencies": {
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"rollup": "4.31.0", "rollup": "4.32.1",
"unbuild": "3.3.1", "unbuild": "3.3.1",
"vue": "3.5.13" "vue": "3.5.13"
}, },
@ -41,8 +41,9 @@
"defu": "^6.1.4", "defu": "^6.1.4",
"esbuild": "^0.24.2", "esbuild": "^0.24.2",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"externality": "^1.0.2",
"get-port-please": "^3.1.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", "jiti": "^2.4.2",
"knitwork": "^1.2.0", "knitwork": "^1.2.0",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
@ -56,7 +57,7 @@
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unplugin": "^2.1.2", "unplugin": "^2.1.2",
"vite": "^6.0.11", "vite": "^6.0.11",
"vite-node": "^3.0.3", "vite-node": "^3.0.4",
"vite-plugin-checker": "^0.8.0", "vite-plugin-checker": "^0.8.0",
"vue-bundle-renderer": "^2.1.1" "vue-bundle-renderer": "^2.1.1"
}, },

View File

@ -9,7 +9,7 @@ import { getPort } from 'get-port-please'
import { joinURL, withoutLeadingSlash } from 'ufo' import { joinURL, withoutLeadingSlash } from 'ufo'
import { defu } from 'defu' import { defu } from 'defu'
import { env, nodeless } from 'unenv' 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 { ViteConfig } from '@nuxt/schema'
import type { ViteBuildContext } from './vite' import type { ViteBuildContext } from './vite'
import { devStyleSSRPlugin } from './plugins/dev-ssr-css' 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 // @ts-expect-error _skip_transform is a private property
event.node.req._skip_transform = true event.node.req._skip_transform = true
} else if (!useViteCors) { } else if (!useViteCors) {
if (event.method === 'OPTIONS') { const isPreflight = handleCors(event, ctx.nuxt.options.devServer.cors)
appendCorsPreflightHeaders(event, {}) if (isPreflight) {
return null return null
} }
appendCorsHeaders(event, {}) setHeader(event, 'Vary', 'Origin')
} }
// Workaround: vite devmiddleware modifies req.url // Workaround: vite devmiddleware modifies req.url

View 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)
}

View File

@ -8,9 +8,11 @@ import { isFileServingAllowed } from 'vite'
import type { ModuleNode, Plugin as VitePlugin } from 'vite' import type { ModuleNode, Plugin as VitePlugin } from 'vite'
import { getQuery } from 'ufo' import { getQuery } from 'ufo'
import { normalizeViteManifest } from 'vue-bundle-renderer' import { normalizeViteManifest } from 'vue-bundle-renderer'
import { resolve as resolveModule } from 'mlly'
import { distDir } from './dirs' import { distDir } from './dirs'
import type { ViteBuildContext } from './vite' import type { ViteBuildContext } from './vite'
import { isCSS } from './utils' import { isCSS } from './utils'
import { createIsExternal } from './utils/external'
// TODO: Remove this in favor of registerViteNodeMiddleware // TODO: Remove this in favor of registerViteNodeMiddleware
// after Nitropack or h3 allows adding middleware after setup // 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) => { return eventHandler(async (event) => {
const moduleId = decodeURI(event.path).substring(1) const moduleId = decodeURI(event.path).substring(1)
if (moduleId === '/') { if (moduleId === '/') {

View File

@ -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 // https://github.com/vitejs/vite/tree/main/packages/vite/src/node/build.ts#L464-L478
assetFileNames: nuxt.options.dev assetFileNames: nuxt.options.dev
? undefined ? 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: { watch: {

View File

@ -42,7 +42,7 @@
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^9.0.2", "fork-ts-checker-webpack-plugin": "^9.0.2",
"globby": "^14.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", "jiti": "^2.4.2",
"knitwork": "^1.2.0", "knitwork": "^1.2.0",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
@ -73,11 +73,11 @@
}, },
"devDependencies": { "devDependencies": {
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@rspack/core": "1.2.0", "@rspack/core": "1.2.2",
"@types/pify": "5.0.4", "@types/pify": "5.0.4",
"@types/webpack-bundle-analyzer": "4.7.0", "@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9", "@types/webpack-hot-middleware": "2.25.9",
"rollup": "4.31.0", "rollup": "4.32.1",
"unbuild": "3.3.1", "unbuild": "3.3.1",
"vue": "3.5.13" "vue": "3.5.13"
}, },

View File

@ -1,6 +1,7 @@
import pify from 'pify' import pify from 'pify'
import { resolve } from 'pathe' 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 type { IncomingMessage, MultiWatching, ServerResponse } from 'webpack-dev-middleware'
import webpackDevMiddleware from 'webpack-dev-middleware' import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware' import webpackHotMiddleware from 'webpack-hot-middleware'
@ -125,7 +126,7 @@ async function createDevMiddleware (compiler: Compiler) {
}) })
// Register devMiddleware on server // Register devMiddleware on server
const devHandler = wdmToH3Handler(devMiddleware) const devHandler = wdmToH3Handler(devMiddleware, nuxt.options.devServer.cors)
const hotHandler = fromNodeMiddleware(hotMiddleware) const hotHandler = fromNodeMiddleware(hotMiddleware)
await nuxt.callHook('server:devHandler', defineEventHandler(async (event) => { await nuxt.callHook('server:devHandler', defineEventHandler(async (event) => {
const body = await devHandler(event) const body = await devHandler(event)
@ -139,8 +140,20 @@ async function createDevMiddleware (compiler: Compiler) {
} }
// TODO: implement upstream in `webpack-dev-middleware` // 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) => { 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 = {
...event.context.webpack, ...event.context.webpack,
devMiddleware: devMiddleware.context, devMiddleware: devMiddleware.context,

File diff suppressed because it is too large Load Diff

View File

@ -61,7 +61,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`) expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`)
const modules = await analyzeSizes(['node_modules/**/*'], serverDir) 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 const packages = modules.files
.filter(m => m.endsWith('package.json')) .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 serverDir = join(pagesRootDir, '.output/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) 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) 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 const packages = modules.files
.filter(m => m.endsWith('package.json')) .filter(m => m.endsWith('package.json'))

View File

@ -1,10 +1,10 @@
import { resolve } from 'pathe' import { resolve } from 'pathe'
import { configDefaults, coverageConfigDefaults, defineConfig } from 'vitest/config' import { configDefaults, coverageConfigDefaults, defineConfig } from 'vitest/config'
import { isWindows } from 'std-env' import { isWindows } from 'std-env'
// import codspeedPlugin from '@codspeed/vitest-plugin' import codspeedPlugin from '@codspeed/vitest-plugin'
export default defineConfig({ export default defineConfig({
// plugins: [codspeedPlugin()], plugins: [codspeedPlugin()],
resolve: { resolve: {
alias: { alias: {
'#build/nuxt.config.mjs': resolve('./test/mocks/nuxt-config'), '#build/nuxt.config.mjs': resolve('./test/mocks/nuxt-config'),