mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-29 00:52:01 +00:00
Merge remote-tracking branch 'origin/main' into feat-add-auto-import-directives
This commit is contained in:
commit
93722c95e5
@ -1,4 +1,4 @@
|
||||
FROM node:lts@sha256:db5dd2f30cb82a8bdbd16acd4a8f3f2789f5b24f6ce43f98aa041be848c82e45
|
||||
FROM node:lts@sha256:a5e0ed56f2c20b9689e0f7dd498cac7e08d2a3a283e92d9304e7b9b83e3c6ff3
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -fy libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 && \
|
||||
|
4
.github/workflows/autofix-docs.yml
vendored
4
.github/workflows/autofix-docs.yml
vendored
@ -17,9 +17,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/autofix.yml
vendored
4
.github/workflows/autofix.yml
vendored
@ -13,9 +13,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
6
.github/workflows/benchmark.yml
vendored
6
.github/workflows/benchmark.yml
vendored
@ -29,9 +29,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -46,7 +46,7 @@ jobs:
|
||||
run: pnpm build
|
||||
|
||||
- name: Run benchmarks
|
||||
uses: CodSpeedHQ/action@ab07afd34cbbb7a1306e8d14b7cc44e029eee37a # v3.0.0
|
||||
uses: CodSpeedHQ/action@b587655f756aab640e742fec141261bc6f0a569d # v3.0.1
|
||||
with:
|
||||
run: pnpm vitest bench
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
4
.github/workflows/changelog.yml
vendored
4
.github/workflows/changelog.yml
vendored
@ -22,11 +22,11 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
47
.github/workflows/ci.yml
vendored
47
.github/workflows/ci.yml
vendored
@ -37,9 +37,9 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -57,7 +57,7 @@ jobs:
|
||||
run: pnpm build
|
||||
|
||||
- name: Cache dist
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
retention-days: 3
|
||||
name: dist
|
||||
@ -72,10 +72,10 @@ jobs:
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9
|
||||
uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
||||
with:
|
||||
config: |
|
||||
paths:
|
||||
@ -91,7 +91,7 @@ jobs:
|
||||
queries: +security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9
|
||||
uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
||||
with:
|
||||
category: "/language:javascript-typescript"
|
||||
|
||||
@ -107,9 +107,9 @@ jobs:
|
||||
module: ["bundler", "node"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -138,9 +138,9 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -162,9 +162,9 @@ jobs:
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -191,7 +191,7 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
env: ["dev", "built"]
|
||||
builder: ["vite", "webpack"]
|
||||
builder: ["vite", "rspack", "webpack"]
|
||||
context: ["async", "default"]
|
||||
manifest: ["manifest-on", "manifest-off"]
|
||||
payload: ["json", "js"]
|
||||
@ -199,12 +199,18 @@ jobs:
|
||||
exclude:
|
||||
- builder: "webpack"
|
||||
payload: "js"
|
||||
- builder: "rspack"
|
||||
payload: "js"
|
||||
- manifest: "manifest-off"
|
||||
payload: "js"
|
||||
- context: "default"
|
||||
payload: "js"
|
||||
- os: windows-latest
|
||||
payload: "js"
|
||||
- env: "dev"
|
||||
builder: "rspack"
|
||||
- manifest: "manifest-off"
|
||||
builder: "rspack"
|
||||
- env: "dev"
|
||||
builder: "webpack"
|
||||
- manifest: "manifest-off"
|
||||
@ -213,9 +219,9 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: "pnpm"
|
||||
@ -242,7 +248,7 @@ jobs:
|
||||
TEST_PAYLOAD: ${{ matrix.payload }}
|
||||
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }}
|
||||
|
||||
- uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
||||
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
|
||||
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 }}
|
||||
@ -256,7 +262,6 @@ jobs:
|
||||
github.event_name == 'push' &&
|
||||
github.repository == 'nuxt/nuxt' &&
|
||||
!contains(github.event.head_commit.message, '[skip-release]') &&
|
||||
!startsWith(github.event.head_commit.message, 'chore') &&
|
||||
!startsWith(github.event.head_commit.message, 'docs')
|
||||
needs:
|
||||
- lint
|
||||
@ -266,11 +271,11 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -307,11 +312,11 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@ -17,6 +17,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
|
||||
uses: actions/dependency-review-action@a6993e2c61fd5dc440b409aa1d6904921c5e1894 # v4.3.5
|
||||
|
6
.github/workflows/docs-check-links.yml
vendored
6
.github/workflows/docs-check-links.yml
vendored
@ -19,17 +19,17 @@ jobs:
|
||||
steps:
|
||||
# Cache lychee results (e.g. to avoid hitting rate limits)
|
||||
- name: Restore lychee cache
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
|
||||
with:
|
||||
path: .lycheecache
|
||||
key: cache-lychee-${{ github.sha }}
|
||||
restore-keys: cache-lychee-
|
||||
|
||||
# check links with Lychee
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Lychee link checker
|
||||
uses: lycheeverse/lychee-action@5047c2a4052946424ce139fe111135f6d7c0fe0b # for v1.8.0
|
||||
uses: lycheeverse/lychee-action@7cd0af4c74a61395d455af97419279d86aafaede # for v1.8.0
|
||||
with:
|
||||
# arguments with file types to check
|
||||
args: >-
|
||||
|
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
@ -21,9 +21,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/lint-sherif.yml
vendored
4
.github/workflows/lint-sherif.yml
vendored
@ -23,9 +23,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/lint-workflows.yml
vendored
4
.github/workflows/lint-workflows.yml
vendored
@ -23,9 +23,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
# From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
|
||||
- name: Check workflow files
|
||||
uses: docker://rhysd/actionlint:1.7.2@sha256:89d3f90f82781dee3c8724651129634b08cf2241bbd67fcd02a1c5198119fc5e
|
||||
uses: docker://rhysd/actionlint:1.7.3@sha256:7617f05bd698cd2f1c3aedc05bc733ccec92cca0738f3e8722c32c5b42c70ae6
|
||||
with:
|
||||
args: -color
|
||||
|
6
.github/workflows/release-pr.yml
vendored
6
.github/workflows/release-pr.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Ensure action is by maintainer
|
||||
uses: octokit/request-action@872c5c97b3c85c23516a572f02b31401ef82415d # v2.3.1
|
||||
uses: octokit/request-action@dad4362715b7fb2ddedf9772c8670824af564f0d # v2.4.0
|
||||
id: check_role
|
||||
with:
|
||||
route: GET /repos/nuxt/nuxt/collaborators/${{ github.event.comment.user.login }}
|
||||
@ -48,13 +48,13 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ steps.pr.outputs.head_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -19,11 +19,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org/"
|
||||
|
2
.github/workflows/reproduire.yml
vendored
2
.github/workflows/reproduire.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
reproduire:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
|
||||
with:
|
||||
label: needs reproduction
|
||||
|
6
.github/workflows/scorecards.yml
vendored
6
.github/workflows/scorecards.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
if: github.repository == 'nuxt/nuxt' && success()
|
||||
with:
|
||||
name: SARIF file
|
||||
@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9
|
||||
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
||||
if: github.repository == 'nuxt/nuxt' && success()
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
2
.github/workflows/stackblitz-link.yml
vendored
2
.github/workflows/stackblitz-link.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
stackblitz:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: huang-julien/reproduire-sur-stackblitz@9ceccbfbb0f2f9a9a8db2d1f0dd909cf5cfe67aa # v1.0.2
|
||||
with:
|
||||
reproduction-heading: '### Reproduction'
|
||||
|
@ -68,6 +68,10 @@ When importing `@nuxt/test-utils` in your vitest config, It is necessary to have
|
||||
> ie. `vitest.config.m{ts,js}`.
|
||||
::
|
||||
|
||||
::tip
|
||||
It is possible to set environment variables for testing by using the `.env.test` file.
|
||||
::
|
||||
|
||||
### Using a Nuxt Runtime Environment
|
||||
|
||||
By default, `@nuxt/test-utils` will not change your default Vitest environment, so you can do fine-grained opt-in and run Nuxt tests together with other unit tests.
|
||||
@ -285,7 +289,7 @@ import { mockNuxtImport } from '@nuxt/test-utils/runtime'
|
||||
|
||||
const { useStorageMock } = vi.hoisted(() => {
|
||||
return {
|
||||
useStorageMock: vi.fn().mockImplementation(() => {
|
||||
useStorageMock: vi.fn(() => {
|
||||
return { value: 'mocked storage'}
|
||||
})
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ export default defineNuxtConfig({
|
||||
// app: 'app'
|
||||
// },
|
||||
// experimental: {
|
||||
// scanPageMeta: 'after-resolve',
|
||||
// sharedPrerenderData: false,
|
||||
// compileTemplate: true,
|
||||
// resetAsyncDataToUndefined: true,
|
||||
@ -178,6 +179,7 @@ nuxt.config.ts
|
||||
1. Create a new directory called `app/`.
|
||||
1. Move your `assets/`, `components/`, `composables/`, `layouts/`, `middleware/`, `pages/`, `plugins/` and `utils/` folders under it, as well as `app.vue`, `error.vue`, `app.config.ts`. If you have an `app/router-options.ts` or `app/spa-loading-template.html`, these paths remain the same.
|
||||
1. Make sure your `nuxt.config.ts`, `content/`, `layers/`, `modules/`, `public/` and `server/` folders remain outside the `app/` folder, in the root of your project.
|
||||
1. Remember to update any third-party configuration files to work with the new directory structure, such as your `tailwindcss` or `eslint` configuration (if required - `@nuxtjs/tailwindcss` should automatically configure `tailwindcss` correctly).
|
||||
|
||||
::tip
|
||||
You can automate this migration by running `npx codemod@latest nuxt/4/file-structure`
|
||||
@ -235,6 +237,45 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
#### Scan Page Meta After Resolution
|
||||
|
||||
🚦 **Impact Level**: Minimal
|
||||
|
||||
##### What Changed
|
||||
|
||||
We now scan page metadata (defined in `definePageMeta`) _after_ calling the `pages:extend` hook rather than before.
|
||||
|
||||
##### Reasons for Change
|
||||
|
||||
This was to allow scanning metadata for pages that users wanted to add in `pages:extend`. We still offer an opportunity to change or override page metadata in a new `pages:resolved` hook.
|
||||
|
||||
##### Migration Steps
|
||||
|
||||
If you want to override page metadata, do that in `pages:resolved` rather than in `pages:extend`.
|
||||
|
||||
```diff
|
||||
export default defineNuxtConfig({
|
||||
hooks: {
|
||||
- 'pages:extend'(pages) {
|
||||
+ 'pages:resolved'(pages) {
|
||||
const myPage = pages.find(page => page.path === '/')
|
||||
myPage.meta ||= {}
|
||||
myPage.meta.layout = 'overridden-layout'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Alternatively, you can revert to the previous behaviour with:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
experimental: {
|
||||
scanPageMeta: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Shared Prerender Data
|
||||
|
||||
🚦 **Impact Level**: Medium
|
||||
|
@ -530,7 +530,7 @@ export default defineNuxtConfig({
|
||||
hooks: {
|
||||
'build:manifest': (manifest) => {
|
||||
// find the app entry, css list
|
||||
const css = manifest['node_modules/nuxt/dist/app/entry.js']?.css
|
||||
const css = Object.values(manifest).find(options => options.isEntry)?.css
|
||||
if (css) {
|
||||
// start from the end of the array and go to the beginning
|
||||
for (let i = css.length - 1; i >= 0; i--) {
|
||||
|
@ -15,7 +15,7 @@ This file system routing uses naming conventions to create dynamic and nested ro
|
||||
::code-group
|
||||
|
||||
```bash [Directory Structure]
|
||||
| pages/
|
||||
-| pages/
|
||||
---| about.vue
|
||||
---| index.vue
|
||||
---| posts/
|
||||
|
@ -8,21 +8,17 @@ Nuxt comes with two composables and a built-in library to perform data-fetching
|
||||
|
||||
In a nutshell:
|
||||
|
||||
- [`useFetch`](/docs/api/composables/use-fetch) is the most straightforward way to handle data fetching in a component setup function.
|
||||
- [`$fetch`](/docs/api/utils/dollarfetch) is great to make network requests based on user interaction.
|
||||
- [`useAsyncData`](/docs/api/composables/use-async-data), combined with `$fetch`, offers more fine-grained control.
|
||||
- [`$fetch`](/docs/api/utils/dollarfetch) is the simplest way to make a network request.
|
||||
- [`useFetch`](/docs/api/composables/use-fetch) is wrapper around `$fetch` that fetches data only once in [universal rendering](/docs/guide/concepts/rendering#universal-rendering).
|
||||
- [`useAsyncData`](/docs/api/composables/use-async-data) is similar to `useFetch` but offers more fine-grained control.
|
||||
|
||||
Both `useFetch` and `useAsyncData` share a common set of options and patterns that we will detail in the last sections.
|
||||
|
||||
Before that, it's imperative to know why these composables exist in the first place.
|
||||
## The need for `useFetch` and `useAsyncData`
|
||||
|
||||
## Why use specific composables for data fetching?
|
||||
Nuxt is a framework which can run isomorphic (or universal) code in both server and client environments. If the [`$fetch` function](/docs/api/utils/dollarfetch) is used to perform data fetching in the setup function of a Vue component, this may cause data to be fetched twice, once on the server (to render the HTML) and once again on the client (when the HTML is hydrated). This can cause hydration issues, increase the time to interactivity and cause unpredictable behavior.
|
||||
|
||||
Nuxt is a framework which can run isomorphic (or universal) code in both server and client environments. If the [`$fetch` function](/docs/api/utils/dollarfetch) is used to perform data fetching in the setup function of a Vue component, this may cause data to be fetched twice, once on the server (to render the HTML) and once again on the client (when the HTML is hydrated). This is why Nuxt offers specific data fetching composables so data is fetched only once.
|
||||
|
||||
### Network calls duplication
|
||||
|
||||
The [`useFetch`](/docs/api/composables/use-fetch) and [`useAsyncData`](/docs/api/composables/use-async-data) composables ensure that once an API call is made on the server, the data is properly forwarded to the client in the payload.
|
||||
The [`useFetch`](/docs/api/composables/use-fetch) and [`useAsyncData`](/docs/api/composables/use-async-data) composables solve this problem by ensuring that if an API call is made on the server, the data is forwarded to the client in the payload.
|
||||
|
||||
The payload is a JavaScript object accessible through [`useNuxtApp().payload`](/docs/api/composables/use-nuxt-app#payload). It is used on the client to avoid refetching the same data when the code is executed in the browser [during hydration](/docs/guide/concepts/rendering#universal-rendering).
|
||||
|
||||
@ -30,17 +26,71 @@ The payload is a JavaScript object accessible through [`useNuxtApp().payload`](/
|
||||
Use the [Nuxt DevTools](https://devtools.nuxt.com) to inspect this data in the **Payload tab**.
|
||||
::
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
const { data } = await useFetch('/api/data')
|
||||
|
||||
async function handleFormSubmit() {
|
||||
const res = await $fetch('/api/submit', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
// My form data
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="data == null">
|
||||
No data
|
||||
</div>
|
||||
<div v-else>
|
||||
<form @submit="handleFormSubmit">
|
||||
<!-- form input tags -->
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
In the example above, `useFetch` would make sure that the request would occur in the server and is properly forwarded to the browser. `$fetch` has no such mechanism and is a better option to use when the request is solely made from the browser.
|
||||
|
||||
### Suspense
|
||||
|
||||
Nuxt uses Vue’s [`<Suspense>`](https://vuejs.org/guide/built-ins/suspense) component under the hood to prevent navigation before every async data is available to the view. The data fetching composables can help you leverage this feature and use what suits best on a per-calls basis.
|
||||
Nuxt uses Vue’s [`<Suspense>`](https://vuejs.org/guide/built-ins/suspense) component under the hood to prevent navigation before every async data is available to the view. The data fetching composables can help you leverage this feature and use what suits best on a per-call basis.
|
||||
|
||||
::note
|
||||
You can add the [`<NuxtLoadingIndicator>`](/docs/api/components/nuxt-loading-indicator) to add a progress bar between page navigations.
|
||||
::
|
||||
|
||||
## `$fetch`
|
||||
|
||||
Nuxt includes the [ofetch](https://github.com/unjs/ofetch) library, and is auto-imported as the `$fetch` alias globally across your application.
|
||||
|
||||
```vue twoslash [pages/todos.vue]
|
||||
<script setup lang="ts">
|
||||
async function addTodo() {
|
||||
const todo = await $fetch('/api/todos', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
// My todo data
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
::warning
|
||||
Beware that using only `$fetch` will not provide [network calls de-duplication and navigation prevention](#the-need-for-usefetch-and-useasyncdata). :br
|
||||
It is recommended to use `$fetch` for client-side interactions (event based) or combined with [`useAsyncData`](#useasyncdata) when fetching the initial component data.
|
||||
::
|
||||
|
||||
::read-more{to="/docs/api/utils/dollarfetch"}
|
||||
Read more about `$fetch`.
|
||||
::
|
||||
|
||||
## `useFetch`
|
||||
|
||||
The [`useFetch`](/docs/api/composables/use-fetch) composable is the most straightforward way to perform data fetching.
|
||||
The [`useFetch`](/docs/api/composables/use-fetch) composable uses `$fetch` under-the-hood to make SSR-safe network calls in the setup function.
|
||||
|
||||
```vue twoslash [app.vue]
|
||||
<script setup lang="ts">
|
||||
@ -62,32 +112,6 @@ Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way!
|
||||
|
||||
:link-example{to="/docs/examples/features/data-fetching"}
|
||||
|
||||
## `$fetch`
|
||||
|
||||
Nuxt includes the [ofetch](https://github.com/unjs/ofetch) library, and is auto-imported as the `$fetch` alias globally across your application. It's what `useFetch` uses behind the scenes.
|
||||
|
||||
```vue twoslash [pages/todos.vue]
|
||||
<script setup lang="ts">
|
||||
async function addTodo() {
|
||||
const todo = await $fetch('/api/todos', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
// My todo data
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
::warning
|
||||
Beware that using only `$fetch` will not provide [network calls de-duplication and navigation prevention](#why-use-specific-composables-for-data-fetching). :br
|
||||
It is recommended to use `$fetch` for client-side interactions (event based) or combined with [`useAsyncData`](#useasyncdata) when fetching the initial component data.
|
||||
::
|
||||
|
||||
::read-more{to="/docs/api/utils/dollarfetch"}
|
||||
Read more about `$fetch`.
|
||||
::
|
||||
|
||||
## `useAsyncData`
|
||||
|
||||
The `useAsyncData` composable is responsible for wrapping async logic and returning the result once it is resolved.
|
||||
|
@ -11,18 +11,41 @@ By default, Nuxt uses **universal rendering** to provide better user experience,
|
||||
|
||||
## Universal Rendering
|
||||
|
||||
When the browser requests a URL with universal (server-side + client-side) rendering enabled, the server returns a fully rendered HTML page to the browser. Whether the page has been generated in advance and cached or is rendered on the fly, at some point, Nuxt has run the JavaScript (Vue.js) code in a server environment, producing an HTML document. Users immediately get the content of our application, contrary to client-side rendering. This step is similar to traditional **server-side rendering** performed by PHP or Ruby applications.
|
||||
This step is similar to traditional **server-side rendering** performed by PHP or Ruby applications. When the browser requests a URL with universal rendering enabled, Nuxt runs the JavaScript (Vue.js) code in a server environment and returns a fully rendered HTML page to the browser. Nuxt may also return a fully rendered HTML page from a cache if the page was generated in advance. Users immediately get the entirety of the initial content of the application, contrary to client-side rendering.
|
||||
|
||||
To not lose the benefits of the client-side rendering method, such as dynamic interfaces and pages transitions, the Client (browser) loads the JavaScript code that runs on the Server in the background once the HTML document has been downloaded. The browser interprets it again (hence **Universal rendering**) and Vue.js takes control of the document and enables interactivity.
|
||||
|
||||
Making a static page interactive in the browser is called "Hydration".
|
||||
Once the HTML document has been downloaded, the browser interprets this and Vue.js takes control of the document. The same JavaScript code that once ran on the server runs on the client (browser) **again** in the background now enabling interactivity (hence **Universal rendering**) by binding its listeners to the HTML. This is called **Hydration**. When hydration is complete, the page can enjoy benefits such as dynamic interfaces and page transitions.
|
||||
|
||||
Universal rendering allows a Nuxt application to provide quick page load times while preserving the benefits of client-side rendering. Furthermore, as the content is already present in the HTML document, crawlers can index it without overhead.
|
||||
|
||||
![Users can access the static content when the HTML document is loaded. Hydration then allows page's interactivity](/assets/docs/concepts/rendering/ssr.svg)
|
||||
|
||||
**What's server-rendered and what's client-rendered?**
|
||||
|
||||
It is normal to ask which parts of a Vue file runs on the server and/or the client in universal rendering mode.
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
const counter = ref(0); // executes in server and client environments
|
||||
|
||||
const handleClick = () => {
|
||||
counter.value++; // executes only in a client environment
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>Count: {{ counter }}</p>
|
||||
<button @click="handleClick">Increment</button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
On the initial request, the `counter` ref is initialized in the server since it is rendered inside the `<p>` tag. The contents of `handleClick` is never executed here. During hydration in the browser, the `counter` ref is re-initialized. The `handleClick` finally binds itself to the button; Therefore it is reasonable to deduce that the body of `handleClick` will always run in a browser environment.
|
||||
|
||||
[Middlewares](/docs/guide/directory-structure/middleware) and [pages](/docs/guide/directory-structure/pages) run in the server and on the client during hydration. [Plugins](/docs/guide/directory-structure/plugins) can be rendered on the server or client or both. [Components](/docs/guide/directory-structure/components) can be forced to run on the client only as well. [Composables](/docs/guide/directory-structure/composables) and [utilities](/docs/guide/directory-structure/utils) are rendered based on the context of their usage.
|
||||
|
||||
**Benefits of server-side rendering:**
|
||||
- **Performance**: Users can get immediate access to the page's content because browsers can display static content much faster than JavaScript-generated content. At the same time, Nuxt preserves the interactivity of a web application when the hydration process happens.
|
||||
- **Performance**: Users can get immediate access to the page's content because browsers can display static content much faster than JavaScript-generated content. At the same time, Nuxt preserves the interactivity of a web application during the hydration process.
|
||||
- **Search Engine Optimization**: Universal rendering delivers the entire HTML content of the page to the browser as a classic server application. Web crawlers can directly index the page's content, which makes Universal rendering a great choice for any content that you want to index quickly.
|
||||
|
||||
**Downsides of server-side rendering:**
|
||||
|
@ -8,9 +8,9 @@ navigation.icon: i-ph-folder
|
||||
Nuxt automatically imports any components in this directory (along with components that are registered by any modules you may be using).
|
||||
|
||||
```bash [Directory Structure]
|
||||
| components/
|
||||
--| AppHeader.vue
|
||||
--| AppFooter.vue
|
||||
-| components/
|
||||
---| AppHeader.vue
|
||||
---| AppFooter.vue
|
||||
```
|
||||
|
||||
```html [app.vue]
|
||||
@ -28,10 +28,10 @@ Nuxt automatically imports any components in this directory (along with componen
|
||||
If you have a component in nested directories such as:
|
||||
|
||||
```bash [Directory Structure]
|
||||
| components/
|
||||
--| base/
|
||||
----| foo/
|
||||
------| Button.vue
|
||||
-| components/
|
||||
---| base/
|
||||
-----| foo/
|
||||
-------| Button.vue
|
||||
```
|
||||
|
||||
... then the component's name will be based on its own path directory and filename, with duplicate segments being removed. Therefore, the component's name will be:
|
||||
@ -285,8 +285,8 @@ export default defineNuxtConfig({
|
||||
Now you can register server-only components with the `.server` suffix and use them anywhere in your application automatically.
|
||||
|
||||
```bash [Directory Structure]
|
||||
| components/
|
||||
--| HighlightedMarkdown.server.vue
|
||||
-| components/
|
||||
---| HighlightedMarkdown.server.vue
|
||||
```
|
||||
|
||||
```vue [pages/example.vue]
|
||||
@ -359,9 +359,9 @@ Slots can be interactive and are wrapped within a `<div>` with `display: content
|
||||
In this case, the `.server` + `.client` components are two 'halves' of a component and can be used in advanced use cases for separate implementations of a component on server and client side.
|
||||
|
||||
```bash [Directory Structure]
|
||||
| components/
|
||||
--| Comments.client.vue
|
||||
--| Comments.server.vue
|
||||
-| components/
|
||||
---| Comments.client.vue
|
||||
---| Comments.server.vue
|
||||
```
|
||||
|
||||
```vue [pages/example.vue]
|
||||
@ -389,15 +389,15 @@ You can use the `components:dirs` hook to extend the directory list without requ
|
||||
Imagine a directory structure like this:
|
||||
|
||||
```bash [Directory Structure]
|
||||
| node_modules/
|
||||
-| node_modules/
|
||||
---| awesome-ui/
|
||||
------| components/
|
||||
---------| Alert.vue
|
||||
---------| Button.vue
|
||||
------| nuxt.js
|
||||
| pages/
|
||||
-----| components/
|
||||
-------| Alert.vue
|
||||
-------| Button.vue
|
||||
-----| nuxt.js
|
||||
-| pages/
|
||||
---| index.vue
|
||||
| nuxt.config.js
|
||||
-| nuxt.config.js
|
||||
```
|
||||
|
||||
Then in `awesome-ui/nuxt.js` you can use the `components:dirs` hook:
|
||||
|
@ -85,11 +85,11 @@ export const useHello = () => {
|
||||
Nuxt only scans files at the top level of the [`composables/` directory](/docs/guide/directory-structure/composables), e.g.:
|
||||
|
||||
```bash [Directory Structure]
|
||||
| composables/
|
||||
-| composables/
|
||||
---| index.ts // scanned
|
||||
---| useFoo.ts // scanned
|
||||
-----| nested/
|
||||
-------| utils.ts // not scanned
|
||||
---| nested/
|
||||
-----| utils.ts // not scanned
|
||||
```
|
||||
|
||||
Only `composables/index.ts` and `composables/useFoo.ts` would be searched for imports.
|
||||
|
@ -72,11 +72,11 @@ Middleware runs in the following order:
|
||||
|
||||
For example, assuming you have the following middleware and component:
|
||||
|
||||
```text [middleware/ directory]
|
||||
middleware/
|
||||
--| analytics.global.ts
|
||||
--| setup.global.ts
|
||||
--| auth.ts
|
||||
```bash [middleware/ directory]
|
||||
-| middleware/
|
||||
---| analytics.global.ts
|
||||
---| setup.global.ts
|
||||
---| auth.ts
|
||||
```
|
||||
|
||||
```vue twoslash [pages/profile.vue]
|
||||
@ -105,11 +105,11 @@ By default, global middleware is executed alphabetically based on the filename.
|
||||
|
||||
However, there may be times you want to define a specific order. For example, in the last scenario, `setup.global.ts` may need to run before `analytics.global.ts`. In that case, we recommend prefixing global middleware with 'alphabetical' numbering.
|
||||
|
||||
```text [Directory structure]
|
||||
middleware/
|
||||
--| 01.setup.global.ts
|
||||
--| 02.analytics.global.ts
|
||||
--| auth.ts
|
||||
```bash [Directory structure]
|
||||
-| middleware/
|
||||
---| 01.setup.global.ts
|
||||
---| 02.analytics.global.ts
|
||||
---| auth.ts
|
||||
```
|
||||
|
||||
::note
|
||||
|
@ -159,7 +159,7 @@ Example:
|
||||
```bash [Directory Structure]
|
||||
-| pages/
|
||||
---| parent/
|
||||
------| child.vue
|
||||
-----| child.vue
|
||||
---| parent.vue
|
||||
```
|
||||
|
||||
@ -408,7 +408,7 @@ However, you can use [Nuxt Layers](/docs/getting-started/layers) to create group
|
||||
```bash [Directory Structure]
|
||||
-| some-app/
|
||||
---| nuxt.config.ts
|
||||
---| pages
|
||||
---| pages/
|
||||
-----| app-page.vue
|
||||
-| nuxt.config.ts
|
||||
```
|
||||
|
@ -108,7 +108,7 @@ In case you're new to 'alphabetical' numbering, remember that filenames are sort
|
||||
|
||||
### Parallel Plugins
|
||||
|
||||
By default, Nuxt loads plugins sequentially. You can define a plugin as `parallel` so Nuxt won't wait the end of the plugin's execution before loading the next plugin.
|
||||
By default, Nuxt loads plugins sequentially. You can define a plugin as `parallel` so Nuxt won't wait until the end of the plugin's execution before loading the next plugin.
|
||||
|
||||
```ts twoslash [plugins/my-plugin.ts]
|
||||
export default defineNuxtPlugin({
|
||||
|
@ -19,6 +19,10 @@ export default defineAppConfig({
|
||||
Do not put any secret values inside `app.config` file. It is exposed to the user client bundle.
|
||||
::
|
||||
|
||||
::note
|
||||
When configuring a custom [`srcDir`](/docs/api/nuxt-config#srcdir), make sure to place the `app.config` file at the root of the new `srcDir` path.
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
To expose config and environment variables to the rest of your app, you will need to define configuration in `app.config` file.
|
||||
@ -31,7 +35,7 @@ export default defineAppConfig({
|
||||
})
|
||||
```
|
||||
|
||||
When adding `theme` to the `app.config`, Nuxt uses Vite or webpack to bundle the code. We can universally access `theme` both when server-rendering the page and in the browser using [`useAppConfig`](/docs/api/composables/use-app-config) composable.
|
||||
We can now universally access `theme` both when server-rendering the page and in the browser using [`useAppConfig`](/docs/api/composables/use-app-config) composable.
|
||||
|
||||
```vue [pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
@ -41,7 +45,23 @@ console.log(appConfig.theme)
|
||||
</script>
|
||||
```
|
||||
|
||||
When configuring a custom [`srcDir`](/docs/api/nuxt-config#srcdir), make sure to place the `app.config` file at the root of the new `srcDir` path.
|
||||
The [`updateAppConfig`](/docs/api/utils/update-app-config) utility can be used to update the `app.config` at runtime.
|
||||
|
||||
```vue [pages/index.vue]
|
||||
<script setup>
|
||||
const appConfig = useAppConfig() // { foo: 'bar' }
|
||||
|
||||
const newAppConfig = { foo: 'baz' }
|
||||
|
||||
updateAppConfig(newAppConfig)
|
||||
|
||||
console.log(appConfig) // { foo: 'baz' }
|
||||
</script>
|
||||
```
|
||||
|
||||
::read-more{to="/docs/api/utils/update-app-config"}
|
||||
Read more about the `updateAppConfig` utility.
|
||||
::
|
||||
|
||||
## Typing App Config
|
||||
|
||||
|
@ -334,6 +334,8 @@ This option allows exposing some route metadata defined in `definePageMeta` at b
|
||||
|
||||
This only works with static or strings/arrays rather than variables or conditional assignment. See [original issue](https://github.com/nuxt/nuxt/issues/24770) for more information and context.
|
||||
|
||||
It is also possible to scan page metadata only after all routes have been registered in `pages:extend`. Then another hook, `pages:resolved` will be called. To enable this behavior, set `scanPageMeta: 'after-resolve'`.
|
||||
|
||||
You can disable this feature if it causes issues in your project.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
|
@ -61,6 +61,7 @@ export default defineNuxtConfig({
|
||||
app: 'app'
|
||||
},
|
||||
experimental: {
|
||||
scanPageMeta: 'after-resolve',
|
||||
sharedPrerenderData: false,
|
||||
compileTemplate: true,
|
||||
resetAsyncDataToUndefined: true,
|
||||
|
@ -31,14 +31,8 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
baseURL: 'https://api.nuxt.com',
|
||||
onRequest({ request, options, error }) {
|
||||
if (session.value?.token) {
|
||||
const headers = options.headers ||= {}
|
||||
if (Array.isArray(headers)) {
|
||||
headers.push(['Authorization', `Bearer ${session.value?.token}`])
|
||||
} else if (headers instanceof Headers) {
|
||||
headers.set('Authorization', `Bearer ${session.value?.token}`)
|
||||
} else {
|
||||
headers.Authorization = `Bearer ${session.value?.token}`
|
||||
}
|
||||
// note that this relies on ofetch >= 1.4.0 - you may need to refresh your lockfile
|
||||
options.headers.set('Authorization', `Bearer ${session.value?.token}`)
|
||||
}
|
||||
},
|
||||
async onResponseError({ response }) {
|
||||
@ -96,6 +90,28 @@ const { data: modules } = await useAPI('/modules')
|
||||
</script>
|
||||
```
|
||||
|
||||
If you want to customize the type of any error returned, you can also do so:
|
||||
|
||||
```ts
|
||||
import type { FetchError } from 'ofetch'
|
||||
import type { UseFetchOptions } from 'nuxt/app'
|
||||
|
||||
interface CustomError {
|
||||
message: string
|
||||
statusCode: number
|
||||
}
|
||||
|
||||
export function useAPI<T>(
|
||||
url: string | (() => string),
|
||||
options?: UseFetchOptions<T>,
|
||||
) {
|
||||
return useFetch<T, FetchError<CustomError>>(url, {
|
||||
...options,
|
||||
$fetch: useNuxtApp().$api
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
::note
|
||||
This example demonstrates how to use a custom `useFetch`, but the same structure is identical for a custom `useAsyncData`.
|
||||
::
|
||||
|
@ -50,8 +50,8 @@ You can also use [interceptors](https://github.com/unjs/ofetch#%EF%B8%8F-interce
|
||||
const { data, status, error, refresh, clear } = await useFetch('/api/auth/login', {
|
||||
onRequest({ request, options }) {
|
||||
// Set the request headers
|
||||
options.headers = options.headers || {}
|
||||
options.headers.authorization = '...'
|
||||
// note that this relies on ofetch >= 1.4.0 - you may need to refresh your lockfile
|
||||
options.headers.set('Authorization', '...')
|
||||
},
|
||||
onRequestError({ request, options, error }) {
|
||||
// Handle the request errors
|
||||
|
48
docs/3.api/2.composables/use-response-header.md
Normal file
48
docs/3.api/2.composables/use-response-header.md
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: "useResponseHeader"
|
||||
description: "Use useResponseHeader to set a server response header."
|
||||
links:
|
||||
- label: Source
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/ssr.ts
|
||||
size: xs
|
||||
---
|
||||
|
||||
::important
|
||||
This composable is available in Nuxt v3.14+.
|
||||
::
|
||||
|
||||
You can use the built-in [`useResponseHeader`](/docs/api/composables/use-response-header) composable to set any server response header within your pages, components, and plugins.
|
||||
|
||||
```ts
|
||||
// Set the a custom response header
|
||||
const header = useResponseHeader('X-My-Header');
|
||||
header.value = 'my-value';
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
We can use `useResponseHeader` to easily set a response header on a per-page basis.
|
||||
|
||||
```vue [pages/test.vue]
|
||||
<script setup>
|
||||
// pages/test.vue
|
||||
const header = useResponseHeader('X-My-Header');
|
||||
header.value = 'my-value';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>Test page with custom header</h1>
|
||||
<p>The response from the server for this "/test" page will have a custom "X-My-Header" header.</p>
|
||||
</template>
|
||||
```
|
||||
|
||||
We can use `useResponseHeader` for example in Nuxt [middleware](/docs/guide/directory-structure/middleware) to set a response header for all pages.
|
||||
|
||||
```ts [middleware/my-header-middleware.ts]
|
||||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
const header = useResponseHeader('X-My-Always-Header');
|
||||
header.value = `I'm Always here!`;
|
||||
});
|
||||
|
||||
```
|
@ -30,6 +30,7 @@ interface PageMeta {
|
||||
redirect?: RouteRecordRedirectOption
|
||||
name?: string
|
||||
path?: string
|
||||
props?: RouteRecordRaw['props']
|
||||
alias?: string | string[]
|
||||
pageTransition?: boolean | TransitionProps
|
||||
layoutTransition?: boolean | TransitionProps
|
||||
@ -63,6 +64,12 @@ interface PageMeta {
|
||||
|
||||
You may define a [custom regular expression](#using-a-custom-regular-expression) if you have a more complex pattern than can be expressed with the file name.
|
||||
|
||||
**`props`**
|
||||
|
||||
- **Type**: [`RouteRecordRaw['props']`](https://router.vuejs.org/guide/essentials/passing-props)
|
||||
|
||||
Allows accessing the route `params` as props passed to the page component.
|
||||
|
||||
**`alias`**
|
||||
|
||||
- **Type**: `string | string[]`
|
||||
|
@ -125,6 +125,19 @@ Make sure to always use `await` or `return` on result of `navigateTo` when calli
|
||||
|
||||
`to` can be a plain string or a route object to redirect to. When passed as `undefined` or `null`, it will default to `'/'`.
|
||||
|
||||
#### Example
|
||||
|
||||
```ts
|
||||
// Passing the URL directly will redirect to the '/blog' page
|
||||
await navigateTo('/blog')
|
||||
|
||||
// Using the route object, will redirect to the route with the name 'blog'
|
||||
await navigateTo({ name: 'blog' })
|
||||
|
||||
// Redirects to the 'product' route while passing a parameter (id = 1) using the route object.
|
||||
await navigateTo({ name: 'product', params: { id: 1 } })
|
||||
```
|
||||
|
||||
### `options` (optional)
|
||||
|
||||
**Type**: `NavigateToOptions`
|
||||
|
@ -3,8 +3,6 @@
|
||||
import { addPluginTemplate, addRouteMiddleware } from 'nuxt/kit'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
typescript: { shim: process.env.DOCS_TYPECHECK === 'true' },
|
||||
pages: process.env.DOCS_TYPECHECK === 'true',
|
||||
modules: [
|
||||
function () {
|
||||
if (!process.env.DOCS_TYPECHECK) { return }
|
||||
@ -18,4 +16,6 @@ export default defineNuxtConfig({
|
||||
})
|
||||
},
|
||||
],
|
||||
pages: process.env.DOCS_TYPECHECK === 'true',
|
||||
typescript: { shim: process.env.DOCS_TYPECHECK === 'true' },
|
||||
})
|
||||
|
68
package.json
68
package.json
@ -35,44 +35,46 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/rspack-builder": "workspace:*",
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/ui-templates": "workspace:*",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@types/node": "20.16.10",
|
||||
"@vue/compiler-core": "3.5.10",
|
||||
"@vue/compiler-dom": "3.5.10",
|
||||
"@vue/shared": "3.5.10",
|
||||
"c12": "2.0.0-beta.3",
|
||||
"@types/node": "20.17.0",
|
||||
"@vue/compiler-core": "3.5.12",
|
||||
"@vue/compiler-dom": "3.5.12",
|
||||
"@vue/shared": "3.5.12",
|
||||
"c12": "2.0.1",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"jiti": "2.0.0",
|
||||
"magic-string": "^0.30.11",
|
||||
"jiti": "2.3.3",
|
||||
"magic-string": "^0.30.12",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nuxt": "workspace:*",
|
||||
"ohash": "1.1.4",
|
||||
"postcss": "8.4.47",
|
||||
"rollup": "4.22.5",
|
||||
"send": ">=0.19.0",
|
||||
"typescript": "5.6.2",
|
||||
"rollup": "4.24.0",
|
||||
"send": ">=1.1.0",
|
||||
"typescript": "5.6.3",
|
||||
"ufo": "1.5.4",
|
||||
"unbuild": "3.0.0-rc.8",
|
||||
"vite": "5.4.8",
|
||||
"vue": "3.5.10"
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vite": "5.4.10",
|
||||
"vue": "3.5.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.11.1",
|
||||
"@nuxt/eslint-config": "0.5.7",
|
||||
"@eslint/js": "9.13.0",
|
||||
"@nuxt/eslint-config": "0.6.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/test-utils": "3.14.2",
|
||||
"@nuxt/rspack-builder": "workspace:*",
|
||||
"@nuxt/test-utils": "3.14.4",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/eslint__js": "8.42.3",
|
||||
"@types/node": "20.16.10",
|
||||
"@types/node": "20.17.0",
|
||||
"@types/semver": "7.5.8",
|
||||
"@unhead/schema": "1.11.6",
|
||||
"@unhead/vue": "1.11.6",
|
||||
"@unhead/schema": "1.11.10",
|
||||
"@unhead/vue": "1.11.10",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vitest/coverage-v8": "2.1.1",
|
||||
"@vitest/coverage-v8": "2.1.3",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"autoprefixer": "10.4.20",
|
||||
"case-police": "0.7.0",
|
||||
@ -81,36 +83,36 @@
|
||||
"cssnano": "7.0.6",
|
||||
"destr": "2.0.3",
|
||||
"devalue": "5.1.1",
|
||||
"eslint": "9.11.1",
|
||||
"eslint": "9.13.0",
|
||||
"eslint-plugin-no-only-tests": "3.3.0",
|
||||
"eslint-plugin-perfectionist": "3.7.0",
|
||||
"eslint-plugin-perfectionist": "3.9.1",
|
||||
"eslint-typegen": "0.3.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"happy-dom": "15.7.4",
|
||||
"jiti": "2.0.0",
|
||||
"jiti": "2.3.3",
|
||||
"markdownlint-cli": "0.42.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nuxi": "3.14.0",
|
||||
"nuxi": "3.15.0",
|
||||
"nuxt": "workspace:*",
|
||||
"nuxt-content-twoslash": "0.1.1",
|
||||
"ofetch": "1.4.0",
|
||||
"ofetch": "1.4.1",
|
||||
"pathe": "1.1.2",
|
||||
"playwright-core": "1.47.2",
|
||||
"playwright-core": "1.48.1",
|
||||
"rimraf": "6.0.1",
|
||||
"semver": "7.6.3",
|
||||
"sherif": "1.0.0",
|
||||
"sherif": "1.0.1",
|
||||
"std-env": "3.7.0",
|
||||
"tinyexec": "0.3.0",
|
||||
"tinyglobby": "0.2.6",
|
||||
"typescript": "5.6.2",
|
||||
"tinyexec": "0.3.1",
|
||||
"tinyglobby": "0.2.9",
|
||||
"typescript": "5.6.3",
|
||||
"ufo": "1.5.4",
|
||||
"vitest": "2.1.1",
|
||||
"vitest": "2.1.3",
|
||||
"vitest-environment-nuxt": "1.0.1",
|
||||
"vue": "3.5.10",
|
||||
"vue": "3.5.12",
|
||||
"vue-router": "4.4.5",
|
||||
"vue-tsc": "2.1.6"
|
||||
},
|
||||
"packageManager": "pnpm@9.11.0",
|
||||
"packageManager": "pnpm@9.12.2",
|
||||
"engines": {
|
||||
"node": "^16.10.0 || >=18.0.0"
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ export default defineBuildConfig({
|
||||
'src/index',
|
||||
],
|
||||
externals: [
|
||||
'@rspack/core',
|
||||
'@nuxt/schema',
|
||||
'nitropack',
|
||||
'nitro',
|
||||
|
@ -27,7 +27,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"c12": "^2.0.0-beta.3",
|
||||
"c12": "^2.0.1",
|
||||
"consola": "^3.2.3",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3",
|
||||
@ -35,25 +35,26 @@
|
||||
"globby": "^14.0.2",
|
||||
"hash-sum": "^2.0.0",
|
||||
"ignore": "^6.0.2",
|
||||
"jiti": "^2.0.0",
|
||||
"jiti": "^2.3.3",
|
||||
"klona": "^2.0.6",
|
||||
"mlly": "^1.7.1",
|
||||
"mlly": "^1.7.2",
|
||||
"pathe": "^1.1.2",
|
||||
"pkg-types": "^1.2.0",
|
||||
"pkg-types": "^1.2.1",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.6.3",
|
||||
"ufo": "^1.5.4",
|
||||
"unctx": "^2.3.1",
|
||||
"unimport": "^3.13.1",
|
||||
"untyped": "^1.5.0"
|
||||
"untyped": "^1.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rspack/core": "1.0.14",
|
||||
"@types/hash-sum": "1.0.2",
|
||||
"@types/semver": "7.5.8",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"unbuild": "3.0.0-rc.8",
|
||||
"vite": "5.4.8",
|
||||
"vitest": "2.1.1",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vite": "5.4.10",
|
||||
"vitest": "2.1.3",
|
||||
"webpack": "5.95.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { Configuration as WebpackConfig, WebpackPluginInstance } from 'webpack'
|
||||
import type { RspackPluginInstance } from '@rspack/core'
|
||||
import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite'
|
||||
import { useNuxt } from './context'
|
||||
import { toArray } from './utils'
|
||||
@ -36,16 +37,7 @@ export interface ExtendWebpackConfigOptions extends ExtendConfigOptions {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface ExtendViteConfigOptions extends ExtendConfigOptions {}
|
||||
|
||||
/**
|
||||
* Extend webpack config
|
||||
*
|
||||
* The fallback function might be called multiple times
|
||||
* when applying to both client and server builds.
|
||||
*/
|
||||
export function extendWebpackConfig (
|
||||
fn: ((config: WebpackConfig) => void),
|
||||
options: ExtendWebpackConfigOptions = {},
|
||||
) {
|
||||
const extendWebpackCompatibleConfig = (builder: 'rspack' | 'webpack') => (fn: ((config: WebpackConfig) => void), options: ExtendWebpackConfigOptions = {}) => {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
if (options.dev === false && nuxt.options.dev) {
|
||||
@ -55,7 +47,7 @@ export function extendWebpackConfig (
|
||||
return
|
||||
}
|
||||
|
||||
nuxt.hook('webpack:config', (configs: WebpackConfig[]) => {
|
||||
nuxt.hook(`${builder}:config`, (configs) => {
|
||||
if (options.server !== false) {
|
||||
const config = configs.find(i => i.name === 'server')
|
||||
if (config) {
|
||||
@ -71,13 +63,25 @@ export function extendWebpackConfig (
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend webpack config
|
||||
*
|
||||
* The fallback function might be called multiple times
|
||||
* when applying to both client and server builds.
|
||||
*/
|
||||
export const extendWebpackConfig = extendWebpackCompatibleConfig('webpack')
|
||||
/**
|
||||
* Extend rspack config
|
||||
*
|
||||
* The fallback function might be called multiple times
|
||||
* when applying to both client and server builds.
|
||||
*/
|
||||
export const extendRspackConfig = extendWebpackCompatibleConfig('rspack')
|
||||
|
||||
/**
|
||||
* Extend Vite config
|
||||
*/
|
||||
export function extendViteConfig (
|
||||
fn: ((config: ViteConfig) => void),
|
||||
options: ExtendViteConfigOptions = {},
|
||||
) {
|
||||
export function extendViteConfig (fn: ((config: ViteConfig) => void), options: ExtendViteConfigOptions = {}) {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
if (options.dev === false && nuxt.options.dev) {
|
||||
@ -114,6 +118,18 @@ export function addWebpackPlugin (pluginOrGetter: WebpackPluginInstance | Webpac
|
||||
config.plugins[method](...toArray(plugin))
|
||||
}, options)
|
||||
}
|
||||
/**
|
||||
* Append rspack plugin to the config.
|
||||
*/
|
||||
export function addRspackPlugin (pluginOrGetter: RspackPluginInstance | RspackPluginInstance[] | (() => RspackPluginInstance | RspackPluginInstance[]), options?: ExtendWebpackConfigOptions) {
|
||||
extendRspackConfig((config) => {
|
||||
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
|
||||
const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter
|
||||
|
||||
config.plugins = config.plugins || []
|
||||
config.plugins[method](...toArray(plugin))
|
||||
}, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Append Vite plugin to the config.
|
||||
@ -131,6 +147,7 @@ export function addVitePlugin (pluginOrGetter: VitePlugin | VitePlugin[] | (() =
|
||||
interface AddBuildPluginFactory {
|
||||
vite?: () => VitePlugin | VitePlugin[]
|
||||
webpack?: () => WebpackPluginInstance | WebpackPluginInstance[]
|
||||
rspack?: () => RspackPluginInstance | RspackPluginInstance[]
|
||||
}
|
||||
|
||||
export function addBuildPlugin (pluginFactory: AddBuildPluginFactory, options?: ExtendConfigOptions) {
|
||||
@ -141,4 +158,8 @@ export function addBuildPlugin (pluginFactory: AddBuildPluginFactory, options?:
|
||||
if (pluginFactory.webpack) {
|
||||
addWebpackPlugin(pluginFactory.webpack, options)
|
||||
}
|
||||
|
||||
if (pluginFactory.rspack) {
|
||||
addRspackPlugin(pluginFactory.rspack, options)
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,13 @@ import { readPackageJSON } from 'pkg-types'
|
||||
import type { Nuxt, NuxtCompatibility, NuxtCompatibilityIssues } from '@nuxt/schema'
|
||||
import { useNuxt } from './context'
|
||||
|
||||
const SEMANTIC_VERSION_RE = /-\d+\.[0-9a-f]+/
|
||||
export function normalizeSemanticVersion (version: string) {
|
||||
return version.replace(/-\d+\.[0-9a-f]+/, '') // Remove edge prefix
|
||||
return version.replace(SEMANTIC_VERSION_RE, '') // Remove edge prefix
|
||||
}
|
||||
|
||||
const builderMap = {
|
||||
'@nuxt/rspack-builder': 'rspack',
|
||||
'@nuxt/vite-builder': 'vite',
|
||||
'@nuxt/webpack-builder': 'webpack',
|
||||
}
|
||||
@ -103,6 +105,7 @@ export function isNuxt3 (nuxt: Nuxt = useNuxt()) {
|
||||
return isNuxtMajorVersion(3, nuxt)
|
||||
}
|
||||
|
||||
const NUXT_VERSION_RE = /^v/g
|
||||
/**
|
||||
* Get nuxt version
|
||||
*/
|
||||
@ -111,5 +114,5 @@ export function getNuxtVersion (nuxt: Nuxt | any = useNuxt() /* TODO: LegacyNuxt
|
||||
if (typeof rawVersion !== 'string') {
|
||||
throw new TypeError('Cannot determine nuxt version! Is current instance passed?')
|
||||
}
|
||||
return rawVersion.replace(/^v/g, '')
|
||||
return rawVersion.replace(NUXT_VERSION_RE, '')
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import type { Component, ComponentsDir } from '@nuxt/schema'
|
||||
import { useNuxt } from './context'
|
||||
import { assertNuxtCompatibility } from './compatibility'
|
||||
import { logger } from './logger'
|
||||
import { MODE_RE } from './utils'
|
||||
|
||||
/**
|
||||
* Register a directory to be scanned for components and imported only when used.
|
||||
@ -28,7 +29,7 @@ export async function addComponent (opts: AddComponentOptions) {
|
||||
nuxt.options.components = nuxt.options.components || []
|
||||
|
||||
if (!opts.mode) {
|
||||
const [, mode = 'all'] = opts.filePath.match(/\.(server|client)(\.\w+)*$/) || []
|
||||
const [, mode = 'all'] = opts.filePath.match(MODE_RE) || []
|
||||
opts.mode = mode as 'all' | 'client' | 'server'
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ export type { LoadNuxtOptions } from './loader/nuxt'
|
||||
// Utils
|
||||
export { addImports, addImportsDir, addImportsSources } from './imports'
|
||||
export { updateRuntimeConfig, useRuntimeConfig } from './runtime-config'
|
||||
export { addBuildPlugin, addVitePlugin, addWebpackPlugin, extendViteConfig, extendWebpackConfig } from './build'
|
||||
export { addBuildPlugin, addVitePlugin, addRspackPlugin, addWebpackPlugin, extendViteConfig, extendRspackConfig, extendWebpackConfig } from './build'
|
||||
export type { ExtendConfigOptions, ExtendViteConfigOptions, ExtendWebpackConfigOptions } from './build'
|
||||
export { assertNuxtCompatibility, checkNuxtCompatibility, getNuxtVersion, hasNuxtCompatibility, isNuxtMajorVersion, normalizeSemanticVersion, isNuxt2, isNuxt3 } from './compatibility'
|
||||
export { addComponent, addComponentsDir } from './components'
|
||||
|
@ -5,10 +5,11 @@ import { useNuxt } from './context'
|
||||
import { logger } from './logger'
|
||||
import { addTemplate } from './template'
|
||||
|
||||
const LAYOUT_RE = /["']/g
|
||||
export function addLayout (template: NuxtTemplate | string, name?: string) {
|
||||
const nuxt = useNuxt()
|
||||
const { filename, src } = addTemplate(template)
|
||||
const layoutName = kebabCase(name || parse(filename).name).replace(/["']/g, '')
|
||||
const layoutName = kebabCase(name || parse(filename).name).replace(LAYOUT_RE, '')
|
||||
|
||||
// Nuxt 3 adds layouts on app
|
||||
nuxt.hook('app:templates', (app) => {
|
||||
|
@ -72,10 +72,7 @@ export const normalizeModuleTranspilePath = (p: string) => {
|
||||
export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, nuxt: Nuxt = useNuxt()) {
|
||||
let buildTimeModuleMeta: ModuleMeta = {}
|
||||
|
||||
const jiti = createJiti(nuxt.options.rootDir, {
|
||||
interopDefault: true,
|
||||
alias: nuxt.options.alias,
|
||||
})
|
||||
const jiti = createJiti(nuxt.options.rootDir, { alias: nuxt.options.alias })
|
||||
|
||||
// Import if input is string
|
||||
if (typeof nuxtModule === 'string') {
|
||||
@ -85,7 +82,7 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const src = jiti.esmResolve(path, { parentURL: parentURL.replace(/\/node_modules\/?$/, '') })
|
||||
nuxtModule = await jiti.import(src) as NuxtModule
|
||||
nuxtModule = await jiti.import(src, { default: true }) as NuxtModule
|
||||
|
||||
// nuxt-module-builder generates a module.json with metadata including the version
|
||||
const moduleMetadataPath = join(dirname(src), 'module.json')
|
||||
|
@ -4,13 +4,14 @@ import { normalize } from 'pathe'
|
||||
import { useNuxt } from './context'
|
||||
import { toArray } from './utils'
|
||||
|
||||
const HANDLER_METHOD_RE = /\.(get|head|patch|post|put|delete|connect|options|trace)(\.\w+)*$/
|
||||
/**
|
||||
* normalize handler object
|
||||
*
|
||||
*/
|
||||
function normalizeHandlerMethod (handler: NitroEventHandler) {
|
||||
// retrieve method from handler file name
|
||||
const [, method = undefined] = handler.handler.match(/\.(get|head|patch|post|put|delete|connect|options|trace)(\.\w+)*$/) || []
|
||||
const [, method = undefined] = handler.handler.match(HANDLER_METHOD_RE) || []
|
||||
return {
|
||||
method: method as 'get' | 'head' | 'patch' | 'post' | 'put' | 'delete' | 'connect' | 'options' | 'trace' | undefined,
|
||||
...handler,
|
||||
|
@ -3,6 +3,7 @@ import type { NuxtPlugin, NuxtPluginTemplate } from '@nuxt/schema'
|
||||
import { useNuxt } from './context'
|
||||
import { addTemplate } from './template'
|
||||
import { resolveAlias } from './resolve'
|
||||
import { MODE_RE } from './utils'
|
||||
|
||||
/**
|
||||
* Normalize a nuxt plugin object
|
||||
@ -27,7 +28,7 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
|
||||
plugin.mode = 'server'
|
||||
}
|
||||
if (!plugin.mode) {
|
||||
const [, mode = 'all'] = plugin.src.match(/\.(server|client)(\.\w+)*$/) || []
|
||||
const [, mode = 'all'] = plugin.src.match(MODE_RE) || []
|
||||
plugin.mode = mode as 'all' | 'client' | 'server'
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { existsSync, promises as fsp } from 'node:fs'
|
||||
import { basename, isAbsolute, join, parse, relative, resolve } from 'pathe'
|
||||
import hash from 'hash-sum'
|
||||
import type { Nuxt, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate, TSReference } from '@nuxt/schema'
|
||||
import type { Nuxt, NuxtServerTemplate, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate, TSReference } from '@nuxt/schema'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import { defu } from 'defu'
|
||||
import type { TSConfig } from 'pkg-types'
|
||||
@ -32,6 +32,18 @@ export function addTemplate<T> (_template: NuxtTemplate<T> | string) {
|
||||
return template
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a virtual file that can be used within the Nuxt Nitro server build.
|
||||
*/
|
||||
export function addServerTemplate (template: NuxtServerTemplate) {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
nuxt.options.nitro.virtual ||= {}
|
||||
nuxt.options.nitro.virtual[template.filename] = template.getContents
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders given types using lodash template during build into the project buildDir
|
||||
* and register them as types.
|
||||
@ -111,6 +123,9 @@ export async function updateTemplates (options?: { filter?: (template: ResolvedN
|
||||
return await tryUseNuxt()?.hooks.callHook('builder:generateApp', options)
|
||||
}
|
||||
|
||||
const EXTENSION_RE = /\b\.\w+$/g
|
||||
// Exclude bridge alias types to support Volar
|
||||
const excludedAlias = [/^@vue\/.*$/, /^#internal\/nuxt/]
|
||||
export async function _generateTypes (nuxt: Nuxt) {
|
||||
const rootDirWithSlash = withTrailingSlash(nuxt.options.rootDir)
|
||||
const relativeRootDir = relativeWithDot(nuxt.options.buildDir, nuxt.options.rootDir)
|
||||
@ -211,13 +226,7 @@ export async function _generateTypes (nuxt: Nuxt) {
|
||||
exclude: [...exclude],
|
||||
} satisfies TSConfig)
|
||||
|
||||
const aliases: Record<string, string> = {
|
||||
...nuxt.options.alias,
|
||||
'#build': nuxt.options.buildDir,
|
||||
}
|
||||
|
||||
// Exclude bridge alias types to support Volar
|
||||
const excludedAlias = [/^@vue\/.*$/]
|
||||
const aliases: Record<string, string> = nuxt.options.alias
|
||||
|
||||
const basePath = tsConfig.compilerOptions!.baseUrl
|
||||
? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl)
|
||||
@ -251,7 +260,7 @@ export async function _generateTypes (nuxt: Nuxt) {
|
||||
} else {
|
||||
const path = stats?.isFile()
|
||||
// remove extension
|
||||
? relativePath.replace(/\b\.\w+$/g, '')
|
||||
? relativePath.replace(EXTENSION_RE, '')
|
||||
// non-existent file probably shouldn't be resolved
|
||||
: aliases[alias]!
|
||||
|
||||
@ -280,7 +289,7 @@ export async function _generateTypes (nuxt: Nuxt) {
|
||||
tsConfig.compilerOptions!.paths[alias] = await Promise.all(paths.map(async (path: string) => {
|
||||
if (!isAbsolute(path)) { return path }
|
||||
const stats = await fsp.stat(path).catch(() => null /* file does not exist */)
|
||||
return relativeWithDot(nuxt.options.buildDir, stats?.isFile() ? path.replace(/\b\.\w+$/g, '') /* remove extension */ : path)
|
||||
return relativeWithDot(nuxt.options.buildDir, stats?.isFile() ? path.replace(EXTENSION_RE, '') /* remove extension */ : path)
|
||||
}))
|
||||
}
|
||||
|
||||
@ -335,6 +344,7 @@ function renderAttr (key: string, value?: string) {
|
||||
return value ? `${key}="${value}"` : ''
|
||||
}
|
||||
|
||||
const RELATIVE_WITH_DOT_RE = /^([^.])/
|
||||
function relativeWithDot (from: string, to: string) {
|
||||
return relative(from, to).replace(/^([^.])/, './$1') || '.'
|
||||
return relative(from, to).replace(RELATIVE_WITH_DOT_RE, './$1') || '.'
|
||||
}
|
||||
|
@ -2,3 +2,5 @@
|
||||
export function toArray<T> (value: T | T[]): T[] {
|
||||
return Array.isArray(value) ? value : [value]
|
||||
}
|
||||
|
||||
export const MODE_RE = /\.(server|client)(\.\w+)*$/
|
||||
|
@ -34,9 +34,6 @@ describe('tsConfig generation', () => {
|
||||
const { tsConfig } = await _generateTypes(mockNuxt)
|
||||
expect(tsConfig.compilerOptions?.paths).toMatchInlineSnapshot(`
|
||||
{
|
||||
"#build": [
|
||||
".",
|
||||
],
|
||||
"some-custom-alias": [
|
||||
"../some-alias",
|
||||
],
|
||||
|
@ -60,19 +60,19 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/devalue": "^2.0.2",
|
||||
"@nuxt/devtools": "^1.5.1",
|
||||
"@nuxt/devtools": "^1.6.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/telemetry": "^2.6.0",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@unhead/dom": "^1.11.6",
|
||||
"@unhead/shared": "^1.11.6",
|
||||
"@unhead/ssr": "^1.11.6",
|
||||
"@unhead/vue": "^1.11.6",
|
||||
"@vue/shared": "^3.5.10",
|
||||
"acorn": "8.12.1",
|
||||
"c12": "^2.0.0-beta.3",
|
||||
"chokidar": "^3.6.0",
|
||||
"@unhead/dom": "^1.11.10",
|
||||
"@unhead/shared": "^1.11.10",
|
||||
"@unhead/ssr": "^1.11.10",
|
||||
"@unhead/vue": "^1.11.10",
|
||||
"@vue/shared": "^3.5.12",
|
||||
"acorn": "8.13.0",
|
||||
"c12": "^2.0.1",
|
||||
"chokidar": "^4.0.1",
|
||||
"compatx": "^0.1.8",
|
||||
"consola": "^3.2.3",
|
||||
"cookie-es": "^1.2.2",
|
||||
@ -87,53 +87,53 @@
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"hookable": "^5.5.3",
|
||||
"ignore": "^6.0.2",
|
||||
"impound": "^0.1.0",
|
||||
"jiti": "^2.0.0",
|
||||
"impound": "^0.2.0",
|
||||
"jiti": "^2.3.3",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.1.0",
|
||||
"magic-string": "^0.30.11",
|
||||
"mlly": "^1.7.1",
|
||||
"magic-string": "^0.30.12",
|
||||
"mlly": "^1.7.2",
|
||||
"nanotar": "^0.1.1",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nuxi": "^3.14.0",
|
||||
"nuxi": "^3.15.0",
|
||||
"nypm": "^0.3.12",
|
||||
"ofetch": "^1.4.0",
|
||||
"ofetch": "^1.4.1",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"pkg-types": "^1.2.0",
|
||||
"pkg-types": "^1.2.1",
|
||||
"radix3": "^1.1.2",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.6.3",
|
||||
"std-env": "^3.7.0",
|
||||
"strip-literal": "^2.1.0",
|
||||
"tinyglobby": "0.2.6",
|
||||
"tinyglobby": "0.2.9",
|
||||
"ufo": "^1.5.4",
|
||||
"ultrahtml": "^1.5.3",
|
||||
"uncrypto": "^0.1.3",
|
||||
"unctx": "^2.3.1",
|
||||
"unenv": "^1.10.0",
|
||||
"unhead": "^1.11.6",
|
||||
"unhead": "^1.11.10",
|
||||
"unimport": "^3.13.1",
|
||||
"unplugin": "^1.14.1",
|
||||
"unplugin-vue-router": "^0.10.8",
|
||||
"unstorage": "^1.12.0",
|
||||
"untyped": "^1.5.0",
|
||||
"vue": "^3.5.10",
|
||||
"untyped": "^1.5.1",
|
||||
"vue": "^3.5.12",
|
||||
"vue-bundle-renderer": "^2.1.1",
|
||||
"vue-devtools-stub": "^0.1.0",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/scripts": "0.9.4",
|
||||
"@nuxt/scripts": "0.9.5",
|
||||
"@nuxt/ui-templates": "1.3.4",
|
||||
"@parcel/watcher": "2.4.1",
|
||||
"@types/estree": "1.0.6",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vue/compiler-sfc": "3.5.10",
|
||||
"unbuild": "3.0.0-rc.8",
|
||||
"vite": "5.4.8",
|
||||
"vitest": "2.1.1"
|
||||
"@vue/compiler-sfc": "3.5.12",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vite": "5.4.10",
|
||||
"vitest": "2.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@parcel/watcher": "^2.1.0",
|
||||
|
@ -1,9 +1,9 @@
|
||||
import type { Component, PropType, VNode } from 'vue'
|
||||
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref, toRaw, watch, withMemo } from 'vue'
|
||||
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch, withMemo } from 'vue'
|
||||
import { debounce } from 'perfect-debounce'
|
||||
import { hash } from 'ohash'
|
||||
import { appendResponseHeader } from 'h3'
|
||||
import { injectHead } from '@unhead/vue'
|
||||
import { type ActiveHeadEntry, type Head, injectHead } from '@unhead/vue'
|
||||
import { randomUUID } from 'uncrypto'
|
||||
import { joinURL, withQuery } from 'ufo'
|
||||
import type { FetchResponse } from 'ofetch'
|
||||
@ -22,6 +22,7 @@ const SSR_UID_RE = /data-island-uid="([^"]*)"/
|
||||
const DATA_ISLAND_UID_RE = /data-island-uid(="")?(?!="[^"])/g
|
||||
const SLOTNAME_RE = /data-island-slot="([^"]*)"/g
|
||||
const SLOT_FALLBACK_RE = / data-island-slot="([^"]*)"[^>]*>/g
|
||||
const ISLAND_SCOPE_ID_RE = /^<[^> ]*/
|
||||
|
||||
let id = 1
|
||||
const getId = import.meta.client ? () => (id++).toString() : randomUUID
|
||||
@ -90,11 +91,13 @@ export default defineComponent({
|
||||
const instance = getCurrentInstance()!
|
||||
const event = useRequestEvent()
|
||||
|
||||
let activeHead: ActiveHeadEntry<Head>
|
||||
|
||||
// TODO: remove use of `$fetch.raw` when nitro 503 issues on windows dev server are resolved
|
||||
const eventFetch = import.meta.server ? event!.fetch : import.meta.dev ? $fetch.raw : globalThis.fetch
|
||||
const mounted = ref(false)
|
||||
onMounted(() => { mounted.value = true; teleportKey.value++ })
|
||||
|
||||
onBeforeUnmount(() => { if (activeHead) { activeHead.dispose() } })
|
||||
function setPayload (key: string, result: NuxtIslandResponse) {
|
||||
const toRevive: Partial<NuxtIslandResponse> = {}
|
||||
if (result.props) { toRevive.props = result.props }
|
||||
@ -140,7 +143,7 @@ export default defineComponent({
|
||||
let html = ssrHTML.value
|
||||
|
||||
if (props.scopeId) {
|
||||
html = html.replace(/^<[^> ]*/, full => full + ' ' + props.scopeId)
|
||||
html = html.replace(ISLAND_SCOPE_ID_RE, full => full + ' ' + props.scopeId)
|
||||
}
|
||||
|
||||
if (import.meta.client && !canLoadClientComponent.value) {
|
||||
@ -215,6 +218,14 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
if (res?.head) {
|
||||
if (activeHead) {
|
||||
activeHead.patch(res.head)
|
||||
} else {
|
||||
activeHead = head.push(res.head)
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.client) {
|
||||
// must await next tick for Teleport to work correctly with static node re-rendering
|
||||
nextTick(() => {
|
||||
@ -250,14 +261,6 @@ export default defineComponent({
|
||||
await loadComponents(props.source, payloads.components)
|
||||
}
|
||||
|
||||
if (import.meta.server || nuxtApp.isHydrating) {
|
||||
// re-push head into active head instance
|
||||
const responseHead = (nuxtApp.payload.data[`${props.name}_${hashId.value}`] as NuxtIslandResponse)?.head
|
||||
if (responseHead) {
|
||||
head.push(responseHead)
|
||||
}
|
||||
}
|
||||
|
||||
return (_ctx: any, _cache: any) => {
|
||||
if (!html.value || error.value) {
|
||||
return [slots.fallback?.({ error: error.value }) ?? createVNode('div')]
|
||||
|
@ -328,8 +328,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
||||
const path = typeof to.value === 'string'
|
||||
? to.value
|
||||
: isExternal.value ? resolveRouteObject(to.value) : router.resolve(to.value).fullPath
|
||||
const normalizedPath = isExternal.value ? new URL(path, window.location.href).href : path
|
||||
await Promise.all([
|
||||
nuxtApp.hooks.callHook('link:prefetch', path).catch(() => {}),
|
||||
nuxtApp.hooks.callHook('link:prefetch', normalizedPath).catch(() => {}),
|
||||
!isExternal.value && !hasTarget.value && preloadRouteComponents(to.value as string, router).catch(() => {}),
|
||||
])
|
||||
}
|
||||
@ -520,11 +521,12 @@ function useObserver (): { observe: ObserveFn } | undefined {
|
||||
return _observer
|
||||
}
|
||||
|
||||
const IS_2G_RE = /2g/
|
||||
function isSlowConnection () {
|
||||
if (import.meta.server) { return }
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/connection
|
||||
const cn = (navigator as any).connection as { saveData: boolean, effectiveType: string } | null
|
||||
if (cn && (cn.saveData || /2g/.test(cn.effectiveType))) { return true }
|
||||
if (cn && (cn.saveData || IS_2G_RE.test(cn.effectiveType))) { return true }
|
||||
return false
|
||||
}
|
||||
|
@ -15,13 +15,16 @@ export const _wrapIf = (component: Component, props: any, slots: any) => {
|
||||
return { default: () => props ? h(component, props, slots) : slots.default?.() }
|
||||
}
|
||||
|
||||
const ROUTE_KEY_PARENTHESES_RE = /(:\w+)\([^)]+\)/g
|
||||
const ROUTE_KEY_SYMBOLS_RE = /(:\w+)[?+*]/g
|
||||
const ROUTE_KEY_NORMAL_RE = /:\w+/g
|
||||
// TODO: consider refactoring into single utility
|
||||
// See https://github.com/nuxt/nuxt/tree/main/packages/nuxt/src/pages/runtime/utils.ts#L8-L19
|
||||
function generateRouteKey (route: RouteLocationNormalized) {
|
||||
const source = route?.meta.key ?? route.path
|
||||
.replace(/(:\w+)\([^)]+\)/g, '$1')
|
||||
.replace(/(:\w+)[?+*]/g, '$1')
|
||||
.replace(/:\w+/g, r => route.params[r.slice(1)]?.toString() || '')
|
||||
.replace(ROUTE_KEY_PARENTHESES_RE, '$1')
|
||||
.replace(ROUTE_KEY_SYMBOLS_RE, '$1')
|
||||
.replace(ROUTE_KEY_NORMAL_RE, r => route.params[r.slice(1)]?.toString() || '')
|
||||
return typeof source === 'function' ? source(route) : source
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,6 @@ export const defineNuxtComponent: typeof defineComponent =
|
||||
}
|
||||
|
||||
if (options.head) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
useHead(typeof options.head === 'function' ? () => options.head(nuxtApp) : options.head)
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ export { useFetch, useLazyFetch } from './fetch'
|
||||
export type { FetchResult, UseFetchOptions } from './fetch'
|
||||
export { useCookie, refreshCookie } from './cookie'
|
||||
export type { CookieOptions, CookieRef } from './cookie'
|
||||
export { onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus } from './ssr'
|
||||
export { onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader } from './ssr'
|
||||
export { onNuxtReady } from './ready'
|
||||
export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter } from './router'
|
||||
export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router'
|
||||
|
@ -114,6 +114,7 @@ export interface NavigateToOptions {
|
||||
open?: OpenOptions
|
||||
}
|
||||
|
||||
const URL_QUOTE_RE = /"/g
|
||||
/** @since 3.0.0 */
|
||||
export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: NavigateToOptions): Promise<void | NavigationFailure | false> | false | void | RouteLocationRaw => {
|
||||
if (!to) {
|
||||
@ -166,7 +167,7 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
|
||||
const redirect = async function (response: any) {
|
||||
// TODO: consider deprecating in favour of `app:rendered` and removing
|
||||
await nuxtApp.callHook('app:redirected')
|
||||
const encodedLoc = location.replace(/"/g, '%22')
|
||||
const encodedLoc = location.replace(URL_QUOTE_RE, '%22')
|
||||
const encodedHeader = encodeURL(location, isExternalHost)
|
||||
|
||||
nuxtApp.ssrContext!._renderResponse = {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { H3Event } from 'h3'
|
||||
import { setResponseStatus as _setResponseStatus, appendHeader, getRequestHeader, getRequestHeaders } from 'h3'
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import { setResponseStatus as _setResponseStatus, appendHeader, getRequestHeader, getRequestHeaders, getResponseHeader, removeResponseHeader, setResponseHeader } from 'h3'
|
||||
import { computed, getCurrentInstance, ref } from 'vue'
|
||||
import { useServerHead } from '@unhead/vue'
|
||||
|
||||
import type { NuxtApp } from '../nuxt'
|
||||
@ -61,6 +61,34 @@ export function setResponseStatus (arg1: H3Event | number | undefined, arg2?: nu
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.14.0 */
|
||||
export function useResponseHeader (header: string) {
|
||||
if (import.meta.client) {
|
||||
if (import.meta.dev) {
|
||||
return computed({
|
||||
get: () => undefined,
|
||||
set: () => console.warn('[nuxt] Setting response headers is not supported in the browser.'),
|
||||
})
|
||||
}
|
||||
return ref()
|
||||
}
|
||||
|
||||
const event = useRequestEvent()!
|
||||
|
||||
return computed({
|
||||
get () {
|
||||
return getResponseHeader(event, header)
|
||||
},
|
||||
set (newValue) {
|
||||
if (!newValue) {
|
||||
return removeResponseHeader(event, header)
|
||||
}
|
||||
|
||||
return setResponseHeader(event, header, newValue)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** @since 3.8.0 */
|
||||
export function prerenderRoutes (path: string | string[]) {
|
||||
if (!import.meta.server || !import.meta.prerender) { return }
|
||||
|
@ -1,7 +1,7 @@
|
||||
export { applyPlugin, applyPlugins, callWithNuxt, createNuxtApp, defineAppConfig, defineNuxtPlugin, definePayloadPlugin, isNuxtPlugin, registerPluginHooks, tryUseNuxtApp, useNuxtApp, useRuntimeConfig } from './nuxt'
|
||||
export type { CreateOptions, NuxtApp, NuxtPayload, NuxtPluginIndicator, NuxtSSRContext, ObjectPlugin, Plugin, PluginEnvContext, PluginMeta, ResolvedPluginMeta, RuntimeNuxtHooks } from './nuxt'
|
||||
|
||||
export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index'
|
||||
export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index'
|
||||
export type { AddRouteMiddlewareOptions, AsyncData, AsyncDataOptions, AsyncDataRequestStatus, CookieOptions, CookieRef, FetchResult, NuxtAppManifest, NuxtAppManifestMeta, NuxtError, ReloadNuxtAppOptions, RouteMiddleware, UseFetchOptions } from './composables/index'
|
||||
|
||||
export { defineNuxtLink } from './components/index'
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { existsSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
|
||||
import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, defineNuxtModule, logger, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, defineNuxtModule, findPath, logger, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit'
|
||||
import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema'
|
||||
|
||||
import { distDir } from '../dirs'
|
||||
@ -16,11 +16,13 @@ import { ComponentNamePlugin } from './plugins/component-names'
|
||||
|
||||
const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string'
|
||||
const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch { return false } }
|
||||
const SLASH_SEPARATOR_RE = /[\\/]/
|
||||
function compareDirByPathLength ({ path: pathA }: { path: string }, { path: pathB }: { path: string }) {
|
||||
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
|
||||
return pathB.split(SLASH_SEPARATOR_RE).filter(Boolean).length - pathA.split(SLASH_SEPARATOR_RE).filter(Boolean).length
|
||||
}
|
||||
|
||||
const DEFAULT_COMPONENTS_DIRS_RE = /\/components(?:\/(?:global|islands))?$/
|
||||
const STARTER_DOT_RE = /^\./g
|
||||
|
||||
export type getComponentsT = (mode?: 'client' | 'server' | 'all') => Component[]
|
||||
|
||||
@ -32,7 +34,7 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
defaults: {
|
||||
dirs: [],
|
||||
},
|
||||
setup (componentOptions, nuxt) {
|
||||
async setup (componentOptions, nuxt) {
|
||||
let componentDirs: ComponentsDir[] = []
|
||||
const context = {
|
||||
components: [] as Component[],
|
||||
@ -89,7 +91,7 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
const dirOptions: ComponentsDir = typeof dir === 'object' ? dir : { path: dir }
|
||||
const dirPath = resolveAlias(dirOptions.path)
|
||||
const transpile = typeof dirOptions.transpile === 'boolean' ? dirOptions.transpile : 'auto'
|
||||
const extensions = (dirOptions.extensions || nuxt.options.extensions).map(e => e.replace(/^\./g, ''))
|
||||
const extensions = (dirOptions.extensions || nuxt.options.extensions).map(e => e.replace(STARTER_DOT_RE, ''))
|
||||
|
||||
const present = isDirectory(dirPath)
|
||||
if (!present && !DEFAULT_COMPONENTS_DIRS_RE.test(dirOptions.path)) {
|
||||
@ -134,8 +136,9 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
addTemplate(componentsMetadataTemplate)
|
||||
}
|
||||
|
||||
addBuildPlugin(TransformPlugin(nuxt, getComponents, 'server'), { server: true, client: false })
|
||||
addBuildPlugin(TransformPlugin(nuxt, getComponents, 'client'), { server: false, client: true })
|
||||
const serverComponentRuntime = await findPath(join(distDir, 'components/runtime/server-component')) ?? join(distDir, 'components/runtime/server-component')
|
||||
addBuildPlugin(TransformPlugin(nuxt, { getComponents, serverComponentRuntime, mode: 'server' }), { server: true, client: false })
|
||||
addBuildPlugin(TransformPlugin(nuxt, { getComponents, serverComponentRuntime, mode: 'client' }), { server: false, client: true })
|
||||
|
||||
// Do not prefetch global components chunks
|
||||
nuxt.hook('build:manifest', (manifest) => {
|
||||
@ -162,7 +165,7 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
}
|
||||
})
|
||||
|
||||
const serverPlaceholderPath = resolve(distDir, 'app/components/server-placeholder')
|
||||
const serverPlaceholderPath = await findPath(join(distDir, 'app/components/server-placeholder')) ?? join(distDir, 'app/components/server-placeholder')
|
||||
|
||||
// Scan components and add to plugin
|
||||
nuxt.hook('app:templates', async (app) => {
|
||||
@ -222,6 +225,7 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
|
||||
const sharedLoaderOptions = {
|
||||
getComponents,
|
||||
serverComponentRuntime,
|
||||
transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined,
|
||||
experimentalComponentIslands: !!nuxt.options.experimental.componentIslands,
|
||||
}
|
||||
@ -272,16 +276,18 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
}
|
||||
})
|
||||
|
||||
nuxt.hook('webpack:config', (configs) => {
|
||||
configs.forEach((config) => {
|
||||
const mode = config.name === 'client' ? 'client' : 'server'
|
||||
config.plugins = config.plugins || []
|
||||
for (const key of ['rspack:config', 'webpack:config'] as const) {
|
||||
nuxt.hook(key, (configs) => {
|
||||
configs.forEach((config) => {
|
||||
const mode = config.name === 'client' ? 'client' : 'server'
|
||||
config.plugins = config.plugins || []
|
||||
|
||||
if (mode !== 'server') {
|
||||
writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}')
|
||||
}
|
||||
if (mode !== 'server') {
|
||||
writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -12,6 +12,7 @@ interface LoaderOptions {
|
||||
}
|
||||
const CLIENT_FALLBACK_RE = /<(?:NuxtClientFallback|nuxt-client-fallback)(?: [^>]*)?>/
|
||||
const CLIENT_FALLBACK_GLOBAL_RE = /<(NuxtClientFallback|nuxt-client-fallback)( [^>]*)?>/g
|
||||
const UID_RE = / :?uid=/
|
||||
export const ClientFallbackAutoIdPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
const exclude = options.transform?.exclude || []
|
||||
const include = options.transform?.include || []
|
||||
@ -37,7 +38,7 @@ export const ClientFallbackAutoIdPlugin = (options: LoaderOptions) => createUnpl
|
||||
|
||||
s.replace(CLIENT_FALLBACK_GLOBAL_RE, (full, name, attrs) => {
|
||||
count++
|
||||
if (/ :?uid=/.test(attrs)) { return full }
|
||||
if (UID_RE.test(attrs)) { return full }
|
||||
return `<${name} :uid="'${hash(relativeID)}' + JSON.stringify($props) + '${count}'" ${attrs ?? ''}>`
|
||||
})
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import MagicString from 'magic-string'
|
||||
import type { Component } from 'nuxt/schema'
|
||||
import { isVue } from '../../core/utils'
|
||||
import { SX_RE, isVue } from '../../core/utils'
|
||||
|
||||
interface NameDevPluginOptions {
|
||||
sourcemap: boolean
|
||||
getComponents: () => Component[]
|
||||
}
|
||||
const FILENAME_RE = /([^/\\]+)\.\w+$/
|
||||
/**
|
||||
* Set the default name of components to their PascalCase name
|
||||
*/
|
||||
@ -15,10 +16,10 @@ export const ComponentNamePlugin = (options: NameDevPluginOptions) => createUnpl
|
||||
name: 'nuxt:component-name-plugin',
|
||||
enforce: 'post',
|
||||
transformInclude (id) {
|
||||
return isVue(id) || !!id.match(/\.[tj]sx$/)
|
||||
return isVue(id) || !!id.match(SX_RE)
|
||||
},
|
||||
transform (code, id) {
|
||||
const filename = id.match(/([^/\\]+)\.\w+$/)?.[1]
|
||||
const filename = id.match(FILENAME_RE)?.[1]
|
||||
if (!filename) {
|
||||
return
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ const TEMPLATE_RE = /<template>([\s\S]*)<\/template>/
|
||||
const NUXTCLIENT_ATTR_RE = /\s:?nuxt-client(="[^"]*")?/g
|
||||
const IMPORT_CODE = '\nimport { mergeProps as __mergeProps } from \'vue\'' + '\nimport { vforToArray as __vforToArray } from \'#app/components/utils\'' + '\nimport NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'' + '\nimport NuxtTeleportSsrSlot from \'#app/components/nuxt-teleport-island-slot\''
|
||||
const EXTRACTED_ATTRS_RE = /v-(?:if|else-if|else)(="[^"]*")?/g
|
||||
const KEY_RE = /:?key="[^"]"/g
|
||||
|
||||
function wrapWithVForDiv (code: string, vfor: string): string {
|
||||
return `<div v-for="${vfor}" style="display: contents;">${code}</div>`
|
||||
@ -90,7 +91,7 @@ export const IslandsTransformPlugin = (options: ServerOnlyComponentTransformPlug
|
||||
if (children.length) {
|
||||
// pass slot fallback to NuxtTeleportSsrSlot fallback
|
||||
const attrString = attributeToString(attributes)
|
||||
const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(/:?key="[^"]"/g, '')
|
||||
const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(KEY_RE, '')
|
||||
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[1].end, `<slot${attrString.replaceAll(EXTRACTED_ATTRS_RE, '')}/><template #fallback>${attributes['v-for'] ? wrapWithVForDiv(slice, attributes['v-for']) : slice}</template>`)
|
||||
} else {
|
||||
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[0].end, code.slice(startingIndex + loc[0].start, startingIndex + loc[0].end).replaceAll(EXTRACTED_ATTRS_RE, ''))
|
||||
|
@ -2,25 +2,26 @@ import { createUnplugin } from 'unplugin'
|
||||
import { genDynamicImport, genImport } from 'knitwork'
|
||||
import MagicString from 'magic-string'
|
||||
import { pascalCase } from 'scule'
|
||||
import { resolve } from 'pathe'
|
||||
import { relative } from 'pathe'
|
||||
import type { Component, ComponentsOptions } from 'nuxt/schema'
|
||||
|
||||
import { logger, tryUseNuxt } from '@nuxt/kit'
|
||||
import { distDir } from '../../dirs'
|
||||
import { isVue } from '../../core/utils'
|
||||
import { QUOTE_RE, SX_RE, isVue } from '../../core/utils'
|
||||
|
||||
interface LoaderOptions {
|
||||
getComponents (): Component[]
|
||||
mode: 'server' | 'client'
|
||||
serverComponentRuntime: string
|
||||
sourcemap?: boolean
|
||||
transform?: ComponentsOptions['transform']
|
||||
experimentalComponentIslands?: boolean
|
||||
}
|
||||
|
||||
const REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE = /(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy(?=[A-Z]))?([^'"]*)["'][^)]*\)/g
|
||||
export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
const exclude = options.transform?.exclude || []
|
||||
const include = options.transform?.include || []
|
||||
const serverComponentRuntime = resolve(distDir, 'components/runtime/server-component')
|
||||
const nuxt = tryUseNuxt()
|
||||
|
||||
return {
|
||||
name: 'nuxt:components-loader',
|
||||
@ -32,9 +33,9 @@ export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
if (include.some(pattern => pattern.test(id))) {
|
||||
return true
|
||||
}
|
||||
return isVue(id, { type: ['template', 'script'] }) || !!id.match(/\.[tj]sx$/)
|
||||
return isVue(id, { type: ['template', 'script'] }) || !!id.match(SX_RE)
|
||||
},
|
||||
transform (code) {
|
||||
transform (code, id) {
|
||||
const components = options.getComponents()
|
||||
|
||||
let num = 0
|
||||
@ -43,13 +44,17 @@ export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
const s = new MagicString(code)
|
||||
|
||||
// replace `_resolveComponent("...")` to direct import
|
||||
s.replace(/(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy(?=[A-Z]))?([^'"]*)["'][^)]*\)/g, (full: string, lazy: string, name: string) => {
|
||||
s.replace(REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE, (full: string, lazy: string, name: string) => {
|
||||
const component = findComponent(components, name, options.mode)
|
||||
if (component) {
|
||||
// @ts-expect-error TODO: refactor to nuxi
|
||||
if (component._internal_install && tryUseNuxt()?.options.test === false) {
|
||||
// @ts-expect-error TODO: refactor to nuxi
|
||||
import('../../core/features').then(({ installNuxtModule }) => installNuxtModule(component._internal_install))
|
||||
// TODO: refactor to nuxi
|
||||
const internalInstall = ((component as any)._internal_install) as string
|
||||
if (internalInstall && nuxt?.options.test === false) {
|
||||
if (!nuxt.options.dev) {
|
||||
const relativePath = relative(nuxt.options.rootDir, id)
|
||||
throw new Error(`[nuxt] \`~/${relativePath}\` is using \`${component.pascalName}\` which requires \`${internalInstall}\``)
|
||||
}
|
||||
import('../../core/features').then(({ installNuxtModule }) => installNuxtModule(internalInstall))
|
||||
}
|
||||
let identifier = map.get(component) || `__nuxt_component_${num++}`
|
||||
map.set(component, identifier)
|
||||
@ -57,7 +62,7 @@ export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
const isServerOnly = !component._raw && component.mode === 'server' &&
|
||||
!components.some(c => c.pascalName === component.pascalName && c.mode === 'client')
|
||||
if (isServerOnly) {
|
||||
imports.add(genImport(serverComponentRuntime, [{ name: 'createServerComponent' }]))
|
||||
imports.add(genImport(options.serverComponentRuntime, [{ name: 'createServerComponent' }]))
|
||||
imports.add(`const ${identifier} = createServerComponent(${JSON.stringify(component.pascalName)})`)
|
||||
if (!options.experimentalComponentIslands) {
|
||||
logger.warn(`Standalone server components (\`${name}\`) are not yet supported without enabling \`experimental.componentIslands\`.`)
|
||||
@ -107,7 +112,7 @@ export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
})
|
||||
|
||||
function findComponent (components: Component[], name: string, mode: LoaderOptions['mode']) {
|
||||
const id = pascalCase(name).replace(/["']/g, '')
|
||||
const id = pascalCase(name).replace(QUOTE_RE, '')
|
||||
// Prefer exact match
|
||||
const component = components.find(component => id === component.pascalName && ['all', mode, undefined].includes(component.mode))
|
||||
if (component) { return component }
|
||||
|
@ -5,15 +5,19 @@ import { createUnimport } from 'unimport'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { parseURL } from 'ufo'
|
||||
import { parseQuery } from 'vue-router'
|
||||
import { normalize, resolve } from 'pathe'
|
||||
import { normalize } from 'pathe'
|
||||
import { genImport } from 'knitwork'
|
||||
import { distDir } from '../../dirs'
|
||||
import type { getComponentsT } from '../module'
|
||||
|
||||
const COMPONENT_QUERY_RE = /[?&]nuxt_component=/
|
||||
|
||||
export function TransformPlugin (nuxt: Nuxt, getComponents: getComponentsT, mode: 'client' | 'server' | 'all') {
|
||||
const serverComponentRuntime = resolve(distDir, 'components/runtime/server-component')
|
||||
interface TransformPluginOptions {
|
||||
getComponents: getComponentsT
|
||||
mode: 'client' | 'server' | 'all'
|
||||
serverComponentRuntime: string
|
||||
}
|
||||
|
||||
export function TransformPlugin (nuxt: Nuxt, options: TransformPluginOptions) {
|
||||
const componentUnimport = createUnimport({
|
||||
imports: [
|
||||
{
|
||||
@ -26,7 +30,7 @@ export function TransformPlugin (nuxt: Nuxt, getComponents: getComponentsT, mode
|
||||
})
|
||||
|
||||
function getComponentsImports (): Import[] {
|
||||
const components = getComponents(mode)
|
||||
const components = options.getComponents(options.mode)
|
||||
return components.flatMap((c): Import[] => {
|
||||
const withMode = (mode: string | undefined) => mode
|
||||
? `${c.filePath}${c.filePath.includes('?') ? '&' : '?'}nuxt_component=${mode}&nuxt_component_name=${c.pascalName}&nuxt_component_export=${c.export || 'default'}`
|
||||
@ -95,7 +99,7 @@ export function TransformPlugin (nuxt: Nuxt, getComponents: getComponentsT, mode
|
||||
const name = query.nuxt_component_name
|
||||
return {
|
||||
code: [
|
||||
`import { createServerComponent } from ${JSON.stringify(serverComponentRuntime)}`,
|
||||
`import { createServerComponent } from ${JSON.stringify(options.serverComponentRuntime)}`,
|
||||
`${exportWording} createServerComponent(${JSON.stringify(name)})`,
|
||||
].join('\n'),
|
||||
map: null,
|
||||
|
@ -33,8 +33,9 @@ export const TreeShakeTemplatePlugin = (options: TreeShakeTemplatePluginOptions)
|
||||
const components = options.getComponents()
|
||||
|
||||
if (!regexpMap.has(components)) {
|
||||
const serverPlaceholderPath = resolve(distDir, 'app/components/server-placeholder')
|
||||
const clientOnlyComponents = components
|
||||
.filter(c => c.mode === 'client' && !components.some(other => other.mode !== 'client' && other.pascalName === c.pascalName && other.filePath !== resolve(distDir, 'app/components/server-placeholder')))
|
||||
.filter(c => c.mode === 'client' && !components.some(other => other.mode !== 'client' && other.pascalName === c.pascalName && !other.filePath.startsWith(serverPlaceholderPath)))
|
||||
.flatMap(c => [c.pascalName, c.kebabName.replaceAll('-', '_')])
|
||||
.concat(['ClientOnly', 'client_only'])
|
||||
|
||||
|
@ -6,8 +6,12 @@ import { isIgnored, logger, useNuxt } from '@nuxt/kit'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import type { Component, ComponentsDir } from 'nuxt/schema'
|
||||
|
||||
import { resolveComponentNameSegments } from '../core/utils'
|
||||
import { QUOTE_RE, resolveComponentNameSegments } from '../core/utils'
|
||||
|
||||
const ISLAND_RE = /\.island(?:\.global)?$/
|
||||
const GLOBAL_RE = /\.global(?:\.island)?$/
|
||||
const COMPONENT_MODE_RE = /(?<=\.)(client|server)(\.global|\.island)*$/
|
||||
const MODE_REPLACEMENT_RE = /(\.(client|server))?(\.global|\.island)*$/
|
||||
/**
|
||||
* Scan the components inside different components folders
|
||||
* and return a unique list of components
|
||||
@ -83,17 +87,17 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
||||
*/
|
||||
let fileName = basename(filePath, extname(filePath))
|
||||
|
||||
const island = /\.island(?:\.global)?$/.test(fileName) || dir.island
|
||||
const global = /\.global(?:\.island)?$/.test(fileName) || dir.global
|
||||
const mode = island ? 'server' : (fileName.match(/(?<=\.)(client|server)(\.global|\.island)*$/)?.[1] || 'all') as 'client' | 'server' | 'all'
|
||||
fileName = fileName.replace(/(\.(client|server))?(\.global|\.island)*$/, '')
|
||||
const island = ISLAND_RE.test(fileName) || dir.island
|
||||
const global = GLOBAL_RE.test(fileName) || dir.global
|
||||
const mode = island ? 'server' : (fileName.match(COMPONENT_MODE_RE)?.[1] || 'all') as 'client' | 'server' | 'all'
|
||||
fileName = fileName.replace(MODE_REPLACEMENT_RE, '')
|
||||
|
||||
if (fileName.toLowerCase() === 'index') {
|
||||
fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */
|
||||
}
|
||||
|
||||
const suffix = (mode !== 'all' ? `-${mode}` : '')
|
||||
const componentNameSegments = resolveComponentNameSegments(fileName.replace(/["']/g, ''), prefixParts)
|
||||
const componentNameSegments = resolveComponentNameSegments(fileName.replace(QUOTE_RE, ''), prefixParts)
|
||||
const pascalName = pascalCase(componentNameSegments)
|
||||
|
||||
if (LAZY_COMPONENT_NAME_REGEX.test(pascalName)) {
|
||||
|
@ -102,14 +102,15 @@ export const componentsIslandsTemplate: NuxtTemplate = {
|
||||
},
|
||||
}
|
||||
|
||||
const NON_VUE_RE = /\b\.(?!vue)\w+$/g
|
||||
export const componentsTypeTemplate = {
|
||||
filename: 'components.d.ts' as const,
|
||||
getContents: ({ app, nuxt }) => {
|
||||
const buildDir = nuxt.options.buildDir
|
||||
const componentTypes = app.components.filter(c => !c.island).map((c) => {
|
||||
const type = `typeof ${genDynamicImport(isAbsolute(c.filePath)
|
||||
? relative(buildDir, c.filePath).replace(/\b\.(?!vue)\w+$/g, '')
|
||||
: c.filePath.replace(/\b\.(?!vue)\w+$/g, ''), { wrapper: false })}['${c.export}']`
|
||||
? relative(buildDir, c.filePath).replace(NON_VUE_RE, '')
|
||||
: c.filePath.replace(NON_VUE_RE, ''), { wrapper: false })}['${c.export}']`
|
||||
return [
|
||||
c.pascalName,
|
||||
c.island || c.mode === 'server' ? `IslandComponent<${type}>` : type,
|
||||
|
@ -57,7 +57,7 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
||||
|
||||
const writes: Array<() => void> = []
|
||||
const changedTemplates: Array<ResolvedNuxtTemplate<any>> = []
|
||||
|
||||
const FORWARD_SLASH_RE = /\//g
|
||||
async function processTemplate (template: ResolvedNuxtTemplate) {
|
||||
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!)
|
||||
const start = performance.now()
|
||||
@ -72,12 +72,12 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
||||
if (template.modified) {
|
||||
nuxt.vfs[fullPath] = contents
|
||||
|
||||
const aliasPath = '#build/' + template.filename!.replace(/\.\w+$/, '')
|
||||
const aliasPath = '#build/' + template.filename
|
||||
nuxt.vfs[aliasPath] = contents
|
||||
|
||||
// In case a non-normalized absolute path is called for on Windows
|
||||
if (process.platform === 'win32') {
|
||||
nuxt.vfs[fullPath.replace(/\//g, '\\')] = contents
|
||||
nuxt.vfs[fullPath.replace(FORWARD_SLASH_RE, '\\')] = contents
|
||||
}
|
||||
|
||||
changedTemplates.push(template)
|
||||
|
@ -10,6 +10,7 @@ import { generateApp as _generateApp, createApp } from './app'
|
||||
import { checkForExternalConfigurationFiles } from './external-config-files'
|
||||
import { cleanupCaches, getVueHash } from './cache'
|
||||
|
||||
const IS_RESTART_PATH_RE = /^(?:app\.|error\.|plugins\/|middleware\/|layouts\/)/i
|
||||
export async function build (nuxt: Nuxt) {
|
||||
const app = createApp(nuxt)
|
||||
nuxt.apps.default = app
|
||||
@ -23,7 +24,7 @@ export async function build (nuxt: Nuxt) {
|
||||
if (event === 'change') { return }
|
||||
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||
const relativePaths = nuxt.options._layers.map(l => relative(l.config.srcDir || l.cwd, path))
|
||||
const restartPath = relativePaths.find(relativePath => /^(?:app\.|error\.|plugins\/|middleware\/|layouts\/)/i.test(relativePath))
|
||||
const restartPath = relativePaths.find(relativePath => IS_RESTART_PATH_RE.test(relativePath))
|
||||
if (restartPath) {
|
||||
if (restartPath.startsWith('app')) {
|
||||
app.mainComponent = undefined
|
||||
|
@ -18,6 +18,7 @@ import { distDir } from '../dirs'
|
||||
import { toArray } from '../utils'
|
||||
import { template as defaultSpaLoadingTemplate } from '../../../ui-templates/dist/templates/spa-loading-icon'
|
||||
import { nuxtImportProtections } from './plugins/import-protection'
|
||||
import { EXTENSION_RE } from './utils'
|
||||
|
||||
const logLevelMapReverse = {
|
||||
silent: 0,
|
||||
@ -25,12 +26,14 @@ const logLevelMapReverse = {
|
||||
verbose: 3,
|
||||
} satisfies Record<NuxtOptions['logLevel'], NitroConfig['logLevel']>
|
||||
|
||||
const NODE_MODULES_RE = /(?<=\/)node_modules\/(.+)$/
|
||||
const PNPM_NODE_MODULES_RE = /\.pnpm\/.+\/node_modules\/(.+)$/
|
||||
export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
// Resolve config
|
||||
const excludePaths = nuxt.options._layers
|
||||
.flatMap(l => [
|
||||
l.cwd.match(/(?<=\/)node_modules\/(.+)$/)?.[1],
|
||||
l.cwd.match(/\.pnpm\/.+\/node_modules\/(.+)$/)?.[1],
|
||||
l.cwd.match(NODE_MODULES_RE)?.[1],
|
||||
l.cwd.match(PNPM_NODE_MODULES_RE)?.[1],
|
||||
])
|
||||
.filter((dir): dir is string => Boolean(dir))
|
||||
.map(dir => escapeRE(dir))
|
||||
@ -101,8 +104,8 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
devHandlers: [],
|
||||
baseURL: nuxt.options.app.baseURL,
|
||||
virtual: {
|
||||
'#internal/nuxt.config.mjs': () => nuxt.vfs['#build/nuxt.config'],
|
||||
'#internal/nuxt/app-config': () => nuxt.vfs['#build/app.config']?.replace(/\/\*\* client \*\*\/[\s\S]*\/\*\* client-end \*\*\//, ''),
|
||||
'#internal/nuxt.config.mjs': () => nuxt.vfs['#build/nuxt.config.mjs'],
|
||||
'#internal/nuxt/app-config': () => nuxt.vfs['#build/app.config.mjs']?.replace(/\/\*\* client \*\*\/[\s\S]*\/\*\* client-end \*\*\//, ''),
|
||||
'#spa-template': async () => `export const template = ${JSON.stringify(await spaLoadingTemplate(nuxt))}`,
|
||||
},
|
||||
routeRules: {
|
||||
@ -189,11 +192,11 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
},
|
||||
'@vue/devtools-api': 'vue-devtools-stub',
|
||||
|
||||
// Paths
|
||||
'#internal/nuxt/paths': resolve(distDir, 'core/runtime/nitro/paths'),
|
||||
|
||||
// Nuxt aliases
|
||||
...nuxt.options.alias,
|
||||
|
||||
// Paths
|
||||
'#internal/nuxt/paths': resolve(distDir, 'core/runtime/nitro/paths'),
|
||||
},
|
||||
replace: {
|
||||
'process.env.NUXT_NO_SSR': nuxt.options.ssr === false,
|
||||
@ -339,19 +342,20 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
}
|
||||
|
||||
// Add fallback server for `ssr: false`
|
||||
const FORWARD_SLASH_RE = /\//g
|
||||
if (!nuxt.options.ssr) {
|
||||
nitroConfig.virtual!['#build/dist/server/server.mjs'] = 'export default () => {}'
|
||||
// In case a non-normalized absolute path is called for on Windows
|
||||
if (process.platform === 'win32') {
|
||||
nitroConfig.virtual!['#build/dist/server/server.mjs'.replace(/\//g, '\\')] = 'export default () => {}'
|
||||
nitroConfig.virtual!['#build/dist/server/server.mjs'.replace(FORWARD_SLASH_RE, '\\')] = 'export default () => {}'
|
||||
}
|
||||
}
|
||||
|
||||
if (nuxt.options.builder === '@nuxt/webpack-builder' || nuxt.options.dev) {
|
||||
if (nuxt.options.dev || nuxt.options.builder === '@nuxt/webpack-builder' || nuxt.options.builder === '@nuxt/rspack-builder') {
|
||||
nitroConfig.virtual!['#build/dist/server/styles.mjs'] = 'export default {}'
|
||||
// In case a non-normalized absolute path is called for on Windows
|
||||
if (process.platform === 'win32') {
|
||||
nitroConfig.virtual!['#build/dist/server/styles.mjs'.replace(/\//g, '\\')] = 'export default {}'
|
||||
nitroConfig.virtual!['#build/dist/server/styles.mjs'.replace(FORWARD_SLASH_RE, '\\')] = 'export default {}'
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,7 +393,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
tsConfig.compilerOptions.paths[alias] = [absolutePath]
|
||||
tsConfig.compilerOptions.paths[`${alias}/*`] = [`${absolutePath}/*`]
|
||||
} else {
|
||||
tsConfig.compilerOptions.paths[alias] = [absolutePath.replace(/\b\.\w+$/g, '')] /* remove extension */
|
||||
tsConfig.compilerOptions.paths[alias] = [absolutePath.replace(EXTENSION_RE, '')] /* remove extension */
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,18 +452,20 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
}
|
||||
}
|
||||
})
|
||||
nuxt.hook('webpack:config', (configuration) => {
|
||||
const clientConfig = configuration.find(config => config.name === 'client')
|
||||
if (!clientConfig!.resolve) { clientConfig!.resolve!.alias = {} }
|
||||
if (Array.isArray(clientConfig!.resolve!.alias)) {
|
||||
clientConfig!.resolve!.alias.push({
|
||||
name: 'vue',
|
||||
alias: 'vue/dist/vue.esm-bundler',
|
||||
})
|
||||
} else {
|
||||
clientConfig!.resolve!.alias!.vue = 'vue/dist/vue.esm-bundler'
|
||||
}
|
||||
})
|
||||
for (const hook of ['webpack:config', 'rspack:config'] as const) {
|
||||
nuxt.hook(hook, (configuration) => {
|
||||
const clientConfig = configuration.find(config => config.name === 'client')
|
||||
if (!clientConfig!.resolve) { clientConfig!.resolve!.alias = {} }
|
||||
if (Array.isArray(clientConfig!.resolve!.alias)) {
|
||||
clientConfig!.resolve!.alias.push({
|
||||
name: 'vue',
|
||||
alias: 'vue/dist/vue.esm-bundler',
|
||||
})
|
||||
} else {
|
||||
clientConfig!.resolve!.alias!.vue = 'vue/dist/vue.esm-bundler'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Setup handlers
|
||||
@ -545,13 +551,15 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
|
||||
// nuxt dev
|
||||
if (nuxt.options.dev) {
|
||||
nuxt.hook('webpack:compile', ({ name, compiler }) => {
|
||||
if (name === 'server') {
|
||||
const memfs = compiler.outputFileSystem as typeof import('node:fs')
|
||||
nitro.options.virtual['#build/dist/server/server.mjs'] = () => memfs.readFileSync(join(nuxt.options.buildDir, 'dist/server/server.mjs'), 'utf-8')
|
||||
}
|
||||
})
|
||||
nuxt.hook('webpack:compiled', () => { nuxt.server.reload() })
|
||||
for (const builder of ['webpack', 'rspack'] as const) {
|
||||
nuxt.hook(`${builder}:compile`, ({ name, compiler }) => {
|
||||
if (name === 'server') {
|
||||
const memfs = compiler.outputFileSystem as typeof import('node:fs')
|
||||
nitro.options.virtual['#build/dist/server/server.mjs'] = () => memfs.readFileSync(join(nuxt.options.buildDir, 'dist/server/server.mjs'), 'utf-8')
|
||||
}
|
||||
})
|
||||
nuxt.hook(`${builder}:compiled`, () => { nuxt.server.reload() })
|
||||
}
|
||||
nuxt.hook('vite:compiled', () => { nuxt.server.reload() })
|
||||
|
||||
nuxt.hook('server:devHandler', (h) => { devMiddlewareHandler.set(h) })
|
||||
@ -562,8 +570,9 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
}
|
||||
}
|
||||
|
||||
const RELATIVE_RE = /^([^.])/
|
||||
function relativeWithDot (from: string, to: string) {
|
||||
return relative(from, to).replace(/^([^.])/, './$1') || '.'
|
||||
return relative(from, to).replace(RELATIVE_RE, './$1') || '.'
|
||||
}
|
||||
|
||||
async function spaLoadingTemplatePath (nuxt: Nuxt) {
|
||||
|
@ -44,6 +44,7 @@ import { RemovePluginMetadataPlugin } from './plugins/plugin-metadata'
|
||||
import { AsyncContextInjectionPlugin } from './plugins/async-context'
|
||||
import { resolveDeepImportsPlugin } from './plugins/resolve-deep-imports'
|
||||
import { prehydrateTransformPlugin } from './plugins/prehydrate'
|
||||
import { VirtualFSPlugin } from './plugins/virtual'
|
||||
|
||||
export function createNuxt (options: NuxtOptions): Nuxt {
|
||||
const hooks = createHooks<NuxtHooks>()
|
||||
@ -177,9 +178,10 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
|
||||
const coreTypePackages = nuxt.options.typescript.hoist || []
|
||||
const packageJSON = await readPackageJSON(nuxt.options.rootDir).catch(() => ({}) as PackageJson)
|
||||
const NESTED_PKG_RE = /^[^@]+\//
|
||||
nuxt._dependencies = new Set([...Object.keys(packageJSON.dependencies || {}), ...Object.keys(packageJSON.devDependencies || {})])
|
||||
const paths = Object.fromEntries(await Promise.all(coreTypePackages.map(async (pkg) => {
|
||||
const [_pkg = pkg, _subpath] = /^[^@]+\//.test(pkg) ? pkg.split('/') : [pkg]
|
||||
const [_pkg = pkg, _subpath] = NESTED_PKG_RE.test(pkg) ? pkg.split('/') : [pkg]
|
||||
const subpath = _subpath ? '/' + _subpath : ''
|
||||
|
||||
// ignore packages that exist in `package.json` as these can be resolved by TypeScript
|
||||
@ -240,6 +242,10 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
}
|
||||
}
|
||||
|
||||
// Support Nuxt VFS
|
||||
addBuildPlugin(VirtualFSPlugin(nuxt, { mode: 'server' }), { client: false })
|
||||
addBuildPlugin(VirtualFSPlugin(nuxt, { mode: 'client', alias: { 'nitro/runtime': join(nuxt.options.buildDir, 'nitro.client.mjs') } }), { server: false })
|
||||
|
||||
// Add plugin normalization plugin
|
||||
addBuildPlugin(RemovePluginMetadataPlugin(nuxt))
|
||||
|
||||
@ -492,9 +498,11 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
|
||||
const envMap = {
|
||||
// defaults from `builder` based on package name
|
||||
'@nuxt/rspack-builder': '@rspack/core/module',
|
||||
'@nuxt/vite-builder': 'vite/client',
|
||||
'@nuxt/webpack-builder': 'webpack/module',
|
||||
// simpler overrides from `typescript.builder` for better DX
|
||||
'rspack': '@rspack/core/module',
|
||||
'vite': 'vite/client',
|
||||
'webpack': 'webpack/module',
|
||||
// default 'merged' builder environment for module authors
|
||||
|
@ -16,7 +16,7 @@ export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
|
||||
conditions = config.mode === 'test' ? [...config.resolve.conditions, 'import', 'require'] : config.resolve.conditions
|
||||
},
|
||||
async resolveId (id, importer) {
|
||||
if (!importer || isAbsolute(id) || (!isAbsolute(importer) && !importer.startsWith('virtual:')) || exclude.some(e => id.startsWith(e))) {
|
||||
if (!importer || isAbsolute(id) || (!isAbsolute(importer) && !importer.startsWith('virtual:') && !importer.startsWith('\0virtual:')) || exclude.some(e => id.startsWith(e))) {
|
||||
return
|
||||
}
|
||||
|
||||
|
68
packages/nuxt/src/core/plugins/virtual.ts
Normal file
68
packages/nuxt/src/core/plugins/virtual.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { resolveAlias } from '@nuxt/kit'
|
||||
import type { Nuxt } from '@nuxt/schema'
|
||||
import { dirname, isAbsolute, resolve } from 'pathe'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
|
||||
const PREFIX = '\0virtual:nuxt:'
|
||||
|
||||
interface VirtualFSPluginOptions {
|
||||
mode: 'client' | 'server'
|
||||
alias?: Record<string, string>
|
||||
}
|
||||
|
||||
const RELATIVE_ID_RE = /^\.{1,2}[\\/]/
|
||||
export const VirtualFSPlugin = (nuxt: Nuxt, options: VirtualFSPluginOptions) => createUnplugin(() => {
|
||||
const extensions = ['', ...nuxt.options.extensions]
|
||||
const alias = { ...nuxt.options.alias, ...options.alias }
|
||||
|
||||
const resolveWithExt = (id: string) => {
|
||||
for (const suffix of ['', '.' + options.mode]) {
|
||||
for (const ext of extensions) {
|
||||
const rId = id + suffix + ext
|
||||
if (rId in nuxt.vfs) {
|
||||
return rId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'nuxt:virtual',
|
||||
resolveId (id, importer) {
|
||||
id = resolveAlias(id, alias)
|
||||
|
||||
if (process.platform === 'win32' && isAbsolute(id)) {
|
||||
// Add back C: prefix on Windows
|
||||
id = resolve(id)
|
||||
}
|
||||
|
||||
const resolvedId = resolveWithExt(id)
|
||||
if (resolvedId) {
|
||||
return PREFIX + resolvedId
|
||||
}
|
||||
|
||||
if (importer && RELATIVE_ID_RE.test(id)) {
|
||||
const path = resolve(dirname(withoutPrefix(importer)), id)
|
||||
const resolved = resolveWithExt(path)
|
||||
if (resolved) {
|
||||
return PREFIX + resolved
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
loadInclude (id) {
|
||||
return id.startsWith(PREFIX) && withoutPrefix(id) in nuxt.vfs
|
||||
},
|
||||
|
||||
load (id) {
|
||||
return {
|
||||
code: nuxt.vfs[withoutPrefix(id)] || '',
|
||||
map: null,
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
function withoutPrefix (id: string) {
|
||||
return id.startsWith(PREFIX) ? id.slice(PREFIX.length) : id
|
||||
}
|
@ -23,7 +23,6 @@ export default defineNuxtModule({
|
||||
|
||||
// Initialize untyped/jiti loader
|
||||
const _resolveSchema = createJiti(fileURLToPath(import.meta.url), {
|
||||
interopDefault: true,
|
||||
cache: false,
|
||||
transformOptions: {
|
||||
babel: {
|
||||
@ -97,7 +96,7 @@ export default defineNuxtModule({
|
||||
let loadedConfig: SchemaDefinition
|
||||
try {
|
||||
// TODO: fix type for second argument of `import`
|
||||
loadedConfig = await _resolveSchema.import(filePath, {}) as SchemaDefinition
|
||||
loadedConfig = await _resolveSchema.import(filePath, { default: true }) as SchemaDefinition
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
'Unable to load schema from',
|
||||
|
@ -11,6 +11,7 @@ import type { NuxtTemplate } from 'nuxt/schema'
|
||||
import type { Nitro } from 'nitro/types'
|
||||
|
||||
import { annotatePlugins, checkForCircularDependencies } from './app'
|
||||
import { EXTENSION_RE } from './utils'
|
||||
|
||||
export const vueShim: NuxtTemplate = {
|
||||
filename: 'types/vue-shim.d.ts',
|
||||
@ -57,8 +58,9 @@ export const cssTemplate: NuxtTemplate = {
|
||||
getContents: ctx => ctx.nuxt.options.css.map(i => genImport(i)).join('\n'),
|
||||
}
|
||||
|
||||
const PLUGIN_TEMPLATE_RE = /_(45|46|47)/g
|
||||
export const clientPluginTemplate: NuxtTemplate = {
|
||||
filename: 'plugins/client.mjs',
|
||||
filename: 'plugins.client.mjs',
|
||||
async getContents (ctx) {
|
||||
const clientPlugins = await annotatePlugins(ctx.nuxt, ctx.app.plugins.filter(p => !p.mode || p.mode !== 'server'))
|
||||
checkForCircularDependencies(clientPlugins)
|
||||
@ -66,7 +68,7 @@ export const clientPluginTemplate: NuxtTemplate = {
|
||||
const imports: string[] = []
|
||||
for (const plugin of clientPlugins) {
|
||||
const path = relative(ctx.nuxt.options.rootDir, plugin.src)
|
||||
const variable = genSafeVariableName(filename(plugin.src)).replace(/_(45|46|47)/g, '_') + '_' + hash(path)
|
||||
const variable = genSafeVariableName(filename(plugin.src)).replace(PLUGIN_TEMPLATE_RE, '_') + '_' + hash(path)
|
||||
exports.push(variable)
|
||||
imports.push(genImport(plugin.src, variable))
|
||||
}
|
||||
@ -78,7 +80,7 @@ export const clientPluginTemplate: NuxtTemplate = {
|
||||
}
|
||||
|
||||
export const serverPluginTemplate: NuxtTemplate = {
|
||||
filename: 'plugins/server.mjs',
|
||||
filename: 'plugins.server.mjs',
|
||||
async getContents (ctx) {
|
||||
const serverPlugins = await annotatePlugins(ctx.nuxt, ctx.app.plugins.filter(p => !p.mode || p.mode !== 'client'))
|
||||
checkForCircularDependencies(serverPlugins)
|
||||
@ -86,7 +88,7 @@ export const serverPluginTemplate: NuxtTemplate = {
|
||||
const imports: string[] = []
|
||||
for (const plugin of serverPlugins) {
|
||||
const path = relative(ctx.nuxt.options.rootDir, plugin.src)
|
||||
const variable = genSafeVariableName(filename(path)).replace(/_(45|46|47)/g, '_') + '_' + hash(path)
|
||||
const variable = genSafeVariableName(filename(path)).replace(PLUGIN_TEMPLATE_RE, '_') + '_' + hash(path)
|
||||
exports.push(variable)
|
||||
imports.push(genImport(plugin.src, variable))
|
||||
}
|
||||
@ -98,7 +100,9 @@ export const serverPluginTemplate: NuxtTemplate = {
|
||||
}
|
||||
|
||||
const TS_RE = /\.[cm]?tsx?$/
|
||||
|
||||
const JS_LETTER_RE = /\.(?<letter>[cm])?jsx?$/
|
||||
const JS_RE = /\.[cm]jsx?$/
|
||||
const JS_CAPTURE_RE = /\.[cm](jsx?)$/
|
||||
export const pluginsDeclaration: NuxtTemplate = {
|
||||
filename: 'types/plugins.d.ts',
|
||||
getContents: async ({ nuxt, app }) => {
|
||||
@ -120,18 +124,18 @@ export const pluginsDeclaration: NuxtTemplate = {
|
||||
const pluginPath = resolve(typesDir, plugin.src)
|
||||
const relativePath = relative(typesDir, pluginPath)
|
||||
|
||||
const correspondingDeclaration = pluginPath.replace(/\.(?<letter>[cm])?jsx?$/, '.d.$<letter>ts')
|
||||
const correspondingDeclaration = pluginPath.replace(JS_LETTER_RE, '.d.$<letter>ts')
|
||||
// if `.d.ts` file exists alongside a `.js` plugin, or if `.d.mts` file exists alongside a `.mjs` plugin, we can use the entire path
|
||||
if (correspondingDeclaration !== pluginPath && exists(correspondingDeclaration)) {
|
||||
tsImports.push(relativePath)
|
||||
continue
|
||||
}
|
||||
|
||||
const incorrectDeclaration = pluginPath.replace(/\.[cm]jsx?$/, '.d.ts')
|
||||
const incorrectDeclaration = pluginPath.replace(JS_RE, '.d.ts')
|
||||
// if `.d.ts` file exists, but plugin is `.mjs`, add `.js` extension to the import
|
||||
// to hotfix issue until ecosystem updates to `@nuxt/module-builder@>=0.8.0`
|
||||
if (incorrectDeclaration !== pluginPath && exists(incorrectDeclaration)) {
|
||||
tsImports.push(relativePath.replace(/\.[cm](jsx?)$/, '.$1'))
|
||||
tsImports.push(relativePath.replace(JS_CAPTURE_RE, '.$1'))
|
||||
continue
|
||||
}
|
||||
|
||||
@ -174,11 +178,13 @@ export { }
|
||||
}
|
||||
|
||||
const adHocModules = ['router', 'pages', 'imports', 'meta', 'components', 'nuxt-config-schema']
|
||||
const IMPORT_NAME_RE = /\.\w+$/
|
||||
const GIT_RE = /^git\+/
|
||||
export const schemaTemplate: NuxtTemplate = {
|
||||
filename: 'types/schema.d.ts',
|
||||
getContents: async ({ nuxt }) => {
|
||||
const relativeRoot = relative(resolve(nuxt.options.buildDir, 'types'), nuxt.options.rootDir)
|
||||
const getImportName = (name: string) => (name[0] === '.' ? './' + join(relativeRoot, name) : name).replace(/\.\w+$/, '')
|
||||
const getImportName = (name: string) => (name[0] === '.' ? './' + join(relativeRoot, name) : name).replace(IMPORT_NAME_RE, '')
|
||||
|
||||
const modules = nuxt.options._installedModules
|
||||
.filter(m => m.meta && m.meta.configKey && m.meta.name && !adHocModules.includes(m.meta.name))
|
||||
@ -210,7 +216,7 @@ export const schemaTemplate: NuxtTemplate = {
|
||||
}
|
||||
if (link) {
|
||||
if (link.startsWith('git+')) {
|
||||
link = link.replace(/^git\+/, '')
|
||||
link = link.replace(GIT_RE, '')
|
||||
}
|
||||
if (!link.startsWith('http')) {
|
||||
link = 'https://github.com/' + link
|
||||
@ -377,7 +383,7 @@ export const appConfigDeclarationTemplate: NuxtTemplate = {
|
||||
filename: 'types/app.config.d.ts',
|
||||
getContents ({ app, nuxt }) {
|
||||
const typesDir = join(nuxt.options.buildDir, 'types')
|
||||
const configPaths = app.configs.map(path => relative(typesDir, path).replace(/\b\.\w+$/g, ''))
|
||||
const configPaths = app.configs.map(path => relative(typesDir, path).replace(EXTENSION_RE, ''))
|
||||
|
||||
return `
|
||||
import type { CustomAppConfig } from 'nuxt/schema'
|
||||
|
@ -14,3 +14,7 @@ export function uniqueBy<T, K extends keyof T> (arr: T[], key: K) {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export const QUOTE_RE = /["']/g
|
||||
export const EXTENSION_RE = /\b\.\w+$/g
|
||||
export const SX_RE = /\.[tj]sx$/
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { basename, dirname, extname, normalize } from 'pathe'
|
||||
import { kebabCase, splitByCase } from 'scule'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import { QUOTE_RE } from '.'
|
||||
|
||||
export function getNameFromPath (path: string, relativeTo?: string) {
|
||||
const relativePath = relativeTo
|
||||
@ -9,7 +10,7 @@ export function getNameFromPath (path: string, relativeTo?: string) {
|
||||
const prefixParts = splitByCase(dirname(relativePath))
|
||||
const fileName = basename(relativePath, extname(relativePath))
|
||||
const segments = resolveComponentNameSegments(fileName.toLowerCase() === 'index' ? '' : fileName, prefixParts).filter(Boolean)
|
||||
return kebabCase(segments).replace(/["']/g, '')
|
||||
return kebabCase(segments).replace(QUOTE_RE, '')
|
||||
}
|
||||
|
||||
export function hasSuffix (path: string, suffix: string) {
|
||||
|
@ -76,8 +76,8 @@ export default import.meta.server ? [CapoPlugin({ track: true })] : [];`
|
||||
|
||||
// template is only exposed in nuxt context, expose in nitro context as well
|
||||
nuxt.hooks.hook('nitro:config', (config) => {
|
||||
config.virtual!['#internal/unhead-plugins.mjs'] = () => nuxt.vfs['#build/unhead-plugins']
|
||||
config.virtual!['#internal/unhead.config.mjs'] = () => nuxt.vfs['#build/unhead.config']
|
||||
config.virtual!['#internal/unhead-plugins.mjs'] = () => nuxt.vfs['#build/unhead-plugins.mjs']
|
||||
config.virtual!['#internal/unhead.config.mjs'] = () => nuxt.vfs['#build/unhead.config.mjs']
|
||||
})
|
||||
|
||||
// Add library-specific plugin
|
||||
|
@ -66,7 +66,7 @@ const granularAppPresets: InlinePreset[] = [
|
||||
from: '#app/composables/cookie',
|
||||
},
|
||||
{
|
||||
imports: ['onPrehydrate', 'prerenderRoutes', 'useRequestHeader', 'useRequestHeaders', 'useRequestEvent', 'useRequestFetch', 'setResponseStatus'],
|
||||
imports: ['onPrehydrate', 'prerenderRoutes', 'useRequestHeader', 'useRequestHeaders', 'useResponseHeader', 'useRequestEvent', 'useRequestFetch', 'setResponseStatus'],
|
||||
from: '#app/composables/ssr',
|
||||
},
|
||||
{
|
||||
|
@ -52,7 +52,7 @@ export default defineNuxtModule({
|
||||
}
|
||||
|
||||
// Add default options at beginning
|
||||
context.files.unshift({ path: resolve(runtimeDir, 'router.options'), optional: true })
|
||||
context.files.unshift({ path: await findPath(resolve(runtimeDir, 'router.options')) || resolve(runtimeDir, 'router.options'), optional: true })
|
||||
|
||||
await nuxt.callHook('pages:routerOptions', context)
|
||||
return context.files
|
||||
@ -170,10 +170,15 @@ export default defineNuxtModule({
|
||||
if (nuxt.apps.default) {
|
||||
nuxt.apps.default.pages = pages
|
||||
}
|
||||
const addedPagePaths = new Set<string>()
|
||||
function addPage (parent: EditableTreeNode, page: NuxtPage) {
|
||||
// Avoid duplicate keys in the generated RouteNamedMap type
|
||||
const absolutePagePath = joinURL(parent.path, page.path)
|
||||
|
||||
// @ts-expect-error TODO: either fix types upstream or figure out another
|
||||
// way to add a route without a file, which must be possible
|
||||
const route = parent.insert(page.path, page.file)
|
||||
const route = addedPagePaths.has(absolutePagePath) ? parent : parent.insert(page.path, page.file)
|
||||
addedPagePaths.add(absolutePagePath)
|
||||
if (page.meta) {
|
||||
route.addToMeta(page.meta)
|
||||
}
|
||||
@ -414,8 +419,18 @@ export default defineNuxtModule({
|
||||
})
|
||||
}
|
||||
|
||||
const componentStubPath = await resolvePath(resolve(runtimeDir, 'component-stub'))
|
||||
if (nuxt.options.test && nuxt.options.dev) {
|
||||
// add component testing route so 404 won't be triggered
|
||||
nuxt.hook('pages:extend', (routes) => {
|
||||
routes.push({
|
||||
_sync: true,
|
||||
path: '/__nuxt_component_test__/:pathMatch(.*)',
|
||||
file: componentStubPath,
|
||||
})
|
||||
})
|
||||
}
|
||||
if (nuxt.options.experimental.appManifest) {
|
||||
const componentStubPath = await resolvePath(resolve(runtimeDir, 'component-stub'))
|
||||
// Add all redirect paths as valid routes to router; we will handle these in a client-side middleware
|
||||
// when the app manifest is enabled.
|
||||
nuxt.hook('pages:extend', (routes) => {
|
||||
@ -477,12 +492,19 @@ export default defineNuxtModule({
|
||||
}
|
||||
})
|
||||
|
||||
const serverComponentRuntime = await findPath(join(distDir, 'components/runtime/server-component')) ?? join(distDir, 'components/runtime/server-component')
|
||||
const clientComponentRuntime = await findPath(join(distDir, 'components/runtime/client-component')) ?? join(distDir, 'components/runtime/client-component')
|
||||
|
||||
// Add routes template
|
||||
addTemplate({
|
||||
filename: 'routes.mjs',
|
||||
getContents ({ app }) {
|
||||
if (!app.pages) { return 'export default []' }
|
||||
const { routes, imports } = normalizeRoutes(app.pages, new Set(), nuxt.options.experimental.scanPageMeta)
|
||||
const { routes, imports } = normalizeRoutes(app.pages, new Set(), {
|
||||
serverComponentRuntime,
|
||||
clientComponentRuntime,
|
||||
overrideMeta: !!nuxt.options.experimental.scanPageMeta,
|
||||
})
|
||||
return [...imports, `export default ${routes}`].join('\n')
|
||||
},
|
||||
})
|
||||
|
@ -176,8 +176,10 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions) => createUnplugin
|
||||
|
||||
// https://github.com/vuejs/vue-loader/pull/1911
|
||||
// https://github.com/vitejs/vite/issues/8473
|
||||
const QUERY_START_RE = /^\?/
|
||||
const MACRO_RE = /¯o=true/
|
||||
function rewriteQuery (id: string) {
|
||||
return id.replace(/\?.+$/, r => '?macro=true&' + r.replace(/^\?/, '').replace(/¯o=true/, ''))
|
||||
return id.replace(/\?.+$/, r => '?macro=true&' + r.replace(QUERY_START_RE, '').replace(MACRO_RE, ''))
|
||||
}
|
||||
|
||||
function parseMacroQuery (id: string) {
|
||||
@ -189,6 +191,7 @@ function parseMacroQuery (id: string) {
|
||||
return query
|
||||
}
|
||||
|
||||
const QUOTED_SPECIFIER_RE = /(["']).*\1/
|
||||
function getQuotedSpecifier (id: string) {
|
||||
return id.match(/(["']).*\1/)?.[0]
|
||||
return id.match(QUOTED_SPECIFIER_RE)?.[0]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from 'vue-router'
|
||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRaw, RouteRecordRedirectOption } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { NitroRouteConfig } from 'nitro/types'
|
||||
import { useNuxtApp } from '#app/nuxt'
|
||||
@ -37,6 +37,11 @@ export interface PageMeta {
|
||||
name?: string
|
||||
/** You may define a path matcher, if you have a more complex pattern than can be expressed with the file name. */
|
||||
path?: string
|
||||
/**
|
||||
* Allows accessing the route `params` as props passed to the page component.
|
||||
* @see https://router.vuejs.org/guide/essentials/passing-props
|
||||
*/
|
||||
props?: RouteRecordRaw['props']
|
||||
/** Set to `false` to avoid scrolling to top on page navigations */
|
||||
scrollToTop?: boolean | ((to: RouteLocationNormalizedLoaded, from: RouteLocationNormalizedLoaded) => boolean)
|
||||
}
|
||||
|
@ -148,16 +148,8 @@ const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
|
||||
if (import.meta.server && failure?.type === 4 /* ErrorTypes.NAVIGATION_ABORTED */) {
|
||||
return
|
||||
}
|
||||
if (to.matched.length === 0) {
|
||||
await nuxtApp.runWithContext(() => showError(createError({
|
||||
statusCode: 404,
|
||||
fatal: false,
|
||||
statusMessage: `Page not found: ${to.fullPath}`,
|
||||
data: {
|
||||
path: to.fullPath,
|
||||
},
|
||||
})))
|
||||
} else if (import.meta.server && to.redirectedFrom && to.fullPath !== initialURL) {
|
||||
|
||||
if (import.meta.server && to.redirectedFrom && to.fullPath !== initialURL) {
|
||||
await nuxtApp.runWithContext(() => navigateTo(to.fullPath || '/'))
|
||||
}
|
||||
})
|
||||
@ -252,6 +244,19 @@ const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
|
||||
await nuxtApp.callHook('page:loading:end')
|
||||
})
|
||||
|
||||
router.afterEach(async (to, _from) => {
|
||||
if (to.matched.length === 0) {
|
||||
await nuxtApp.runWithContext(() => showError(createError({
|
||||
statusCode: 404,
|
||||
fatal: false,
|
||||
statusMessage: `Page not found: ${to.fullPath}`,
|
||||
data: {
|
||||
path: to.fullPath,
|
||||
},
|
||||
})))
|
||||
}
|
||||
})
|
||||
|
||||
nuxtApp.hooks.hookOnce('app:created', async () => {
|
||||
try {
|
||||
// #4920, #4982
|
||||
|
@ -5,11 +5,14 @@ type InstanceOf<T> = T extends new (...args: any[]) => infer R ? R : never
|
||||
type RouterViewSlot = Exclude<InstanceOf<typeof RouterView>['$slots']['default'], undefined>
|
||||
export type RouterViewSlotProps = Parameters<RouterViewSlot>[0]
|
||||
|
||||
const ROUTE_KEY_PARENTHESES_RE = /(:\w+)\([^)]+\)/g
|
||||
const ROUTE_KEY_SYMBOLS_RE = /(:\w+)[?+*]/g
|
||||
const ROUTE_KEY_NORMAL_RE = /:\w+/g
|
||||
const interpolatePath = (route: RouteLocationNormalizedLoaded, match: RouteLocationMatched) => {
|
||||
return match.path
|
||||
.replace(/(:\w+)\([^)]+\)/g, '$1')
|
||||
.replace(/(:\w+)[?+*]/g, '$1')
|
||||
.replace(/:\w+/g, r => route.params[r.slice(1)]?.toString() || '')
|
||||
.replace(ROUTE_KEY_PARENTHESES_RE, '$1')
|
||||
.replace(ROUTE_KEY_SYMBOLS_RE, '$1')
|
||||
.replace(ROUTE_KEY_NORMAL_RE, r => route.params[r.slice(1)]?.toString() || '')
|
||||
}
|
||||
|
||||
export const generateRouteKey = (routeProps: RouterViewSlotProps, override?: string | ((route: RouteLocationNormalizedLoaded) => string)) => {
|
||||
|
@ -15,7 +15,6 @@ import type { NuxtPage } from 'nuxt/schema'
|
||||
|
||||
import { getLoader, uniqueBy } from '../core/utils'
|
||||
import { toArray } from '../utils'
|
||||
import { distDir } from '../dirs'
|
||||
|
||||
enum SegmentParserState {
|
||||
initial,
|
||||
@ -65,18 +64,25 @@ export async function resolvePagesRoutes (): Promise<NuxtPage[]> {
|
||||
})
|
||||
|
||||
const pages = uniqueBy(allRoutes, 'path')
|
||||
|
||||
const shouldAugment = nuxt.options.experimental.scanPageMeta || nuxt.options.experimental.typedPages
|
||||
|
||||
if (shouldAugment) {
|
||||
if (shouldAugment === false) {
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
return pages
|
||||
}
|
||||
|
||||
if (shouldAugment === 'after-resolve') {
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
await augmentPages(pages, nuxt.vfs)
|
||||
} else {
|
||||
const augmentedPages = await augmentPages(pages, nuxt.vfs)
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
await augmentPages(pages, nuxt.vfs, augmentedPages)
|
||||
augmentedPages.clear()
|
||||
} else {
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
}
|
||||
|
||||
await nuxt.callHook('pages:resolved', pages)
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
@ -84,6 +90,7 @@ type GenerateRoutesFromFilesOptions = {
|
||||
shouldUseServerComponents?: boolean
|
||||
}
|
||||
|
||||
const INDEX_PAGE_RE = /\/index$/
|
||||
export function generateRoutesFromFiles (files: ScannedFile[], options: GenerateRoutesFromFilesOptions = {}): NuxtPage[] {
|
||||
const routes: NuxtPage[] = []
|
||||
|
||||
@ -129,7 +136,7 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
|
||||
route.name += (route.name && '/') + segmentName
|
||||
|
||||
// ex: parent.vue + parent/child.vue
|
||||
const path = withLeadingSlash(joinURL(route.path, getRoutePath(tokens).replace(/\/index$/, '/')))
|
||||
const path = withLeadingSlash(joinURL(route.path, getRoutePath(tokens).replace(INDEX_PAGE_RE, '/')))
|
||||
const child = parent.find(parentRoute => parentRoute.name === route.name && parentRoute.path === path)
|
||||
|
||||
if (child && child.children) {
|
||||
@ -184,7 +191,7 @@ export function extractScriptContent (html: string) {
|
||||
}
|
||||
|
||||
const PAGE_META_RE = /definePageMeta\([\s\S]*?\)/
|
||||
const extractionKeys = ['name', 'path', 'alias', 'redirect'] as const
|
||||
const extractionKeys = ['name', 'path', 'props', 'alias', 'redirect'] as const
|
||||
const DYNAMIC_META_KEY = '__nuxt_dynamic_meta_key' as const
|
||||
|
||||
const pageContentsCache: Record<string, string> = {}
|
||||
@ -266,7 +273,7 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
|
||||
continue
|
||||
}
|
||||
|
||||
if (property.value.type !== 'Literal' || typeof property.value.value !== 'string') {
|
||||
if (property.value.type !== 'Literal' || (typeof property.value.value !== 'string' && typeof property.value.value !== 'boolean')) {
|
||||
console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not a string literal or array of string literals (reading \`${absolutePath}\`).`)
|
||||
dynamicProperties.add(key)
|
||||
continue
|
||||
@ -301,6 +308,7 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
|
||||
return extractedMeta
|
||||
}
|
||||
|
||||
const COLON_RE = /:/g
|
||||
function getRoutePath (tokens: SegmentToken[]): string {
|
||||
return tokens.reduce((path, token) => {
|
||||
return (
|
||||
@ -313,7 +321,7 @@ function getRoutePath (tokens: SegmentToken[]): string {
|
||||
? `:${token.value}(.*)*`
|
||||
: token.type === SegmentTokenType.group
|
||||
? ''
|
||||
: encodePath(token.value).replace(/:/g, '\\:'))
|
||||
: encodePath(token.value).replace(COLON_RE, '\\:'))
|
||||
)
|
||||
}, '/')
|
||||
}
|
||||
@ -433,13 +441,14 @@ function findRouteByName (name: string, routes: NuxtPage[]): NuxtPage | undefine
|
||||
return findRouteByName(name, routes)
|
||||
}
|
||||
|
||||
const NESTED_PAGE_RE = /\//g
|
||||
function prepareRoutes (routes: NuxtPage[], parent?: NuxtPage, names = new Set<string>()) {
|
||||
for (const route of routes) {
|
||||
// Remove -index
|
||||
if (route.name) {
|
||||
route.name = route.name
|
||||
.replace(/\/index$/, '')
|
||||
.replace(/\//g, '-')
|
||||
.replace(INDEX_PAGE_RE, '')
|
||||
.replace(NESTED_PAGE_RE, '-')
|
||||
|
||||
if (names.has(route.name)) {
|
||||
const existingRoute = findRouteByName(route.name, routes)
|
||||
@ -476,7 +485,12 @@ function serializeRouteValue (value: any, skipSerialisation = false) {
|
||||
|
||||
type NormalizedRoute = Partial<Record<Exclude<keyof NuxtPage, 'file'>, string>> & { component?: string }
|
||||
type NormalizedRouteKeys = (keyof NormalizedRoute)[]
|
||||
export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> = new Set(), overrideMeta = false): { imports: Set<string>, routes: string } {
|
||||
interface NormalizeRoutesOptions {
|
||||
overrideMeta?: boolean
|
||||
serverComponentRuntime: string
|
||||
clientComponentRuntime: string
|
||||
}
|
||||
export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> = new Set(), options: NormalizeRoutesOptions): { imports: Set<string>, routes: string } {
|
||||
return {
|
||||
imports: metaImports,
|
||||
routes: genArrayFromRaw(routes.map((page) => {
|
||||
@ -506,7 +520,7 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
|
||||
}
|
||||
|
||||
if (page.children?.length) {
|
||||
route.children = normalizeRoutes(page.children, metaImports, overrideMeta).routes
|
||||
route.children = normalizeRoutes(page.children, metaImports, options).routes
|
||||
}
|
||||
|
||||
// Without a file, we can't use `definePageMeta` to extract route-level meta from the file
|
||||
@ -528,6 +542,7 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
|
||||
const metaRoute: NormalizedRoute = {
|
||||
name: `${metaImportName}?.name ?? ${route.name}`,
|
||||
path: `${metaImportName}?.path ?? ${route.path}`,
|
||||
props: `${metaImportName}?.props ?? false`,
|
||||
meta: `${metaImportName} || {}`,
|
||||
alias: `${metaImportName}?.alias || []`,
|
||||
redirect: `${metaImportName}?.redirect`,
|
||||
@ -542,14 +557,14 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
|
||||
metaImports.add(`
|
||||
let _createIslandPage
|
||||
async function createIslandPage (name) {
|
||||
_createIslandPage ||= await import(${JSON.stringify(resolve(distDir, 'components/runtime/server-component'))}).then(r => r.createIslandPage)
|
||||
_createIslandPage ||= await import(${JSON.stringify(options?.serverComponentRuntime)}).then(r => r.createIslandPage)
|
||||
return _createIslandPage(name)
|
||||
};`)
|
||||
} else if (page.mode === 'client') {
|
||||
metaImports.add(`
|
||||
let _createClientPage
|
||||
async function createClientPage(loader) {
|
||||
_createClientPage ||= await import(${JSON.stringify(resolve(distDir, 'components/runtime/client-component'))}).then(r => r.createClientPage)
|
||||
_createClientPage ||= await import(${JSON.stringify(options?.clientComponentRuntime)}).then(r => r.createClientPage)
|
||||
return _createClientPage(loader);
|
||||
}`)
|
||||
}
|
||||
@ -562,7 +577,7 @@ async function createClientPage(loader) {
|
||||
metaRoute.meta = `{ ...(${metaImportName} || {}), ...${route.meta} }`
|
||||
}
|
||||
|
||||
if (overrideMeta) {
|
||||
if (options?.overrideMeta) {
|
||||
// skip and retain fallback if marked dynamic
|
||||
// set to extracted value or fallback if none extracted
|
||||
for (const key of ['name', 'path'] satisfies NormalizedRouteKeys) {
|
||||
@ -571,7 +586,7 @@ async function createClientPage(loader) {
|
||||
}
|
||||
|
||||
// set to extracted value or delete if none extracted
|
||||
for (const key of ['meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) {
|
||||
for (const key of ['meta', 'alias', 'redirect', 'props'] satisfies NormalizedRouteKeys) {
|
||||
if (markedDynamic.has(key)) { continue }
|
||||
|
||||
if (route[key] == null) {
|
||||
@ -596,6 +611,7 @@ async function createClientPage(loader) {
|
||||
}
|
||||
}
|
||||
|
||||
const PATH_TO_NITRO_GLOB_RE = /\/[^:/]*:\w.*$/
|
||||
export function pathToNitroGlob (path: string) {
|
||||
if (!path) {
|
||||
return null
|
||||
@ -605,7 +621,7 @@ export function pathToNitroGlob (path: string) {
|
||||
return null
|
||||
}
|
||||
|
||||
return path.replace(/\/[^:/]*:\w.*$/, '/**')
|
||||
return path.replace(PATH_TO_NITRO_GLOB_RE, '/**')
|
||||
}
|
||||
|
||||
export function resolveRoutePaths (page: NuxtPage, parent = '/'): string[] {
|
||||
|
@ -6,6 +6,7 @@
|
||||
"meta": "{ ...(mockMeta || {}), ...{"someMetaData":true} }",
|
||||
"name": "mockMeta?.name ?? "pushed-route"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -24,6 +25,18 @@
|
||||
"meta": "{ ...(mockMeta || {}), ...{"test":1} }",
|
||||
"name": "mockMeta?.name ?? "page-with-meta"",
|
||||
"path": "mockMeta?.path ?? "/page-with-meta"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
"route.meta props generate by file": [
|
||||
{
|
||||
"alias": "mockMeta?.alias || []",
|
||||
"component": "() => import("pages/page-with-props.vue")",
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "page-with-props"",
|
||||
"path": "mockMeta?.path ?? "/page-with-props"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -34,6 +47,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "test:name"",
|
||||
"path": "mockMeta?.path ?? "/test\\:name"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -50,6 +64,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "param-index"",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -58,6 +73,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "param-index-sibling"",
|
||||
"path": "mockMeta?.path ?? "sibling"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -65,6 +81,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -73,6 +90,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "param-sibling"",
|
||||
"path": "mockMeta?.path ?? "sibling"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -80,6 +98,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? "/param"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -91,6 +110,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "wrapper-expose-other"",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -99,6 +119,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "wrapper-expose-other-sibling"",
|
||||
"path": "mockMeta?.path ?? "sibling"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -106,6 +127,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? "/wrapper-expose/other"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -116,6 +138,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "home"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": ""/"",
|
||||
},
|
||||
],
|
||||
@ -126,6 +149,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "slug"",
|
||||
"path": "mockMeta?.path ?? "/:slug(.*)*"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -134,6 +158,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -144,6 +169,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -152,6 +178,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "slug"",
|
||||
"path": "mockMeta?.path ?? "/:slug()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -163,6 +190,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "foo"",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -170,6 +198,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? "/:foo?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -178,6 +207,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "optional-opt"",
|
||||
"path": "mockMeta?.path ?? "/optional/:opt?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -186,6 +216,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "optional-prefix-opt"",
|
||||
"path": "mockMeta?.path ?? "/optional/prefix-:opt?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -194,6 +225,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "optional-opt-postfix"",
|
||||
"path": "mockMeta?.path ?? "/optional/:opt?-postfix"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -202,6 +234,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "optional-prefix-opt-postfix"",
|
||||
"path": "mockMeta?.path ?? "/optional/prefix-:opt?-postfix"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -210,6 +243,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "bar"",
|
||||
"path": "mockMeta?.path ?? "/:bar()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -218,6 +252,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "nonopt-slug"",
|
||||
"path": "mockMeta?.path ?? "/nonopt/:slug()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -226,6 +261,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "opt-slug"",
|
||||
"path": "mockMeta?.path ?? "/opt/:slug?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -234,6 +270,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "sub-route-slug"",
|
||||
"path": "mockMeta?.path ?? "/:sub?/route-:slug()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -244,6 +281,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "stories"",
|
||||
"path": "mockMeta?.path ?? "/:stories(.*)*"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -252,6 +290,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "stories-id"",
|
||||
"path": "mockMeta?.path ?? "/stories/:id()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -262,6 +301,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "stories-id"",
|
||||
"path": "mockMeta?.path ?? "/stories/:id()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -270,6 +310,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "stories"",
|
||||
"path": "mockMeta?.path ?? "/:stories(.*)*"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -280,6 +321,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "kebab-case"",
|
||||
"path": "mockMeta?.path ?? "/kebab-case"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -290,6 +332,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "snake_case"",
|
||||
"path": "mockMeta?.path ?? "/snake_case"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -300,6 +343,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -308,6 +352,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent"",
|
||||
"path": "mockMeta?.path ?? "/parent"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -316,6 +361,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent-child"",
|
||||
"path": "mockMeta?.path ?? "/parent/child"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -329,6 +375,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent-child"",
|
||||
"path": "mockMeta?.path ?? "child"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -336,6 +383,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent"",
|
||||
"path": "mockMeta?.path ?? "/parent"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -346,6 +394,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -357,6 +406,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "about"",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -364,6 +414,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? "/about"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -377,6 +428,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index-index-all"",
|
||||
"path": "mockMeta?.path ?? "all"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -384,6 +436,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -394,6 +447,7 @@
|
||||
"meta": "{ ...(mockMeta || {}), ...{"test":1} }",
|
||||
"name": "mockMeta?.name ?? "page-with-meta"",
|
||||
"path": "mockMeta?.path ?? "/page-with-meta"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -404,6 +458,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent-child"",
|
||||
"path": "mockMeta?.path ?? "/parent/:child()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -412,6 +467,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent-child"",
|
||||
"path": "mockMeta?.path ?? "/parent-:child()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -422,6 +478,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "foo"",
|
||||
"path": "mockMeta?.path ?? "/:foo?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -430,6 +487,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "foo"",
|
||||
"path": "mockMeta?.path ?? "/:foo()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -440,6 +498,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "a1_1a"",
|
||||
"path": "mockMeta?.path ?? "/:a1_1a()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -448,6 +507,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "b2.2b"",
|
||||
"path": "mockMeta?.path ?? "/:b2.2b()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -456,6 +516,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "b2_2b"",
|
||||
"path": "mockMeta?.path ?? "/:b2()_:2b()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -464,6 +525,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "c33c"",
|
||||
"path": "mockMeta?.path ?? "/:c33c?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -472,6 +534,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "d44d"",
|
||||
"path": "mockMeta?.path ?? "/:d44d?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -482,6 +545,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "home"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -492,6 +556,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
|
@ -24,6 +24,13 @@
|
||||
"path": ""/page-with-meta"",
|
||||
},
|
||||
],
|
||||
"route.meta props generate by file": [
|
||||
{
|
||||
"component": "() => import("pages/page-with-props.vue")",
|
||||
"name": ""page-with-props"",
|
||||
"path": ""/page-with-props"",
|
||||
},
|
||||
],
|
||||
"should allow pages with `:` in their path": [
|
||||
{
|
||||
"component": "() => import("pages/test:name.vue")",
|
||||
|
@ -92,7 +92,11 @@ function createTransformer (components: Component[], mode: 'client' | 'server' |
|
||||
},
|
||||
},
|
||||
} as Nuxt
|
||||
const plugin = TransformPlugin(stubNuxt, () => components, mode).vite()
|
||||
const plugin = TransformPlugin(stubNuxt, {
|
||||
mode,
|
||||
getComponents: () => components,
|
||||
serverComponentRuntime: '<repo>/nuxt/src/components/runtime/server-component',
|
||||
}).vite()
|
||||
|
||||
return async (code: string, id: string) => {
|
||||
const result = await (plugin as any).transform!(code, id)
|
||||
|
@ -168,7 +168,11 @@ describe('normalizeRoutes', () => {
|
||||
page.meta.layout = 'test'
|
||||
page.meta.foo = 'bar'
|
||||
|
||||
const { routes, imports } = normalizeRoutes([page], new Set(), true)
|
||||
const { routes, imports } = normalizeRoutes([page], new Set(), {
|
||||
clientComponentRuntime: '<client-component-runtime>',
|
||||
serverComponentRuntime: '<server-component-runtime>',
|
||||
overrideMeta: true,
|
||||
})
|
||||
expect({ routes, imports }).toMatchInlineSnapshot(`
|
||||
{
|
||||
"imports": Set {
|
||||
@ -193,7 +197,11 @@ describe('normalizeRoutes', () => {
|
||||
page.meta.layout = 'test'
|
||||
page.meta.foo = 'bar'
|
||||
|
||||
const { routes, imports } = normalizeRoutes([page], new Set())
|
||||
const { routes, imports } = normalizeRoutes([page], new Set(), {
|
||||
clientComponentRuntime: '<client-component-runtime>',
|
||||
serverComponentRuntime: '<server-component-runtime>',
|
||||
overrideMeta: false,
|
||||
})
|
||||
expect({ routes, imports }).toMatchInlineSnapshot(`
|
||||
{
|
||||
"imports": Set {
|
||||
@ -203,6 +211,7 @@ describe('normalizeRoutes', () => {
|
||||
{
|
||||
name: indexN6pT4Un8hYMeta?.name ?? undefined,
|
||||
path: indexN6pT4Un8hYMeta?.path ?? "/",
|
||||
props: indexN6pT4Un8hYMeta?.props ?? false,
|
||||
meta: { ...(indexN6pT4Un8hYMeta || {}), ...{"layout":"test","foo":"bar"} },
|
||||
alias: indexN6pT4Un8hYMeta?.alias || [],
|
||||
redirect: indexN6pT4Un8hYMeta?.redirect,
|
||||
|
@ -601,6 +601,30 @@ describe('pages:generateRoutesFromFiles', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'route.meta props generate by file',
|
||||
files: [
|
||||
{
|
||||
path: `${pagesDir}/page-with-props.vue`,
|
||||
template: `
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
props: true
|
||||
})
|
||||
</script>
|
||||
`,
|
||||
},
|
||||
],
|
||||
output: [
|
||||
{
|
||||
name: 'page-with-props',
|
||||
path: '/page-with-props',
|
||||
file: `${pagesDir}/page-with-props.vue`,
|
||||
children: [],
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'should handle route groups',
|
||||
files: [
|
||||
@ -667,8 +691,18 @@ describe('pages:generateRoutesFromFiles', () => {
|
||||
|
||||
if (result) {
|
||||
expect(result).toEqual(test.output)
|
||||
normalizedResults[test.description] = normalizeRoutes(result, new Set()).routes
|
||||
normalizedOverrideMetaResults[test.description] = normalizeRoutes(result, new Set(), true).routes
|
||||
|
||||
normalizedResults[test.description] = normalizeRoutes(result, new Set(), {
|
||||
clientComponentRuntime: '<client-component-runtime>',
|
||||
serverComponentRuntime: '<server-component-runtime>',
|
||||
overrideMeta: false,
|
||||
}).routes
|
||||
|
||||
normalizedOverrideMetaResults[test.description] = normalizeRoutes(result, new Set(), {
|
||||
clientComponentRuntime: '<client-component-runtime>',
|
||||
serverComponentRuntime: '<server-component-runtime>',
|
||||
overrideMeta: true,
|
||||
}).routes
|
||||
}
|
||||
})
|
||||
}
|
||||
|
18
packages/rspack/build.config.ts
Normal file
18
packages/rspack/build.config.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
import config from '../webpack/build.config'
|
||||
|
||||
export default defineBuildConfig({
|
||||
...config[0],
|
||||
externals: [
|
||||
'@rspack/core',
|
||||
'#builder',
|
||||
'@nuxt/schema',
|
||||
],
|
||||
entries: [
|
||||
{
|
||||
input: '../webpack/src/index',
|
||||
name: 'index',
|
||||
declaration: true,
|
||||
},
|
||||
],
|
||||
})
|
5
packages/rspack/builder.mjs
Normal file
5
packages/rspack/builder.mjs
Normal file
@ -0,0 +1,5 @@
|
||||
import webpack from '@rspack/core'
|
||||
|
||||
export const builder = 'rspack'
|
||||
export { webpack }
|
||||
export const MiniCssExtractPlugin = webpack.CssExtractRspackPlugin
|
94
packages/rspack/package.json
Normal file
94
packages/rspack/package.json
Normal file
@ -0,0 +1,94 @@
|
||||
{
|
||||
"name": "@nuxt/rspack-builder",
|
||||
"version": "3.12.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||
"directory": "packages/rspack"
|
||||
},
|
||||
"description": "rspack bundler for Nuxt",
|
||||
"homepage": "https://nuxt.com",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"types": "./dist/index.d.ts",
|
||||
"imports": {
|
||||
"#builder": "./builder.mjs"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs"
|
||||
},
|
||||
"./dist/*": "./dist/*"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"builder.mjs"
|
||||
],
|
||||
"scripts": {
|
||||
"prepack": "unbuild"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@rspack/core": "^1.0.14",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"cssnano": "^7.0.6",
|
||||
"defu": "^6.1.4",
|
||||
"esbuild-loader": "^4.2.2",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^9.0.2",
|
||||
"globby": "^14.0.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"hash-sum": "^2.0.0",
|
||||
"jiti": "^2.3.3",
|
||||
"knitwork": "^1.1.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"magic-string": "^0.30.12",
|
||||
"memfs": "^4.14.0",
|
||||
"mlly": "^1.7.2",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
"pify": "^6.1.0",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-import-resolver": "^2.0.0",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"postcss-url": "^10.1.3",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"std-env": "^3.7.0",
|
||||
"time-fix-plugin": "^2.0.7",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^1.14.1",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-bundle-renderer": "^2.1.1",
|
||||
"vue-loader": "^17.4.2",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"webpack-dev-middleware": "^7.4.2",
|
||||
"webpack-hot-middleware": "^2.26.1",
|
||||
"webpack-virtual-modules": "^0.6.2",
|
||||
"webpackbar": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@types/hash-sum": "1.0.2",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
"rollup": "4.24.0",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vue": "3.5.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
}
|
@ -39,23 +39,22 @@
|
||||
"@types/file-loader": "5.0.4",
|
||||
"@types/pug": "2.0.10",
|
||||
"@types/sass-loader": "8.0.9",
|
||||
"@unhead/schema": "1.11.6",
|
||||
"@unhead/schema": "1.11.10",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "4.0.1",
|
||||
"@vue/compiler-core": "3.5.10",
|
||||
"@vue/compiler-sfc": "3.5.10",
|
||||
"@vue/compiler-core": "3.5.12",
|
||||
"@vue/compiler-sfc": "3.5.12",
|
||||
"@vue/language-core": "2.1.6",
|
||||
"c12": "2.0.0-beta.3",
|
||||
"esbuild-loader": "4.2.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"ignore": "6.0.2",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"ofetch": "1.4.0",
|
||||
"unbuild": "3.0.0-rc.8",
|
||||
"ofetch": "1.4.1",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"unctx": "2.3.1",
|
||||
"unenv": "1.10.0",
|
||||
"vite": "5.4.8",
|
||||
"vue": "3.5.10",
|
||||
"vite": "5.4.10",
|
||||
"vue": "3.5.12",
|
||||
"vue-bundle-renderer": "2.1.1",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue-router": "4.4.5",
|
||||
@ -63,18 +62,19 @@
|
||||
"webpack-dev-middleware": "7.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"c12": "^2.0.1",
|
||||
"compatx": "^0.1.8",
|
||||
"consola": "^3.2.3",
|
||||
"defu": "^6.1.4",
|
||||
"hookable": "^5.5.3",
|
||||
"pathe": "^1.1.2",
|
||||
"pkg-types": "^1.2.0",
|
||||
"pkg-types": "^1.2.1",
|
||||
"scule": "^1.3.0",
|
||||
"std-env": "^3.7.0",
|
||||
"ufo": "^1.5.4",
|
||||
"uncrypto": "^0.1.3",
|
||||
"unimport": "^3.13.1",
|
||||
"untyped": "^1.5.0"
|
||||
"untyped": "^1.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
|
@ -7,14 +7,15 @@ import { consola } from 'consola'
|
||||
export default defineUntypedSchema({
|
||||
/**
|
||||
* The builder to use for bundling the Vue part of your application.
|
||||
* @type {'vite' | 'webpack' | { bundle: (nuxt: typeof import('../src/types/nuxt').Nuxt) => Promise<void> }}
|
||||
* @type {'vite' | 'webpack' | 'rspack' | { bundle: (nuxt: typeof import('../src/types/nuxt').Nuxt) => Promise<void> }}
|
||||
*/
|
||||
builder: {
|
||||
$resolve: async (val: 'vite' | 'webpack' | { bundle: (nuxt: unknown) => Promise<void> } | undefined = 'vite', get) => {
|
||||
$resolve: async (val: 'vite' | 'webpack' | 'rspack' | { bundle: (nuxt: unknown) => Promise<void> } | undefined = 'vite', get) => {
|
||||
if (typeof val === 'object') {
|
||||
return val
|
||||
}
|
||||
const map: Record<string, string> = {
|
||||
rspack: '@nuxt/rspack-builder',
|
||||
vite: '@nuxt/vite-builder',
|
||||
webpack: '@nuxt/webpack-builder',
|
||||
}
|
||||
|
@ -424,7 +424,7 @@ export default defineUntypedSchema({
|
||||
*/
|
||||
alias: {
|
||||
$resolve: async (val: Record<string, string>, get): Promise<Record<string, string>> => {
|
||||
const [srcDir, rootDir, assetsDir, publicDir] = await Promise.all([get('srcDir'), get('rootDir'), get('dir.assets'), get('dir.public')]) as [string, string, string, string]
|
||||
const [srcDir, rootDir, assetsDir, publicDir, buildDir] = await Promise.all([get('srcDir'), get('rootDir'), get('dir.assets'), get('dir.public'), get('buildDir')]) as [string, string, string, string, string]
|
||||
return {
|
||||
'~': srcDir,
|
||||
'@': srcDir,
|
||||
@ -432,6 +432,8 @@ export default defineUntypedSchema({
|
||||
'@@': rootDir,
|
||||
[basename(assetsDir)]: resolve(srcDir, assetsDir),
|
||||
[basename(publicDir)]: resolve(srcDir, publicDir),
|
||||
'#build': buildDir,
|
||||
'#internal/nuxt/paths': resolve(buildDir, 'paths.mjs'),
|
||||
...val,
|
||||
}
|
||||
},
|
||||
|
@ -297,8 +297,13 @@ export default defineUntypedSchema({
|
||||
* This only works with static or strings/arrays rather than variables or conditional assignment.
|
||||
*
|
||||
* @see [Nuxt Issues #24770](https://github.com/nuxt/nuxt/issues/24770)
|
||||
* @type {boolean | 'after-resolve'}
|
||||
*/
|
||||
scanPageMeta: true,
|
||||
scanPageMeta: {
|
||||
async $resolve (val, get) {
|
||||
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion === 4 ? 'after-resolve' : true)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Automatically share payload _data_ between pages that are prerendered. This can result in a significant
|
||||
|
@ -20,7 +20,7 @@ export default defineUntypedSchema({
|
||||
* builder environment types (with `false`) to handle this fully yourself, or opt for a 'shared' option.
|
||||
*
|
||||
* The 'shared' option is advised for module authors, who will want to support multiple possible builders.
|
||||
* @type {'vite' | 'webpack' | 'shared' | false | undefined}
|
||||
* @type {'vite' | 'webpack' | 'rspack' | 'shared' | false | undefined}
|
||||
*/
|
||||
builder: {
|
||||
$resolve: val => val ?? null,
|
||||
|
@ -6,7 +6,7 @@ export type { GenerateAppOptions, HookResult, ImportPresetWithDeprecation, NuxtA
|
||||
export type { ImportsOptions } from './types/imports'
|
||||
export type { AppHeadMetaObject, MetaObject, MetaObjectRaw, HeadAugmentations } from './types/head'
|
||||
export type { ModuleDefinition, ModuleMeta, ModuleOptions, ModuleSetupInstallResult, ModuleSetupReturn, NuxtModule, ResolvedModuleOptions } from './types/module'
|
||||
export type { Nuxt, NuxtApp, NuxtPlugin, NuxtPluginTemplate, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate } from './types/nuxt'
|
||||
export type { Nuxt, NuxtApp, NuxtPlugin, NuxtPluginTemplate, NuxtTemplate, NuxtTypeTemplate, NuxtServerTemplate, ResolvedNuxtTemplate } from './types/nuxt'
|
||||
export type { RouterConfig, RouterConfigSerializable, RouterOptions } from './types/router'
|
||||
|
||||
// Schema
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user