mirror of
https://github.com/nuxt/nuxt.git
synced 2025-03-03 19:14:10 +00:00
Merge branch 'main' into patch-21
This commit is contained in:
commit
d65d3174b8
2
.github/workflows/autofix-docs.yml
vendored
2
.github/workflows/autofix-docs.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/autofix.yml
vendored
2
.github/workflows/autofix.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/changelog.yml
vendored
2
.github/workflows/changelog.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
4
.github/workflows/check-links.yml
vendored
4
.github/workflows/check-links.yml
vendored
@ -25,10 +25,10 @@ jobs:
|
|||||||
restore-keys: cache-lychee-
|
restore-keys: cache-lychee-
|
||||||
|
|
||||||
# check links with Lychee
|
# check links with Lychee
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
|
|
||||||
- name: Lychee link checker
|
- name: Lychee link checker
|
||||||
uses: lycheeverse/lychee-action@054a8e8c7a88ada133165c6633a49825a32174e2 # for v1.8.0
|
uses: lycheeverse/lychee-action@25a231001d1723960a301b7d4c82884dc7ef857d # for v1.8.0
|
||||||
with:
|
with:
|
||||||
# arguments with file types to check
|
# arguments with file types to check
|
||||||
args: >-
|
args: >-
|
||||||
|
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
- build
|
- build
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -83,7 +83,7 @@ jobs:
|
|||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
|
uses: github/codeql-action/init@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
|
||||||
with:
|
with:
|
||||||
languages: javascript
|
languages: javascript
|
||||||
queries: +security-and-quality
|
queries: +security-and-quality
|
||||||
@ -95,7 +95,7 @@ jobs:
|
|||||||
path: packages
|
path: packages
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
|
uses: github/codeql-action/analyze@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
|
||||||
with:
|
with:
|
||||||
category: "/language:javascript"
|
category: "/language:javascript"
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ jobs:
|
|||||||
module: ["bundler", "node"]
|
module: ["bundler", "node"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -142,7 +142,7 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -166,7 +166,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- build
|
- build
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -199,8 +199,17 @@ jobs:
|
|||||||
context: ["async", "default"]
|
context: ["async", "default"]
|
||||||
manifest: ["manifest-on", "manifest-off"]
|
manifest: ["manifest-on", "manifest-off"]
|
||||||
version: ["v4", "v3"]
|
version: ["v4", "v3"]
|
||||||
|
payload: ["json", "js"]
|
||||||
node: [18]
|
node: [18]
|
||||||
exclude:
|
exclude:
|
||||||
|
- builder: "webpack"
|
||||||
|
payload: "js"
|
||||||
|
- manifest: "manifest-off"
|
||||||
|
payload: "js"
|
||||||
|
- context: "default"
|
||||||
|
payload: "js"
|
||||||
|
- os: windows-latest
|
||||||
|
payload: "js"
|
||||||
- env: "dev"
|
- env: "dev"
|
||||||
builder: "webpack"
|
builder: "webpack"
|
||||||
- manifest: "manifest-off"
|
- manifest: "manifest-off"
|
||||||
@ -209,7 +218,7 @@ jobs:
|
|||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -236,9 +245,10 @@ jobs:
|
|||||||
TEST_MANIFEST: ${{ matrix.manifest }}
|
TEST_MANIFEST: ${{ matrix.manifest }}
|
||||||
TEST_CONTEXT: ${{ matrix.context }}
|
TEST_CONTEXT: ${{ matrix.context }}
|
||||||
TEST_V4: ${{ matrix.version == 'v4' }}
|
TEST_V4: ${{ matrix.version == 'v4' }}
|
||||||
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || runner.os == 'Windows' }}
|
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@5ecb98a3c6b747ed38dc09f787459979aebb39be # v4.3.1
|
- uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1
|
||||||
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
|
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
@ -260,7 +270,7 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
@ -299,7 +309,7 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@ -17,6 +17,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2
|
uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2
|
||||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/introspect.yml
vendored
2
.github/workflows/introspect.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
# From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
|
# From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
|
||||||
- name: Check workflow files
|
- name: Check workflow files
|
||||||
run: |
|
run: |
|
||||||
|
2
.github/workflows/nuxt2-edge.yml
vendored
2
.github/workflows/nuxt2-edge.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
with:
|
with:
|
||||||
ref: '2.x'
|
ref: '2.x'
|
||||||
fetch-depth: 0 # All history
|
fetch-depth: 0 # All history
|
||||||
|
4
.github/workflows/release-pr.yml
vendored
4
.github/workflows/release-pr.yml
vendored
@ -22,14 +22,14 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Ensure action is by maintainer
|
- name: Ensure action is by maintainer
|
||||||
uses: octokit/request-action@21d174fc38ff59af9cf4d7e07347d29df6dbaa99 # v2.3.0
|
uses: octokit/request-action@872c5c97b3c85c23516a572f02b31401ef82415d # v2.3.1
|
||||||
id: check_role
|
id: check_role
|
||||||
with:
|
with:
|
||||||
route: GET /repos/nuxt/nuxt/collaborators/${{ github.event.comment.user.login }}
|
route: GET /repos/nuxt/nuxt/collaborators/${{ github.event.comment.user.login }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
with:
|
with:
|
||||||
ref: refs/pull/${{ github.event.issue.number }}/merge
|
ref: refs/pull/${{ github.event.issue.number }}/merge
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
2
.github/workflows/reproduire.yml
vendored
2
.github/workflows/reproduire.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
reproduire:
|
reproduire:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
|
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
|
||||||
with:
|
with:
|
||||||
label: needs reproduction
|
label: needs reproduction
|
||||||
|
6
.github/workflows/scorecards.yml
vendored
6
.github/workflows/scorecards.yml
vendored
@ -32,12 +32,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: "Run analysis"
|
- name: "Run analysis"
|
||||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
|
||||||
with:
|
with:
|
||||||
results_file: results.sarif
|
results_file: results.sarif
|
||||||
results_format: sarif
|
results_format: sarif
|
||||||
@ -68,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
|
uses: github/codeql-action/upload-sarif@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
|
||||||
if: github.repository == 'nuxt/nuxt' && success()
|
if: github.repository == 'nuxt/nuxt' && success()
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
@ -33,6 +33,7 @@ It provides a number of features that make it easy to build fast, SEO-friendly,
|
|||||||
- ❤️ [Contribute](#contribute)
|
- ❤️ [Contribute](#contribute)
|
||||||
- 🏠 [Local Development](#local-development)
|
- 🏠 [Local Development](#local-development)
|
||||||
- ⛰️ [Nuxt 2](#nuxt-2)
|
- ⛰️ [Nuxt 2](#nuxt-2)
|
||||||
|
- 🛟 [Professional Support](#professional-support)
|
||||||
- 🔗 [Follow us](#follow-us)
|
- 🔗 [Follow us](#follow-us)
|
||||||
- ⚖️ [License](#license)
|
- ⚖️ [License](#license)
|
||||||
|
|
||||||
@ -104,6 +105,13 @@ Follow the docs to [Set Up Your Local Development Environment](https://nuxt.com/
|
|||||||
|
|
||||||
You can find the code for Nuxt 2 on the [`2.x` branch](https://github.com/nuxt/nuxt/tree/2.x) and the documentation at [v2.nuxt.com](https://v2.nuxt.com).
|
You can find the code for Nuxt 2 on the [`2.x` branch](https://github.com/nuxt/nuxt/tree/2.x) and the documentation at [v2.nuxt.com](https://v2.nuxt.com).
|
||||||
|
|
||||||
|
If you expect to be using Nuxt 2 beyond the EOL (End of Life) date (June 30, 2024), and still need a maintained version that can satisfy security and browser compatibility requirements, make sure to check out [HeroDevs’ NES (Never-Ending Support) Nuxt 2](https://www.herodevs.com/support/nuxt-nes?utm_source=nuxt-github&utm_medium=nuxt-readme).
|
||||||
|
|
||||||
|
## <a name="professional-support">🛟 Professional Support</a>
|
||||||
|
|
||||||
|
- Technical audit & consulting: [Nuxt Experts](https://nuxt.com/enterprise/support)
|
||||||
|
- Custom development & more: [Nuxt Agencies Partners](https://nuxt.com/enterprise/agencies)
|
||||||
|
|
||||||
## <a name="follow-us">🔗 Follow us</a>
|
## <a name="follow-us">🔗 Follow us</a>
|
||||||
|
|
||||||
<p valign="center">
|
<p valign="center">
|
||||||
|
@ -10,6 +10,10 @@ If you are a module author, you can find more specific information in the [Modul
|
|||||||
|
|
||||||
Nuxt offers first-class support for end-to-end and unit testing of your Nuxt application via `@nuxt/test-utils`, a library of test utilities and configuration that currently powers the [tests we use on Nuxt itself](https://github.com/nuxt/nuxt/tree/main/test) and tests throughout the module ecosystem.
|
Nuxt offers first-class support for end-to-end and unit testing of your Nuxt application via `@nuxt/test-utils`, a library of test utilities and configuration that currently powers the [tests we use on Nuxt itself](https://github.com/nuxt/nuxt/tree/main/test) and tests throughout the module ecosystem.
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=yGzwk9xi9gU" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter about getting started with the `@nuxt/test-utils`.
|
||||||
|
::
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
In order to allow you to manage your other testing dependencies, `@nuxt/test-utils` ships with various optional peer dependencies. For example:
|
In order to allow you to manage your other testing dependencies, `@nuxt/test-utils` ships with various optional peer dependencies. For example:
|
||||||
@ -160,21 +164,32 @@ export default defineVitestConfig({
|
|||||||
`mountSuspended` allows you to mount any Vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins. For example:
|
`mountSuspended` allows you to mount any Vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins. For example:
|
||||||
|
|
||||||
```ts twoslash
|
```ts twoslash
|
||||||
import type { Component } from 'vue'
|
|
||||||
import { it, expect } from 'vitest'
|
import { it, expect } from 'vitest'
|
||||||
declare const SomeComponent: Component
|
import type { Component } from 'vue'
|
||||||
declare const App: Component
|
declare module '#components' {
|
||||||
|
export const SomeComponent: Component
|
||||||
|
}
|
||||||
// ---cut---
|
// ---cut---
|
||||||
// tests/components/SomeComponents.nuxt.spec.ts
|
// tests/components/SomeComponents.nuxt.spec.ts
|
||||||
import { mountSuspended } from '@nuxt/test-utils/runtime'
|
import { mountSuspended } from '@nuxt/test-utils/runtime'
|
||||||
|
import { SomeComponent } from '#components'
|
||||||
|
|
||||||
it('can mount some component', async () => {
|
it('can mount some component', async () => {
|
||||||
const component = await mountSuspended(SomeComponent)
|
const component = await mountSuspended(SomeComponent)
|
||||||
expect(component.text()).toMatchInlineSnapshot(
|
expect(component.text()).toMatchInlineSnapshot(
|
||||||
'This is an auto-imported component'
|
'"This is an auto-imported component"'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts twoslash
|
||||||
|
import { it, expect } from 'vitest'
|
||||||
|
// ---cut---
|
||||||
|
// tests/components/SomeComponents.nuxt.spec.ts
|
||||||
|
import { mountSuspended } from '@nuxt/test-utils/runtime'
|
||||||
|
import App from '~/app.vue'
|
||||||
|
|
||||||
// tests/App.nuxt.spec.ts
|
// tests/App.nuxt.spec.ts
|
||||||
it('can also mount an app', async () => {
|
it('can also mount an app', async () => {
|
||||||
const component = await mountSuspended(App, { route: '/test' })
|
const component = await mountSuspended(App, { route: '/test' })
|
||||||
@ -199,13 +214,15 @@ The passed in component will be rendered inside a `<div id="test-wrapper"></div>
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```ts twoslash
|
```ts twoslash
|
||||||
import type { Component } from 'vue'
|
|
||||||
import { it, expect } from 'vitest'
|
import { it, expect } from 'vitest'
|
||||||
declare const SomeComponent: Component
|
import type { Component } from 'vue'
|
||||||
declare const App: Component
|
declare module '#components' {
|
||||||
|
export const SomeComponent: Component
|
||||||
|
}
|
||||||
// ---cut---
|
// ---cut---
|
||||||
// tests/components/SomeComponents.nuxt.spec.ts
|
// tests/components/SomeComponents.nuxt.spec.ts
|
||||||
import { renderSuspended } from '@nuxt/test-utils/runtime'
|
import { renderSuspended } from '@nuxt/test-utils/runtime'
|
||||||
|
import { SomeComponent } from '#components'
|
||||||
import { screen } from '@testing-library/vue'
|
import { screen } from '@testing-library/vue'
|
||||||
|
|
||||||
it('can render some component', async () => {
|
it('can render some component', async () => {
|
||||||
@ -215,13 +232,11 @@ it('can render some component', async () => {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts twoslash
|
```ts twoslash
|
||||||
import type { Component } from 'vue'
|
|
||||||
import { it, expect } from 'vitest'
|
import { it, expect } from 'vitest'
|
||||||
declare const SomeComponent: Component
|
|
||||||
declare const App: Component
|
|
||||||
// ---cut---
|
// ---cut---
|
||||||
// tests/App.nuxt.spec.ts
|
// tests/App.nuxt.spec.ts
|
||||||
import { renderSuspended } from '@nuxt/test-utils/runtime'
|
import { renderSuspended } from '@nuxt/test-utils/runtime'
|
||||||
|
import App from '~/app.vue'
|
||||||
|
|
||||||
it('can also render an app', async () => {
|
it('can also render an app', async () => {
|
||||||
const html = await renderSuspended(App, { route: '/test' })
|
const html = await renderSuspended(App, { route: '/test' })
|
||||||
@ -358,7 +373,7 @@ registerEndpoint('/test/', {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**: If your requests in a component go to external API, you can use `baseURL` and then make it empty using Nuxt Environment Config (`$test`) so all your requests will go to Nitro server.
|
> **Note**: If your requests in a component go to an external API, you can use `baseURL` and then make it empty using [Nuxt Environment Override Config](/docs/getting-started/configuration#environment-overrides) (`$test`) so all your requests will go to Nitro server.
|
||||||
|
|
||||||
#### Conflict with End-To-End Testing
|
#### Conflict with End-To-End Testing
|
||||||
|
|
||||||
|
@ -5,11 +5,11 @@ navigation.icon: i-ph-arrow-circle-up-duotone
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
## Upgrading Nuxt 3
|
## Upgrading Nuxt
|
||||||
|
|
||||||
### Latest release
|
### Latest release
|
||||||
|
|
||||||
To upgrade Nuxt 3 to the [latest release](https://github.com/nuxt/nuxt/releases), use the `nuxi upgrade` command.
|
To upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases), use the `nuxi upgrade` command.
|
||||||
|
|
||||||
```bash [Terminal]
|
```bash [Terminal]
|
||||||
npx nuxi upgrade
|
npx nuxi upgrade
|
||||||
@ -17,7 +17,409 @@ npx nuxi upgrade
|
|||||||
|
|
||||||
### Nightly Release Channel
|
### Nightly Release Channel
|
||||||
|
|
||||||
To use the latest Nuxt 3 build and test features before their release, read about the [nightly release channel](/docs/guide/going-further/nightly-release-channel) guide.
|
To use the latest Nuxt build and test features before their release, read about the [nightly release channel](/docs/guide/going-further/nightly-release-channel) guide.
|
||||||
|
|
||||||
|
## Testing Nuxt 4
|
||||||
|
|
||||||
|
Nuxt 4 is planned to be released **on or before June 14** (though obviously this is dependent on having enough time after Nitro's major release to be properly tested in the community, so be aware that this is not an exact date).
|
||||||
|
|
||||||
|
Until then, it is possible to test many of Nuxt 4's breaking changes on the nightly release channel.
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=r4wFKlcJK6c" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking changes already.
|
||||||
|
::
|
||||||
|
|
||||||
|
### Opting in to Nuxt 4
|
||||||
|
|
||||||
|
First, opt in to the nightly release channel [following these steps](/docs/guide/going-further/nightly-release-channel#opting-in).
|
||||||
|
|
||||||
|
Then you can set your `compatibilityVersion` to match Nuxt 4 behavior:
|
||||||
|
|
||||||
|
```ts twoslash [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
future: {
|
||||||
|
compatibilityVersion: 4,
|
||||||
|
},
|
||||||
|
// To re-enable _all_ Nuxt v3 behavior, set the following options:
|
||||||
|
// srcDir: '.',
|
||||||
|
// dir: {
|
||||||
|
// app: 'app'
|
||||||
|
// },
|
||||||
|
// experimental: {
|
||||||
|
// sharedPrerenderData: false,
|
||||||
|
// compileTemplate: true,
|
||||||
|
// resetAsyncDataToUndefined: true,
|
||||||
|
// templateUtils: true,
|
||||||
|
// relativeWatchPaths: true,
|
||||||
|
// defaults: {
|
||||||
|
// useAsyncData: {
|
||||||
|
// deep: true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// unhead: {
|
||||||
|
// renderSSRHeadOptions: {
|
||||||
|
// omitLineBreaks: false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When you set your `compatibilityVersion` to `4`, defaults throughout your Nuxt configuration will change to opt in to Nuxt v4 behavior, but you can granularly re-enable Nuxt v3 behavior when testing, following the commented out lines above. Please file issues if so, so that we can address them in Nuxt or in the ecosystem.
|
||||||
|
|
||||||
|
### Migrating to Nuxt 4
|
||||||
|
|
||||||
|
Breaking or significant changes will be noted here along with migration steps for backward/forward compatibility.
|
||||||
|
|
||||||
|
::alert
|
||||||
|
This section is subject to change until the final release, so please check back here regularly if you are testing Nuxt 4 using `compatibilityVersion: 4`.
|
||||||
|
::
|
||||||
|
|
||||||
|
#### New Directory Structure
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Significant
|
||||||
|
|
||||||
|
Nuxt now defaults to a new directory structure, with backwards compatibility (so if Nuxt detects you are using the old structure, such as with a top-level `pages/` directory, this new structure will not apply).
|
||||||
|
|
||||||
|
👉 [See full RFC](https://github.com/nuxt/nuxt/issues/26444)
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
* the new Nuxt default `srcDir` is `app/` by default, and most things are resolved from there.
|
||||||
|
* `serverDir` now defaults to `<rootDir>/server` rather than `<srcDir>/server`
|
||||||
|
* `modules` and `public` are resolved relative to `<rootDir>` by default
|
||||||
|
* a new `dir.app` is added, which is the directory we look for `router.options.ts` and `spa-loading-template.html` - this defaults to `<srcDir>/`
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>An example v4 folder structure.</summary>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
.output/
|
||||||
|
.nuxt/
|
||||||
|
app/
|
||||||
|
assets/
|
||||||
|
components/
|
||||||
|
composables/
|
||||||
|
layouts/
|
||||||
|
middleware/
|
||||||
|
pages/
|
||||||
|
plugins/
|
||||||
|
utils/
|
||||||
|
app.config.ts
|
||||||
|
app.vue
|
||||||
|
router.options.ts
|
||||||
|
modules/
|
||||||
|
node_modules/
|
||||||
|
public/
|
||||||
|
server/
|
||||||
|
api/
|
||||||
|
middleware/
|
||||||
|
plugins/
|
||||||
|
routes/
|
||||||
|
utils/
|
||||||
|
nuxt.config.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
👉 For more details, see the [PR implementing this change](https://github.com/nuxt/nuxt/pull/27029).
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
1. **Performance** - placing all your code in the root of your repo causes issues with `.git/` and `node_modules/` folders being scanned/included by FS watchers which can significantly delay startup on non-Mac OSes.
|
||||||
|
1. **IDE type-safety** - `server/` and the rest of your app are running in two entirely different contexts with different global imports available, and making sure `server/` isn't _inside_ the same folder as the rest of your app is a big first step to ensuring you get good auto-completes in your IDE.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
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`, `modules/`, `public/` and `server/` folders remain outside the `app/` folder, in the root of your project.
|
||||||
|
|
||||||
|
However, migration is _not required_. If you wish to keep your current folder structure, Nuxt should auto-detect it. (If it does not, please raise an issue.) You can also force a v3 folder structure with the following configuration:
|
||||||
|
|
||||||
|
```ts [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
// This reverts the new srcDir default from `app` back to your root directory
|
||||||
|
srcDir: '.',
|
||||||
|
// This specifies the directory prefix for `app/router.options.ts` and `app/spa-loading-template.html`
|
||||||
|
dir: {
|
||||||
|
app: 'app'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Shared Prerender Data
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Medium
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
We enabled a previously experimental feature to share data from `useAsyncData` and `useFetch` calls, across different pages. See [original PR](https://github.com/nuxt/nuxt/pull/24894).
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
This feature automatically shares payload _data_ between pages that are prerendered. This can result in a significant performance improvement when prerendering sites that use `useAsyncData` or `useFetch` and fetch the same data in different pages.
|
||||||
|
|
||||||
|
For example, if your site requires a `useFetch` call for every page (for example, to get navigation data for a menu, or site settings from a CMS), this data would only be fetched once when prerendering the first page that uses it, and then cached for use when prerendering other pages.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
Make sure that any unique key of your data is always resolvable to the same data. For example, if you are using `useAsyncData` to fetch data related to a particular page, you should provide a key that uniquely matches that data. (`useFetch` should do this automatically for you.)
|
||||||
|
|
||||||
|
```ts [app/pages/test/[slug\\].vue]
|
||||||
|
// This would be unsafe in a dynamic page (e.g. `[slug].vue`) because the route slug makes a difference
|
||||||
|
// to the data fetched, but Nuxt can't know that because it's not reflected in the key.
|
||||||
|
const route = useRoute()
|
||||||
|
const { data } = await useAsyncData(async () => {
|
||||||
|
return await $fetch(`/api/my-page/${route.params.slug}`)
|
||||||
|
})
|
||||||
|
// Instead, you should use a key that uniquely identifies the data fetched.
|
||||||
|
const { data } = await useAsyncData(route.params.slug, async () => {
|
||||||
|
return await $fetch(`/api/my-page/${route.params.slug}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can disable this feature with:
|
||||||
|
|
||||||
|
```ts twoslash [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
sharedPrerenderData: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Default `data` and `error` values in `useAsyncData` and `useFetch`
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Minimal
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
`data` and `error` objects returned from `useAsyncData` will now default to `undefined`.
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
Previously `data` was initialized to `null` but reset in `clearNuxtData` to `undefined`. `error` was initialized to `null`. This change is to bring greater consistency.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
If you encounter any issues you can revert back to the previous behavior with:
|
||||||
|
|
||||||
|
```ts twoslash [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
defaults: {
|
||||||
|
useAsyncData: {
|
||||||
|
value: 'null',
|
||||||
|
errorValue: 'null'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Please report an issue if you are doing this, as we do not plan to keep this as configurable.
|
||||||
|
|
||||||
|
#### Respect defaults when clearing `data` in `useAsyncData` and `useFetch`
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Minimal
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
If you provide a custom `default` value for `useAsyncData`, this will now be used when calling `clear` or `clearNuxtData` and it will be reset to its default value rather than simply unset.
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
Often users set an appropriately empty value, such as an empty array, to avoid the need to check for `null`/`undefined` when iterating over it. This should be respected when resetting/clearing the data.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
If you encounter any issues you can revert back to the previous behavior, for now, with:
|
||||||
|
|
||||||
|
```ts twoslash [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
resetAsyncDataToUndefined: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Please report an issue if you are doing so, as we do not plan to keep this as configurable.
|
||||||
|
|
||||||
|
#### Shallow Data Reactivity in `useAsyncData` and `useFetch`
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Minimal
|
||||||
|
|
||||||
|
The `data` object returned from `useAsyncData`, `useFetch`, `useLazyAsyncData` and `useLazyFetch` is now a `shallowRef` rather than a `ref`.
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
When new data is fetched, anything depending on `data` will still be reactive because the entire object is replaced. But if your code changes a property _within_ that data structure, this will not trigger any reactivity in your app.
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
This brings a **significant** performance improvement for deeply nested objects and arrays because Vue does not need to watch every single property/array for modification. In most cases, `data` should also be immutable.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
In most cases, no migration steps are required, but if you rely on the reactivity of the data object then you have two options:
|
||||||
|
|
||||||
|
1. You can granularly opt in to deep reactivity on a per-composable basis:
|
||||||
|
```diff
|
||||||
|
- const { data } = useFetch('/api/test')
|
||||||
|
+ const { data } = useFetch('/api/test', { deep: true })
|
||||||
|
```
|
||||||
|
1. You can change the default behavior on a project-wide basis (not recommended):
|
||||||
|
```ts twoslash [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
defaults: {
|
||||||
|
useAsyncData: {
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Absolute Watch Paths in `builder:watch`
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Minimal
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
The Nuxt `builder:watch` hook now emits a path which is absolute rather than relative to your project `srcDir`.
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
This allows us to support watching paths which are outside your `srcDir`, and offers better support for layers and other more complex patterns.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
We have already proactively migrated the public Nuxt modules which we are aware use this hook. See [issue #25339](https://github.com/nuxt/nuxt/issues/25339).
|
||||||
|
|
||||||
|
However, if you are a module author using the `builder:watch` hook and wishing to remain backwards/forwards compatible, you can use the following code to ensure that your code works the same in both Nuxt v3 and Nuxt v4:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
+ import { relative, resolve } from 'node:fs'
|
||||||
|
// ...
|
||||||
|
nuxt.hook('builder:watch', async (event, path) => {
|
||||||
|
+ path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path))
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Directory index scanning
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Medium
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
Child folders in your `middleware/` folder are also scanned for `index` files and these are now also registered as middleware in your project.
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
Nuxt scans a number of folders automatically, including `middleware/` and `plugins/`.
|
||||||
|
|
||||||
|
Child folders in your `plugins/` folder are scanned for `index` files and we wanted to make this behavior consistent between scanned directories.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
Probably no migration is necessary but if you wish to revert to previous behavior you can add a hook to filter out these middleware:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
hooks: {
|
||||||
|
'app:resolve'(app) {
|
||||||
|
app.middleware = app.middleware.filter(mw => !/\/index\.[^/]+$/.test(mw.path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Template Compilation Changes
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Minimal
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
Previously, Nuxt used `lodash/template` to compile templates located on the file system using the `.ejs` file format/syntax.
|
||||||
|
|
||||||
|
In addition, we provided some template utilities (`serialize`, `importName`, `importSources`) which could be used for code-generation within these templates, which are now being removed.
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
In Nuxt v3 we moved to a 'virtual' syntax with a `getContents()` function which is much more flexible and performant.
|
||||||
|
|
||||||
|
In addition, `lodash/template` has had a succession of security issues. These do not really apply to Nuxt projects because it is being used at build-time, not runtime, and by trusted code. However, they still appear in security audits. Moreover, `lodash` is a hefty dependency and is unused by most projects.
|
||||||
|
|
||||||
|
Finally, providing code serialization functions directly within Nuxt is not ideal. Instead, we maintain projects like [unjs/knitwork](http://github.com/unjs/knitwork) which can be dependencies of your project, and where security issues can be reported/resolved directly without requiring an upgrade of Nuxt itself.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
We have raised PRs to update modules using EJS syntax, but if you need to do this yourself, you have three backwards/forwards-compatible alternatives:
|
||||||
|
|
||||||
|
* Moving your string interpolation logic directly into `getContents()`.
|
||||||
|
* Using a custom function to handle the replacement, such as in https://github.com/nuxt-modules/color-mode/pull/240.
|
||||||
|
* Continuing to use `lodash`, as a dependency of _your_ project rather than Nuxt:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
+ import { readFileSync } from 'node:fs'
|
||||||
|
+ import { template } from 'lodash-es'
|
||||||
|
// ...
|
||||||
|
addTemplate({
|
||||||
|
fileName: 'appinsights-vue.js'
|
||||||
|
options: { /* some options */ },
|
||||||
|
- src: resolver.resolve('./runtime/plugin.ejs'),
|
||||||
|
+ getContents({ options }) {
|
||||||
|
+ const contents = readFileSync(resolver.resolve('./runtime/plugin.ejs'), 'utf-8')
|
||||||
|
+ return template(contents)({ options })
|
||||||
|
+ },
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, if you are using the template utilities (`serialize`, `importName`, `importSources`), you can replace them as follows with utilities from `knitwork`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
|
||||||
|
|
||||||
|
const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"{(.+)}"(?=,?$)/gm, r => JSON.parse(r).replace(/^{(.*)}$/, '$1'))
|
||||||
|
|
||||||
|
const importSources = (sources: string | string[], { lazy = false } = {}) => {
|
||||||
|
return toArray(sources).map((src) => {
|
||||||
|
if (lazy) {
|
||||||
|
return `const ${genSafeVariableName(src)} = ${genDynamicImport(src, { comment: `webpackChunkName: ${JSON.stringify(src)}` })}`
|
||||||
|
}
|
||||||
|
return genImport(src, genSafeVariableName(src))
|
||||||
|
}).join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
const importName = genSafeVariableName
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Removal of Experimental Features
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Minimal
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
Four experimental features are no longer configurable in Nuxt 4:
|
||||||
|
|
||||||
|
* `treeshakeClientOnly` will be `true` (default since v3.0)
|
||||||
|
* `configSchema` will be `true` (default since v3.3)
|
||||||
|
* `polyfillVueUseHead` will be `false` (default since v3.4)
|
||||||
|
* `respectNoSSRHeader` will be `false` (default since v3.4)
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
These options have been set to their current values for some time and we do not have a reason to believe that they need to remain configurable.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
* `polyfillVueUseHead` is implementable in user-land with [this plugin](https://github.com/nuxt/nuxt/blob/f209158352b09d1986aa320e29ff36353b91c358/packages/nuxt/src/head/runtime/plugins/vueuse-head-polyfill.ts#L10-L11)
|
||||||
|
|
||||||
|
* `respectNoSSRHeader`is implementable in user-land with [server middleware](https://github.com/nuxt/nuxt/blob/c660b39447f0d5b8790c0826092638d321cd6821/packages/nuxt/src/core/runtime/nitro/no-ssr.ts#L8-L9)
|
||||||
|
|
||||||
## Nuxt 2 vs Nuxt 3
|
## Nuxt 2 vs Nuxt 3
|
||||||
|
|
||||||
@ -26,7 +428,7 @@ In the table below, there is a quick comparison between 3 versions of Nuxt:
|
|||||||
Feature / Version | Nuxt 2 | Nuxt Bridge | Nuxt 3
|
Feature / Version | Nuxt 2 | Nuxt Bridge | Nuxt 3
|
||||||
-------------------------|-----------------|------------------|---------
|
-------------------------|-----------------|------------------|---------
|
||||||
Vue | 2 | 2 | 3
|
Vue | 2 | 2 | 3
|
||||||
Stability | 😊 Stable | 😌 Semi-stable | 😊 Stable
|
Stability | 😊 Stable | 😊 Stable | 😊 Stable
|
||||||
Performance | 🏎 Fast | ✈️ Faster | 🚀 Fastest
|
Performance | 🏎 Fast | ✈️ Faster | 🚀 Fastest
|
||||||
Nitro Engine | ❌ | ✅ | ✅
|
Nitro Engine | ❌ | ✅ | ✅
|
||||||
ESM support | 🌙 Partial | 👍 Better | ✅
|
ESM support | 🌙 Partial | 👍 Better | ✅
|
||||||
|
@ -46,6 +46,10 @@ export default defineNuxtConfig({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=DFZI2iVCrNc" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter about the env-aware `nuxt.config.ts`.
|
||||||
|
::
|
||||||
|
|
||||||
::note
|
::note
|
||||||
If you're authoring layers, you can also use the `$meta` key to provide metadata that you or the consumers of your layer might use.
|
If you're authoring layers, you can also use the `$meta` key to provide metadata that you or the consumers of your layer might use.
|
||||||
::
|
::
|
||||||
|
@ -16,9 +16,9 @@ Both `useFetch` and `useAsyncData` share a common set of options and patterns th
|
|||||||
|
|
||||||
Before that, it's imperative to know why these composables exist in the first place.
|
Before that, it's imperative to know why these composables exist in the first place.
|
||||||
|
|
||||||
## Why using specific composables?
|
## Why use specific composables for data fetching?
|
||||||
|
|
||||||
When using a framework like Nuxt that can perform calls and render pages on both client and server environments, some challenges must be addressed. This is why Nuxt provides composables to wrap your queries, instead of letting the developer rely on [`$fetch`](/docs/api/utils/dollarfetch) calls alone.
|
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
|
### Network calls duplication
|
||||||
|
|
||||||
@ -54,6 +54,10 @@ const { data: count } = await useFetch('/api/count')
|
|||||||
|
|
||||||
This composable is a wrapper around the [`useAsyncData`](/docs/api/composables/use-async-data) composable and `$fetch` utility.
|
This composable is a wrapper around the [`useAsyncData`](/docs/api/composables/use-async-data) composable and `$fetch` utility.
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=njsGVmcWviY" target="_blank"}
|
||||||
|
Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way!
|
||||||
|
::
|
||||||
|
|
||||||
:read-more{to="/docs/api/composables/use-fetch"}
|
:read-more{to="/docs/api/composables/use-fetch"}
|
||||||
|
|
||||||
:link-example{to="/docs/examples/features/data-fetching"}
|
:link-example{to="/docs/examples/features/data-fetching"}
|
||||||
@ -76,7 +80,7 @@ async function addTodo() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
::warning
|
::warning
|
||||||
Beware that using only `$fetch` will not provide [network calls de-duplication and navigation prevention](#why-using-specific-composables). :br
|
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.
|
It is recommended to use `$fetch` for client-side interactions (event based) or combined with [`useAsyncData`](#useasyncdata) when fetching the initial component data.
|
||||||
::
|
::
|
||||||
|
|
||||||
@ -93,6 +97,10 @@ The `useAsyncData` composable is responsible for wrapping async logic and return
|
|||||||
It's developer experience sugar for the most common use case.
|
It's developer experience sugar for the most common use case.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=0X-aOpSGabA" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter to dig deeper into the difference between `useFetch` and `useAsyncData`.
|
||||||
|
::
|
||||||
|
|
||||||
There are some cases when using the [`useFetch`](/docs/api/composables/use-fetch) composable is not appropriate, for example when a CMS or a third-party provide their own query layer. In this case, you can use [`useAsyncData`](/docs/api/composables/use-async-data) to wrap your calls and still keep the benefits provided by the composable.
|
There are some cases when using the [`useFetch`](/docs/api/composables/use-fetch) composable is not appropriate, for example when a CMS or a third-party provide their own query layer. In this case, you can use [`useAsyncData`](/docs/api/composables/use-async-data) to wrap your calls and still keep the benefits provided by the composable.
|
||||||
|
|
||||||
```vue [pages/users.vue]
|
```vue [pages/users.vue]
|
||||||
|
@ -8,6 +8,10 @@ Nuxt provides the [`useState`](/docs/api/composables/use-state) composable to cr
|
|||||||
|
|
||||||
[`useState`](/docs/api/composables/use-state) is an SSR-friendly [`ref`](https://vuejs.org/api/reactivity-core.html#ref) replacement. Its value will be preserved after server-side rendering (during client-side hydration) and shared across all components using a unique key.
|
[`useState`](/docs/api/composables/use-state) is an SSR-friendly [`ref`](https://vuejs.org/api/reactivity-core.html#ref) replacement. Its value will be preserved after server-side rendering (during client-side hydration) and shared across all components using a unique key.
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=mv0WcBABcIk" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter about why and when to use `useState()`.
|
||||||
|
::
|
||||||
|
|
||||||
::important
|
::important
|
||||||
Because the data inside [`useState`](/docs/api/composables/use-state) will be serialized to JSON, it is important that it does not contain anything that cannot be serialized, such as classes, functions or symbols.
|
Because the data inside [`useState`](/docs/api/composables/use-state) will be serialized to JSON, it is important that it does not contain anything that cannot be serialized, such as classes, functions or symbols.
|
||||||
::
|
::
|
||||||
@ -208,6 +212,10 @@ const color = useColor() // Same as useState('color')
|
|||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=dZSNW07sO-A" target="_blank"}
|
||||||
|
Watch a video from Daniel Roe on how to deal with global state and SSR in Nuxt.
|
||||||
|
::
|
||||||
|
|
||||||
## Using third-party libraries
|
## Using third-party libraries
|
||||||
|
|
||||||
Nuxt **used to rely** on the Vuex library to provide global state management. If you are migrating from Nuxt 2, please head to [the migration guide](/docs/migration/configuration#vuex).
|
Nuxt **used to rely** on the Vuex library to provide global state management. If you are migrating from Nuxt 2, please head to [the migration guide](/docs/migration/configuration#vuex).
|
||||||
|
@ -20,6 +20,10 @@ Using Nitro gives Nuxt superpowers:
|
|||||||
|
|
||||||
Nitro is internally using [h3](https://github.com/unjs/h3), a minimal H(TTP) framework built for high performance and portability.
|
Nitro is internally using [h3](https://github.com/unjs/h3), a minimal H(TTP) framework built for high performance and portability.
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=DkvgJa-X31k" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter to understand the responsibilities of Nuxt and Nitro in your application.
|
||||||
|
::
|
||||||
|
|
||||||
## Server Endpoints & Middleware
|
## Server Endpoints & Middleware
|
||||||
|
|
||||||
You can easily manage the server-only part of your Nuxt app, from API endpoints to middleware.
|
You can easily manage the server-only part of your Nuxt app, from API endpoints to middleware.
|
||||||
|
@ -51,7 +51,7 @@ Read more about layers in the **Layer Author Guide**.
|
|||||||
Watch a video from Learn Vue about Nuxt Layers.
|
Watch a video from Learn Vue about Nuxt Layers.
|
||||||
::
|
::
|
||||||
|
|
||||||
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=fr5yo3aVkfA&t=271s" target="_blank"}
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=fr5yo3aVkfA" target="_blank"}
|
||||||
Watch a video from Alexander Lichter about Nuxt Layers.
|
Watch a video from Alexander Lichter about Nuxt Layers.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -16,4 +16,7 @@ surround: false
|
|||||||
::card{icon="i-ph-star-duotone" title="Going Further" to="/docs/guide/going-further"}
|
::card{icon="i-ph-star-duotone" title="Going Further" to="/docs/guide/going-further"}
|
||||||
Master Nuxt with advanced concepts like experimental features, hooks, modules, and more.
|
Master Nuxt with advanced concepts like experimental features, hooks, modules, and more.
|
||||||
::
|
::
|
||||||
|
::card{icon="i-ph-book-open-duotone" title="Recipes" to="/docs/guide/recipes"}
|
||||||
|
Find solutions to common problems and learn how to implement them in your Nuxt project.
|
||||||
|
::
|
||||||
::
|
::
|
||||||
|
@ -60,6 +60,10 @@ That means that (with very few exceptions) you cannot use them outside a Nuxt pl
|
|||||||
|
|
||||||
If you get an error message like `Nuxt instance is unavailable` then it probably means you are calling a Nuxt composable in the wrong place in the Vue or Nuxt lifecycle.
|
If you get an error message like `Nuxt instance is unavailable` then it probably means you are calling a Nuxt composable in the wrong place in the Vue or Nuxt lifecycle.
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=ofuKRZLtOdY" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter about handling async code in composables and fixing `Nuxt instance is unavailable` in your app.
|
||||||
|
::
|
||||||
|
|
||||||
::read-more{to="/docs/guide/going-further/experimental-features#asynccontext" icon="i-ph-star-duotone"}
|
::read-more{to="/docs/guide/going-further/experimental-features#asynccontext" icon="i-ph-star-duotone"}
|
||||||
Checkout the `asyncContext` experimental feature to use Nuxt composables in async functions.
|
Checkout the `asyncContext` experimental feature to use Nuxt composables in async functions.
|
||||||
::
|
::
|
||||||
@ -168,3 +172,7 @@ export default defineNuxtConfig({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=FT2LQJ2NvVI" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter on how to easily set up custom auto imports.
|
||||||
|
::
|
||||||
|
@ -65,6 +65,10 @@ This file contains the recommended basic TypeScript configuration for your proje
|
|||||||
|
|
||||||
[Read more about how to extend this configuration](/docs/guide/directory-structure/tsconfig).
|
[Read more about how to extend this configuration](/docs/guide/directory-structure/tsconfig).
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://youtu.be/umLI7SlPygY" target="_blank"}
|
||||||
|
Watch a video from Daniel Roe explaining built-in Nuxt aliases.
|
||||||
|
::
|
||||||
|
|
||||||
::note
|
::note
|
||||||
Nitro also [auto-generates types](/docs/guide/concepts/server-engine#typed-api-routes) for API routes. Plus, Nuxt also generates types for globally available components and [auto-imports from your composables](/docs/guide/directory-structure/composables), plus other core functionality.
|
Nitro also [auto-generates types](/docs/guide/concepts/server-engine#typed-api-routes) for API routes. Plus, Nuxt also generates types for globally available components and [auto-imports from your composables](/docs/guide/directory-structure/composables), plus other core functionality.
|
||||||
::
|
::
|
||||||
|
@ -259,7 +259,7 @@ Watch Learn Vue video about Nuxt Server Components.
|
|||||||
::
|
::
|
||||||
|
|
||||||
::tip{icon="i-ph-article-duotone" to="https://roe.dev/blog/nuxt-server-components" target="_blank"}
|
::tip{icon="i-ph-article-duotone" to="https://roe.dev/blog/nuxt-server-components" target="_blank"}
|
||||||
Read Daniel Roe's guide to Nuxt server components
|
Read Daniel Roe's guide to Nuxt Server Components.
|
||||||
::
|
::
|
||||||
|
|
||||||
### Standalone server components
|
### Standalone server components
|
||||||
|
@ -46,7 +46,11 @@ export default defineEventHandler(() => {
|
|||||||
|
|
||||||
When starting Nuxt, the `hello` module will be registered and the `/api/hello` route will be available.
|
When starting Nuxt, the `hello` module will be registered and the `/api/hello` route will be available.
|
||||||
|
|
||||||
Local modules are registered in alphabetical order. You can change the order by adding a number to the front of each directory name:
|
Modules are executed in the following sequence:
|
||||||
|
- First, the modules defined in [`nuxt.config.ts`](/docs/api/nuxt-config#modules-1) are loaded.
|
||||||
|
- Then, modules found in the `modules/` directory are executed, and they load in alphabetical order.
|
||||||
|
|
||||||
|
You can change the order of local module by adding a number to the front of each directory name:
|
||||||
|
|
||||||
```bash [Directory structure]
|
```bash [Directory structure]
|
||||||
modules/
|
modules/
|
||||||
|
@ -76,6 +76,10 @@ export default defineNuxtPlugin({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=2aXZyXB1QGQ" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter about the Object Syntax for Nuxt plugins.
|
||||||
|
::
|
||||||
|
|
||||||
::note
|
::note
|
||||||
If you are using the object-syntax, the properties may be statically analyzed in future to produce a more optimized build. So you should not define them at runtime. :br
|
If you are using the object-syntax, the properties may be statically analyzed in future to produce a more optimized build. So you should not define them at runtime. :br
|
||||||
For example, setting `enforce: import.meta.server ? 'pre' : 'post'` would defeat any future optimization Nuxt is able to do for your plugins.
|
For example, setting `enforce: import.meta.server ? 'pre' : 'post'` would defeat any future optimization Nuxt is able to do for your plugins.
|
||||||
|
@ -33,11 +33,25 @@ npx nuxi dev --dotenv .env.local
|
|||||||
|
|
||||||
When updating `.env` in development mode, the Nuxt instance is automatically restarted to apply new values to the `process.env`.
|
When updating `.env` in development mode, the Nuxt instance is automatically restarted to apply new values to the `process.env`.
|
||||||
|
|
||||||
## Production Preview
|
## Production
|
||||||
|
|
||||||
**After your server is built**, you are responsible for setting environment variables when you run the server.
|
**After your server is built**, you are responsible for setting environment variables when you run the server.
|
||||||
|
|
||||||
Your `.env` file will not be read at this point. How you do this is different for every environment.
|
Your `.env` files will not be read at this point. How you do this is different for every environment.
|
||||||
|
|
||||||
|
This design decision was made to ensure compatibility across various deployment environments, some of which may not have a traditional file system available, such as serverless platforms or edge networks like Cloudflare Workers.
|
||||||
|
|
||||||
|
Since `.env` files are not used in production, you must explicitly set environment variables using the tools and methods provided by your hosting environment. Here are some common approaches:
|
||||||
|
|
||||||
|
* You can pass the environment variables as arguments using the terminal:
|
||||||
|
|
||||||
|
`$ DATABASE_HOST=mydatabaseconnectionstring node .output/server/index.mjs`
|
||||||
|
|
||||||
|
* You can set environment variables in shell configuration files like `.bashrc` or `.profile`.
|
||||||
|
|
||||||
|
* Many cloud service providers, such as Vercel, Netlify, and AWS, provide interfaces for setting environment variables via their dashboards, CLI tools or configuration files.
|
||||||
|
|
||||||
|
## Production Preview
|
||||||
|
|
||||||
For local production preview purpose, we recommend using [`nuxi preview`](/docs/api/commands/preview) since using this command, the `.env` file will be loaded into `process.env` for convenience. Note that this command requires dependencies to be installed in the package directory.
|
For local production preview purpose, we recommend using [`nuxi preview`](/docs/api/commands/preview) since using this command, the `.env` file will be loaded into `process.env` for convenience. Note that this command requires dependencies to be installed in the package directory.
|
||||||
|
|
||||||
|
@ -306,6 +306,10 @@ Out of the box, this will enable typed usage of [`navigateTo`](/docs/api/utils/n
|
|||||||
|
|
||||||
You can even get typed params within a page by using `const route = useRoute('route-name')`.
|
You can even get typed params within a page by using `const route = useRoute('route-name')`.
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=SXk-L19gTZk" target="_blank"}
|
||||||
|
Watch a video from Daniel Roe explaining type-safe routing in Nuxt.
|
||||||
|
::
|
||||||
|
|
||||||
## watcher
|
## watcher
|
||||||
|
|
||||||
Set an alternative watcher that will be used as the watching service for Nuxt.
|
Set an alternative watcher that will be used as the watching service for Nuxt.
|
||||||
@ -340,6 +344,10 @@ export default defineNuxtConfig({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=1jUupYHVvrU" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter about the experimental `sharedPrerenderData` setting.
|
||||||
|
::
|
||||||
|
|
||||||
It is particularly important when enabling this feature to make sure that any unique key of your data
|
It is particularly important when enabling this feature to make sure that any unique key of your data
|
||||||
is always resolvable to the same data. For example, if you are using `useAsyncData` to fetch
|
is always resolvable to the same data. For example, if you are using `useAsyncData` to fetch
|
||||||
data related to a particular page, you should provide a key that uniquely matches that data. (`useFetch`
|
data related to a particular page, you should provide a key that uniquely matches that data. (`useFetch`
|
||||||
@ -378,6 +386,16 @@ 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.
|
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.
|
||||||
|
|
||||||
|
<!-- You can disable this feature if it causes issues in your project.
|
||||||
|
|
||||||
|
```ts twoslash [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
scanPageMeta: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
``` -->
|
||||||
|
|
||||||
## cookieStore
|
## cookieStore
|
||||||
|
|
||||||
Enables CookieStore support to listen for cookie updates (if supported by the browser) and refresh `useCookie` ref values.
|
Enables CookieStore support to listen for cookie updates (if supported by the browser) and refresh `useCookie` ref values.
|
||||||
|
@ -40,7 +40,7 @@ There is also a `future` namespace for early opting-in to new features that will
|
|||||||
### compatibilityVersion
|
### compatibilityVersion
|
||||||
|
|
||||||
::important
|
::important
|
||||||
This configuration option is available in Nuxt v3.12+.
|
This configuration option is available in Nuxt v3.12+ or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
||||||
::
|
::
|
||||||
|
|
||||||
This enables early access to Nuxt features or flags.
|
This enables early access to Nuxt features or flags.
|
||||||
@ -69,6 +69,11 @@ export default defineNuxtConfig({
|
|||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
unhead: {
|
||||||
|
renderSSRHeadOptions: {
|
||||||
|
omitLineBreaks: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
@ -61,6 +61,10 @@ Setting the default of `runtimeConfig` values to *differently named environment
|
|||||||
It is advised to use environment variables that match the structure of your `runtimeConfig` object.
|
It is advised to use environment variables that match the structure of your `runtimeConfig` object.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://youtu.be/_FYV5WfiWvs" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter showcasing the top mistake developers make using runtimeConfig.
|
||||||
|
::
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
```sh [.env]
|
```sh [.env]
|
||||||
|
@ -15,6 +15,10 @@ Discover all Nuxt Kit utilities.
|
|||||||
|
|
||||||
You can install the latest Nuxt Kit by adding it to the `dependencies` section of your `package.json`. However, please consider always explicitly installing the `@nuxt/kit` package even if it is already installed by Nuxt.
|
You can install the latest Nuxt Kit by adding it to the `dependencies` section of your `package.json`. However, please consider always explicitly installing the `@nuxt/kit` package even if it is already installed by Nuxt.
|
||||||
|
|
||||||
|
::note
|
||||||
|
`@nuxt/kit` and `@nuxt/schema` are key dependencies for Nuxt. If you are installing it separately, make sure that the versions of `@nuxt/kit` and `@nuxt/schema` are equal to or greater than your `nuxt` version to avoid any unexpected behavior.
|
||||||
|
::
|
||||||
|
|
||||||
```json [package.json]
|
```json [package.json]
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
105
docs/2.guide/4.recipes/3.custom-usefetch.md
Normal file
105
docs/2.guide/4.recipes/3.custom-usefetch.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
navigation.title: 'Custom useFetch'
|
||||||
|
title: Custom useFetch in Nuxt
|
||||||
|
description: How to create a custom fetcher for calling your external API in Nuxt 3.
|
||||||
|
---
|
||||||
|
|
||||||
|
When working with Nuxt, you might be making the frontend and fetching an external API, and you might want to set some default options for fetching from your API.
|
||||||
|
|
||||||
|
The [`$fetch`](/docs/api/utils/dollarfetch) utility function (used by the [`useFetch`](/docs/api/composables/use-fetch) composable) is intentionally not globally configurable. This is important so that fetching behavior throughout your application remains consistent, and other integrations (like modules) can rely on the behavior of core utilities like `$fetch`.
|
||||||
|
|
||||||
|
However, Nuxt provides a way to create a custom fetcher for your API (or multiple fetchers if you have multiple APIs to call).
|
||||||
|
|
||||||
|
## Custom `$fetch`
|
||||||
|
|
||||||
|
Let's create a custom `$fetch` instance with a [Nuxt plugin](/docs/guide/directory-structure/plugins).
|
||||||
|
|
||||||
|
::note
|
||||||
|
`$fetch` is a configured instance of [ofetch](https://github.com/unjs/ofetch) which supports adding the base URL of your Nuxt server as well as direct function calls during SSR (avoiding HTTP roundtrips).
|
||||||
|
::
|
||||||
|
|
||||||
|
Let's pretend here that:
|
||||||
|
- The main API is https://api.nuxt.com
|
||||||
|
- We are storing the JWT token in a session with [nuxt-auth-utils](https://github.com/atinux/nuxt-auth-utils)
|
||||||
|
- If the API responds with a `401` status code, we redirect the user to the `/login` page
|
||||||
|
|
||||||
|
```ts [plugins/api.ts]
|
||||||
|
export default defineNuxtPlugin(() => {
|
||||||
|
const { session } = useUserSession()
|
||||||
|
|
||||||
|
const api = $fetch.create({
|
||||||
|
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}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onResponseError({ response }) {
|
||||||
|
if (response.status === 401) {
|
||||||
|
await navigateTo('/login')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Expose to useNuxtApp().$api
|
||||||
|
return {
|
||||||
|
provide: {
|
||||||
|
api
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
With this Nuxt plugin, `$api` is exposed from `useNuxtApp()` to make API calls directly from the Vue components:
|
||||||
|
|
||||||
|
```vue [app.vue]
|
||||||
|
<script setup>
|
||||||
|
const { $api } = useNuxtApp()
|
||||||
|
const { data: modules } = await useAsyncData('modules', () => $api('/modules'))
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
::callout
|
||||||
|
Wrapping with [`useAsyncData`](/docs/api/composables/use-async-data) **avoid double data fetching when doing server-side rendering** (server & client on hydration).
|
||||||
|
::
|
||||||
|
|
||||||
|
## Custom `useFetch`
|
||||||
|
|
||||||
|
Now that `$api` has the logic we want, let's create a `useAPI` composable to replace the usage of `useAsyncData` + `$api`:
|
||||||
|
|
||||||
|
```ts [composables/useAPI.ts]
|
||||||
|
import type { UseFetchOptions } from 'nuxt/app'
|
||||||
|
|
||||||
|
export function useAPI<T>(
|
||||||
|
url: string | (() => string),
|
||||||
|
options: Omit<UseFetchOptions<T>, 'default'> & { default: () => T | Ref<T> },
|
||||||
|
) {
|
||||||
|
return useFetch(url, {
|
||||||
|
...options,
|
||||||
|
$fetch: useNuxtApp().$api
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's use the new composable and have a nice and clean component:
|
||||||
|
|
||||||
|
```vue [app.vue]
|
||||||
|
<script setup>
|
||||||
|
const { data: modules } = await useAPI('/modules')
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
::callout{icon="i-simple-icons-youtube" color="red" to="https://www.youtube.com/watch?v=jXH8Tr-exhI"}
|
||||||
|
Watch a video about custom `$fetch` and Repository Pattern in Nuxt.
|
||||||
|
::
|
||||||
|
|
||||||
|
::note
|
||||||
|
We are currently discussing to find a cleaner way to let you create a custom fetcher, see https://github.com/nuxt/nuxt/issues/14736.
|
||||||
|
::
|
@ -11,7 +11,7 @@ links:
|
|||||||
---
|
---
|
||||||
|
|
||||||
::important
|
::important
|
||||||
This component will be available in Nuxt v3.12.
|
This component will be available in Nuxt v3.12 or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
||||||
::
|
::
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -25,6 +25,22 @@ In this example, we use `<NuxtLink>` component to link to another page of the ap
|
|||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Passing Params to Dynamic Routes
|
||||||
|
|
||||||
|
In this example, we pass the `id` param to link to the route `~/pages/posts/[id].vue`.
|
||||||
|
|
||||||
|
```vue [pages/index.vue]
|
||||||
|
<template>
|
||||||
|
<NuxtLink :to="{ name: 'posts-id', params: { id: 123 } }">
|
||||||
|
Post 123
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
::tip
|
||||||
|
Check out the Pages panel in Nuxt DevTools to see the route name and the params it might take.
|
||||||
|
::
|
||||||
|
|
||||||
### Handling 404s
|
### Handling 404s
|
||||||
|
|
||||||
When using `<NuxtLink>` for `/public` directory files or when pointing to a different app on the same domain, you should use the `external` prop.
|
When using `<NuxtLink>` for `/public` directory files or when pointing to a different app on the same domain, you should use the `external` prop.
|
||||||
|
@ -30,6 +30,7 @@ You can pass custom HTML or components through the loading indicator's default s
|
|||||||
## Props
|
## Props
|
||||||
|
|
||||||
- `color`: The color of the loading bar. It can be set to `false` to turn off explicit color styling.
|
- `color`: The color of the loading bar. It can be set to `false` to turn off explicit color styling.
|
||||||
|
- `errorColor`: The color of the loading bar when `error` is set to `true`.
|
||||||
- `height`: Height of the loading bar, in pixels (default `3`).
|
- `height`: Height of the loading bar, in pixels (default `3`).
|
||||||
- `duration`: Duration of the loading bar, in milliseconds (default `2000`).
|
- `duration`: Duration of the loading bar, in milliseconds (default `2000`).
|
||||||
- `throttle`: Throttle the appearing and hiding, in milliseconds (default `200`).
|
- `throttle`: Throttle the appearing and hiding, in milliseconds (default `200`).
|
||||||
|
61
docs/3.api/2.composables/on-prehydrate.md
Normal file
61
docs/3.api/2.composables/on-prehydrate.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
title: "onPrehydrate"
|
||||||
|
description: "Use onPrehydrate to run a callback on the client immediately before
|
||||||
|
Nuxt hydrates the page."
|
||||||
|
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 will be available in Nuxt v3.12+ or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
||||||
|
::
|
||||||
|
|
||||||
|
`onPrehydrate` is a composable lifecycle hook that allows you to run a callback on the client immediately before
|
||||||
|
Nuxt hydrates the page.
|
||||||
|
|
||||||
|
::note
|
||||||
|
This is an advanced utility and should be used with care. For example, [`nuxt-time`](https://github.com/danielroe/nuxt-time/pull/251) and [`@nuxtjs/color-mode`](https://github.com/nuxt-modules/color-mode/blob/main/src/script.js) manipulate the DOM to avoid hydration mismatches.
|
||||||
|
::
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
`onPrehydrate` can be called directly in the setup function of a Vue component (for example, in `<script setup>`), or in a plugin.
|
||||||
|
It will only have an effect when it is called on the server, and it will not be included in your client build.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- `callback`: A function that will be stringified and inlined in the HTML. It should not have any external
|
||||||
|
dependencies (such as auto-imports) or refer to variables defined outside the callback. The callback will run
|
||||||
|
before Nuxt runtime initializes so it should not rely on the Nuxt or Vue context.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```vue twoslash [app.vue]
|
||||||
|
<script setup lang="ts">
|
||||||
|
declare const window: Window
|
||||||
|
// ---cut---
|
||||||
|
// onPrehydrate is guaranteed to run before Nuxt hydrates
|
||||||
|
onPrehydrate(() => {
|
||||||
|
console.log(window)
|
||||||
|
})
|
||||||
|
|
||||||
|
// As long as it only has one root node, you can access the element
|
||||||
|
onPrehydrate((el) => {
|
||||||
|
console.log(el.outerHTML)
|
||||||
|
// <div data-v-inspector="app.vue:15:3" data-prehydrate-id=":b3qlvSiBeH:"> Hi there </div>
|
||||||
|
})
|
||||||
|
|
||||||
|
// For _very_ advanced use cases (such as not having a single root node) you
|
||||||
|
// can access/set `data-prehydrate-id` yourself
|
||||||
|
const prehydrateId = onPrehydrate((el) => {})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Hi there
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
@ -59,13 +59,10 @@ Use these options to set the expiration of the cookie.
|
|||||||
The given number will be converted to an integer by rounding down. By default, no maximum age is set.
|
The given number will be converted to an integer by rounding down. By default, no maximum age is set.
|
||||||
|
|
||||||
`expires`: Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1).
|
`expires`: Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1).
|
||||||
By default, no expiration is set. Most clients will consider this a "non-persistent cookie" and
|
By default, no expiration is set. Most clients will consider this a "non-persistent cookie" and will delete it on a condition like exiting a web browser application.
|
||||||
will delete it on a condition like exiting a web browser application.
|
|
||||||
|
|
||||||
::note
|
::note
|
||||||
The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and
|
The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and `maxAge` is set, then `maxAge` takes precedence, but not all clients may obey this, so if both are set, they should point to the same date and time!
|
||||||
`maxAge` is set, then `maxAge` takes precedence, but not all clients may obey this,
|
|
||||||
so if both are set, they should point to the same date and time!
|
|
||||||
::
|
::
|
||||||
|
|
||||||
::note
|
::note
|
||||||
@ -74,22 +71,29 @@ If neither of `expires` and `maxAge` is set, the cookie will be session-only and
|
|||||||
|
|
||||||
### `httpOnly`
|
### `httpOnly`
|
||||||
|
|
||||||
Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6). When truthy,
|
Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6). When truthy, the `HttpOnly` attribute is set; otherwise it is not. By default, the `HttpOnly` attribute is not set.
|
||||||
the `HttpOnly` attribute is set; otherwise it is not. By default, the `HttpOnly` attribute is not set.
|
|
||||||
|
|
||||||
::warning
|
::warning
|
||||||
Be careful when setting this to `true`, as compliant clients will not allow client-side
|
Be careful when setting this to `true`, as compliant clients will not allow client-side JavaScript to see the cookie in `document.cookie`.
|
||||||
JavaScript to see the cookie in `document.cookie`.
|
|
||||||
::
|
::
|
||||||
|
|
||||||
### `secure`
|
### `secure`
|
||||||
|
|
||||||
Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5). When truthy,
|
Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5). When truthy, the `Secure` attribute is set; otherwise it is not. By default, the `Secure` attribute is not set.
|
||||||
the `Secure` attribute is set; otherwise it is not. By default, the `Secure` attribute is not set.
|
|
||||||
|
|
||||||
::warning
|
::warning
|
||||||
Be careful when setting this to `true`, as compliant clients will not send the cookie back to
|
Be careful when setting this to `true`, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection. This can lead to hydration errors.
|
||||||
the server in the future if the browser does not have an HTTPS connection. This can lead to hydration errors.
|
::
|
||||||
|
|
||||||
|
### `partitioned`
|
||||||
|
|
||||||
|
Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set.
|
||||||
|
|
||||||
|
::note
|
||||||
|
This is an attribute that has not yet been fully standardized, and may change in the future.
|
||||||
|
This also means many clients may ignore this attribute until they understand it.
|
||||||
|
|
||||||
|
More information can be found in the [proposal](https://github.com/privacycg/CHIPS).
|
||||||
::
|
::
|
||||||
|
|
||||||
### `domain`
|
### `domain`
|
||||||
@ -114,23 +118,18 @@ More information about the different enforcement levels can be found in [the spe
|
|||||||
|
|
||||||
### `encode`
|
### `encode`
|
||||||
|
|
||||||
Specifies a function that will be used to encode a cookie's value. Since the value of a cookie
|
Specifies a function that will be used to encode a cookie's value. Since the value of a cookie has a limited character set (and must be a simple string), this function can be used to encode a value into a string suited for a cookie's value.
|
||||||
has a limited character set (and must be a simple string), this function can be used to encode
|
|
||||||
a value into a string suited for a cookie's value.
|
|
||||||
|
|
||||||
The default encoder is the `JSON.stringify` + `encodeURIComponent`.
|
The default encoder is the `JSON.stringify` + `encodeURIComponent`.
|
||||||
|
|
||||||
### `decode`
|
### `decode`
|
||||||
|
|
||||||
Specifies a function that will be used to decode a cookie's value. Since the value of a cookie
|
Specifies a function that will be used to decode a cookie's value. Since the value of a cookie has a limited character set (and must be a simple string), this function can be used to decode a previously encoded cookie value into a JavaScript string or other object.
|
||||||
has a limited character set (and must be a simple string), this function can be used to decode
|
|
||||||
a previously encoded cookie value into a JavaScript string or other object.
|
|
||||||
|
|
||||||
The default decoder is `decodeURIComponent` + [destr](https://github.com/unjs/destr).
|
The default decoder is `decodeURIComponent` + [destr](https://github.com/unjs/destr).
|
||||||
|
|
||||||
::note
|
::note
|
||||||
If an error is thrown from this function, the original, non-decoded cookie value will
|
If an error is thrown from this function, the original, non-decoded cookie value will be returned as the cookie's value.
|
||||||
be returned as the cookie's value.
|
|
||||||
::
|
::
|
||||||
|
|
||||||
### `default`
|
### `default`
|
||||||
|
@ -66,6 +66,10 @@ const { data, pending, error, refresh } = await useFetch('/api/auth/login', {
|
|||||||
`useFetch` is a reserved function name transformed by the compiler, so you should not name your own function `useFetch`.
|
`useFetch` is a reserved function name transformed by the compiler, so you should not name your own function `useFetch`.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=njsGVmcWviY" target="_blank"}
|
||||||
|
Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way!
|
||||||
|
::
|
||||||
|
|
||||||
:link-example{to="/docs/examples/advanced/use-custom-fetch-composable"}
|
:link-example{to="/docs/examples/advanced/use-custom-fetch-composable"}
|
||||||
|
|
||||||
:read-more{to="/docs/getting-started/data-fetching"}
|
:read-more{to="/docs/getting-started/data-fetching"}
|
||||||
@ -83,6 +87,8 @@ const { data, pending, error, refresh } = await useFetch('/api/auth/login', {
|
|||||||
- `headers`: Request headers.
|
- `headers`: Request headers.
|
||||||
- `baseURL`: Base URL for the request.
|
- `baseURL`: Base URL for the request.
|
||||||
- `timeout`: Milliseconds to automatically abort request
|
- `timeout`: Milliseconds to automatically abort request
|
||||||
|
- `cache`: Handles cache control according to [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/fetch#cache)
|
||||||
|
- You can pass boolean to disable the cache or you can pass one of the following values: `default`, `no-store`, `reload`, `no-cache`, `force-cache`, and `only-if-cached`.
|
||||||
|
|
||||||
::note
|
::note
|
||||||
All fetch options can be given a `computed` or `ref` value. These will be watched and new requests made automatically with any new values if they are updated.
|
All fetch options can be given a `computed` or `ref` value. These will be watched and new requests made automatically with any new values if they are updated.
|
||||||
|
@ -26,6 +26,11 @@ It hooks into [`page:loading:start`](/docs/api/advanced/hooks#app-hooks-runtime)
|
|||||||
- **type**: `Ref<boolean>`
|
- **type**: `Ref<boolean>`
|
||||||
- **description**: The loading state
|
- **description**: The loading state
|
||||||
|
|
||||||
|
### `error`
|
||||||
|
|
||||||
|
- **type**: `Ref<boolean>`
|
||||||
|
- **description**: The error state
|
||||||
|
|
||||||
### `progress`
|
### `progress`
|
||||||
|
|
||||||
- **type**: `Ref<number>`
|
- **type**: `Ref<number>`
|
||||||
@ -39,7 +44,7 @@ Set `isLoading` to true and start to increase the `progress` value.
|
|||||||
|
|
||||||
### `finish()`
|
### `finish()`
|
||||||
|
|
||||||
Set the `progress` value to `100`, stop all timers and intervals then reset the loading state `500` ms later. `finish` accepts a `{ force: true }` option to skip the interval before the state is reset.
|
Set the `progress` value to `100`, stop all timers and intervals then reset the loading state `500` ms later. `finish` accepts a `{ force: true }` option to skip the interval before the state is reset, and `{ error: true }` to change the loading bar color and set the error property to true.
|
||||||
|
|
||||||
### `clear()`
|
### `clear()`
|
||||||
|
|
||||||
|
@ -18,6 +18,14 @@ const nuxtApp = useNuxtApp()
|
|||||||
|
|
||||||
If runtime context is unavailable in your scope, `useNuxtApp` will throw an exception when called. You can use [`tryUseNuxtApp`](#tryusenuxtapp) instead for composables that do not require `nuxtApp`, or to simply check if context is available or not without an exception.
|
If runtime context is unavailable in your scope, `useNuxtApp` will throw an exception when called. You can use [`tryUseNuxtApp`](#tryusenuxtapp) instead for composables that do not require `nuxtApp`, or to simply check if context is available or not without an exception.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
note
|
||||||
|
By default, the shared runtime context of Nuxt is namespaced under the [`buildId`](/docs/api/nuxt-config#buildid) option. It allows the support of multiple runtime contexts.
|
||||||
|
|
||||||
|
## Params
|
||||||
|
|
||||||
|
- `appName`: an optional application name. If you do not provide it, the Nuxt `buildId` option is used. Otherwise, it must match with an existing `buildId`. -->
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
|
||||||
### `provide (name, value)`
|
### `provide (name, value)`
|
||||||
@ -130,6 +138,10 @@ Nuxt exposes the following properties through `ssrContext`:
|
|||||||
|
|
||||||
Since [Nuxt v3.4](https://nuxt.com/blog/v3-4#payload-enhancements), it is possible to define your own reducer/reviver for types that are not supported by Nuxt.
|
Since [Nuxt v3.4](https://nuxt.com/blog/v3-4#payload-enhancements), it is possible to define your own reducer/reviver for types that are not supported by Nuxt.
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=8w6ffRBs8a4" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter about serializing payloads, especially with regards to classes.
|
||||||
|
::
|
||||||
|
|
||||||
In the example below, we define a reducer (or a serializer) and a reviver (or deserializer) for the [Luxon](https://moment.github.io/luxon/#/) DateTime class, using a payload plugin.
|
In the example below, we define a reducer (or a serializer) and a reviver (or deserializer) for the [Luxon](https://moment.github.io/luxon/#/) DateTime class, using a payload plugin.
|
||||||
|
|
||||||
```ts [plugins/date-time-payload.ts]
|
```ts [plugins/date-time-payload.ts]
|
||||||
@ -278,3 +290,7 @@ export function useStandType() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<!-- ### Params
|
||||||
|
|
||||||
|
- `appName`: an optional application name. If you do not provide it, the Nuxt `buildId` option is used. Otherwise, it must match with an existing `buildId`. -->
|
||||||
|
@ -11,7 +11,7 @@ links:
|
|||||||
---
|
---
|
||||||
|
|
||||||
::important
|
::important
|
||||||
This composable will be available in Nuxt v3.12.
|
This composable will be available in Nuxt v3.12 or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
||||||
::
|
::
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
@ -25,6 +25,10 @@ Because the data inside `useState` will be serialized to JSON, it is important t
|
|||||||
`useState` is a reserved function name transformed by the compiler, so you should not name your own function `useState`.
|
`useState` is a reserved function name transformed by the compiler, so you should not name your own function `useState`.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=mv0WcBABcIk" target="_blank"}
|
||||||
|
Watch a video from Alexander Lichter about why and when to use `useState()`.
|
||||||
|
::
|
||||||
|
|
||||||
## Using `shallowRef`
|
## Using `shallowRef`
|
||||||
|
|
||||||
If you don't need your state to be deeply reactive, you can combine `useState` with [`shallowRef`](https://vuejs.org/api/reactivity-advanced.html#shallowref). This can improve performance when your state contains large objects and arrays.
|
If you don't need your state to be deeply reactive, you can combine `useState` with [`shallowRef`](https://vuejs.org/api/reactivity-advanced.html#shallowref). This can improve performance when your state contains large objects and arrays.
|
||||||
|
@ -18,11 +18,17 @@ The `init` command initializes a fresh Nuxt project using [unjs/giget](https://g
|
|||||||
|
|
||||||
Option | Default | Description
|
Option | Default | Description
|
||||||
-------------------------|-----------------|------------------
|
-------------------------|-----------------|------------------
|
||||||
|
`--cwd` | | Current working directory
|
||||||
|
`--log-level` | | Log level
|
||||||
`--template, -t` | `v3` | Specify template name or git repository to use as a template. Format is `gh:org/name` to use a custom github template.
|
`--template, -t` | `v3` | Specify template name or git repository to use as a template. Format is `gh:org/name` to use a custom github template.
|
||||||
`--force` | `false` | Force clone to any existing directory.
|
`--force, -f` | `false` | Force clone to any existing directory.
|
||||||
`--offline` | `false` | Do not attempt to download from github and only use local cache.
|
`--offline` | `false` | Force offline mode (do not attempt to download template from GitHub and only use local cache).
|
||||||
`--prefer-offline` | `false` | Try local cache first to download templates.
|
`--prefer-offline` | `false` | Prefer offline mode (try local cache first to download templates).
|
||||||
`--shell` | `false` | Open shell in cloned directory (experimental).
|
`--no-install` | `false` | Skip installing dependencies.
|
||||||
|
`--git-init` | `false` | Initialize git repository.
|
||||||
|
`--shell` | `false` | Start shell after installation in project directory (experimental).
|
||||||
|
`--package-manager` | `npm` | Package manager choice (npm, pnpm, yarn, bun).
|
||||||
|
`--dir` | | Project directory.
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|
||||||
|
27
docs/3.api/5.kit/10.runtime-config.md
Normal file
27
docs/3.api/5.kit/10.runtime-config.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: Runtime Config
|
||||||
|
description: Nuxt Kit provides a set of utilities to help you access and modify Nuxt runtime configuration.
|
||||||
|
links:
|
||||||
|
- label: Source
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
to: https://github.com/nuxt/nuxt/blob/main/packages/kit/src/runtime-config.ts
|
||||||
|
size: xs
|
||||||
|
---
|
||||||
|
|
||||||
|
## `useRuntimeConfig`
|
||||||
|
|
||||||
|
At build-time, it is possible to access the resolved Nuxt [runtime config](/docs/guide/going-further/runtime-config).
|
||||||
|
|
||||||
|
### Type
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function useRuntimeConfig (): Record<string, unknown>
|
||||||
|
```
|
||||||
|
|
||||||
|
## `updateRuntimeConfig`
|
||||||
|
|
||||||
|
It is also possible to update runtime configuration. This will be merged with the existing runtime configuration, and if Nitro has already been initialized it will trigger an HMR event to reload the Nitro runtime config.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function updateRuntimeConfig (config: Record<string, unknown>): void | Promise<void>
|
||||||
|
```
|
@ -25,6 +25,12 @@ async function checkNuxtCompatibility(
|
|||||||
interface NuxtCompatibility {
|
interface NuxtCompatibility {
|
||||||
nuxt?: string;
|
nuxt?: string;
|
||||||
bridge?: boolean;
|
bridge?: boolean;
|
||||||
|
builder?: {
|
||||||
|
// Set `false` if your module is not compatible with a builder
|
||||||
|
// or a semver-compatible string version constraint
|
||||||
|
vite?: false | string;
|
||||||
|
webpack?: false | string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NuxtCompatibilityIssue {
|
interface NuxtCompatibilityIssue {
|
||||||
|
@ -32,6 +32,10 @@ We'll do our best to follow our [internal issue decision making flowchart](https
|
|||||||
|
|
||||||
### Send a Pull Request
|
### Send a Pull Request
|
||||||
|
|
||||||
|
::Tip
|
||||||
|
On windows, you need to clone the repository with `git clone -c core.symlinks=true https://github.com/nuxt/nuxt.git` to make symlinks work.
|
||||||
|
::
|
||||||
|
|
||||||
We always welcome pull requests! ❤️
|
We always welcome pull requests! ❤️
|
||||||
|
|
||||||
#### Before You Start
|
#### Before You Start
|
||||||
|
@ -38,12 +38,12 @@ Translations | - | [nuxt/translations#4](https://github.com/nuxt/tra
|
|||||||
|
|
||||||
In addition to the Nuxt framework, there are modules that are vital for the ecosystem. Their status will be updated below.
|
In addition to the Nuxt framework, there are modules that are vital for the ecosystem. Their status will be updated below.
|
||||||
|
|
||||||
Module | Status | Nuxt Support | Repository | Description
|
Module | Status | Nuxt Support | Repository | Description
|
||||||
---------------|---------------------|--------------|------------|-------------------
|
------------------------------------|---------------------|--------------|------------|-------------------
|
||||||
Scripts | April 2024 | 3.x | `nuxt/scripts` to be announced | Easy 3rd party script management. [nuxt/nuxt#22016](https://github.com/nuxt/nuxt/discussions/22016)
|
[Scripts](https://scripts.nuxt.com) | Public Preview | 3.x | [nuxt/scripts](https://github.com/nuxt/scripts) | Easy 3rd party script management.
|
||||||
A11y | Planned | 3.x | `nuxt/a11y` to be announced | Accessibility hinting and utilities [nuxt/nuxt#23255](https://github.com/nuxt/nuxt/issues/23255)
|
A11y | Planned | 3.x | `nuxt/a11y` to be announced | Accessibility hinting and utilities [nuxt/nuxt#23255](https://github.com/nuxt/nuxt/issues/23255)
|
||||||
Auth | Planned | 3.x | `nuxt/auth` to be announced | Nuxt 3 support is planned after session support
|
Auth | Planned | 3.x | `nuxt/auth` to be announced | Nuxt 3 support is planned after session support.
|
||||||
Hints | Planned | 3.x | `nuxt/hints` to be announced | Guidance and suggestions for enhancing development practices
|
Hints | Planned | 3.x | `nuxt/hints` to be announced | Guidance and suggestions for enhancing development practices.
|
||||||
|
|
||||||
## Release Cycle
|
## Release Cycle
|
||||||
|
|
||||||
@ -64,9 +64,9 @@ Each active version has its own nightly releases which are generated automatical
|
|||||||
Release | | Initial release | End Of Life | Docs
|
Release | | Initial release | End Of Life | Docs
|
||||||
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
|
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
|
||||||
**4.x** (scheduled) | | 2024 Q2 | |
|
**4.x** (scheduled) | | 2024 Q2 | |
|
||||||
**3.x** (stable) | <a href="https://npmjs.com/package/nuxt"><img alt="Nuxt latest 3.x version" src="https://flat.badgen.net/npm/v/nuxt?label="></a> | 2022-11-16 | TBA | [nuxt.com](/docs)
|
**3.x** (stable) | <a href="https://npmjs.com/package/nuxt"><img alt="Nuxt latest 3.x version" src="https://flat.badgen.net/npm/v/nuxt?label=" class="not-prose"></a> | 2022-11-16 | TBA | [nuxt.com](/docs)
|
||||||
**2.x** (maintenance) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 2.x version" src="https://flat.badgen.net/npm/v/nuxt/2x?label="></a> | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
|
**2.x** (maintenance) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 2.x version" src="https://flat.badgen.net/npm/v/nuxt/2x?label=" class="not-prose"></a> | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
|
||||||
**1.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 1.x version" src="https://flat.badgen.net/npm/v/nuxt/1x?label="></a> | 2018-01-08 | 2019-09-21 |
|
**1.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 1.x version" src="https://flat.badgen.net/npm/v/nuxt/1x?label=" class="not-prose"></a> | 2018-01-08 | 2019-09-21 |
|
||||||
|
|
||||||
### Support Status
|
### Support Status
|
||||||
|
|
||||||
|
@ -68,6 +68,16 @@ navigation.icon: i-ph-notification-duotone
|
|||||||
::card
|
::card
|
||||||
---
|
---
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
|
title: nuxt/scripts
|
||||||
|
to: https://github.com/nuxt/scripts/tags
|
||||||
|
target: _blank
|
||||||
|
ui.icon.base: text-black dark:text-white
|
||||||
|
---
|
||||||
|
Nuxt Scripts releases. (Public Preview)
|
||||||
|
::
|
||||||
|
::card
|
||||||
|
---
|
||||||
|
icon: i-simple-icons-github
|
||||||
title: nuxt/ui
|
title: nuxt/ui
|
||||||
to: https://github.com/nuxt/ui/releases
|
to: https://github.com/nuxt/ui/releases
|
||||||
target: _blank
|
target: _blank
|
||||||
|
51
package.json
51
package.json
@ -8,11 +8,11 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm --filter @nuxt/ui-templates prepack && pnpm --filter './packages/[^u]**' prepack",
|
"build": "pnpm --filter './packages/**' prepack",
|
||||||
"build:stub": "pnpm dev:prepare",
|
"build:stub": "pnpm dev:prepare",
|
||||||
"cleanup": "rimraf 'packages/**/node_modules' 'playground/node_modules' 'node_modules'",
|
"cleanup": "rimraf 'packages/**/node_modules' 'playground/node_modules' 'node_modules'",
|
||||||
"dev": "pnpm play",
|
"dev": "pnpm play",
|
||||||
"dev:prepare": "pnpm --filter @nuxt/ui-templates prepack && pnpm --filter './packages/[^u]**' prepack --stub",
|
"dev:prepare": "pnpm --filter './packages/**' prepack --stub",
|
||||||
"lint": "eslint . --cache",
|
"lint": "eslint . --cache",
|
||||||
"lint:fix": "eslint . --cache --fix",
|
"lint:fix": "eslint . --cache --fix",
|
||||||
"lint:docs": "markdownlint ./docs && case-police 'docs/**/*.md' *.md",
|
"lint:docs": "markdownlint ./docs && case-police 'docs/**/*.md' *.md",
|
||||||
@ -20,6 +20,7 @@
|
|||||||
"lint:knip": "pnpx knip",
|
"lint:knip": "pnpx knip",
|
||||||
"play": "nuxi dev playground",
|
"play": "nuxi dev playground",
|
||||||
"play:build": "nuxi build playground",
|
"play:build": "nuxi build playground",
|
||||||
|
"play:generate": "nuxi generate playground",
|
||||||
"play:preview": "nuxi preview playground",
|
"play:preview": "nuxi preview playground",
|
||||||
"test": "pnpm test:fixtures && pnpm test:fixtures:dev && pnpm test:fixtures:webpack && pnpm test:unit && pnpm test:runtime && pnpm test:types && pnpm typecheck",
|
"test": "pnpm test:fixtures && pnpm test:fixtures:dev && pnpm test:fixtures:webpack && pnpm test:unit && pnpm test:runtime && pnpm test:types && pnpm typecheck",
|
||||||
"test:prepare": "jiti ./test/prepare.ts",
|
"test:prepare": "jiti ./test/prepare.ts",
|
||||||
@ -28,7 +29,7 @@
|
|||||||
"test:fixtures:webpack": "TEST_BUILDER=webpack pnpm test:fixtures",
|
"test:fixtures:webpack": "TEST_BUILDER=webpack pnpm test:fixtures",
|
||||||
"test:runtime": "vitest -c vitest.nuxt.config.ts",
|
"test:runtime": "vitest -c vitest.nuxt.config.ts",
|
||||||
"test:types": "pnpm --filter './test/fixtures/**' test:types",
|
"test:types": "pnpm --filter './test/fixtures/**' test:types",
|
||||||
"test:unit": "JITI_CACHE=0 vitest run packages/",
|
"test:unit": "vitest run packages/",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs"
|
"typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs"
|
||||||
},
|
},
|
||||||
@ -40,59 +41,59 @@
|
|||||||
"@nuxt/webpack-builder": "workspace:*",
|
"@nuxt/webpack-builder": "workspace:*",
|
||||||
"magic-string": "^0.30.10",
|
"magic-string": "^0.30.10",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"rollup": "^4.17.2",
|
"rollup": "^4.18.0",
|
||||||
"vite": "5.2.11",
|
"vite": "5.2.12",
|
||||||
"vue": "3.4.26"
|
"vue": "3.4.27"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.1.1",
|
"@eslint/js": "9.3.0",
|
||||||
"@nuxt/eslint-config": "0.3.10",
|
"@nuxt/eslint-config": "0.3.13",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@nuxt/test-utils": "3.12.1",
|
"@nuxt/test-utils": "3.13.1",
|
||||||
"@nuxt/webpack-builder": "workspace:*",
|
"@nuxt/webpack-builder": "workspace:*",
|
||||||
"@testing-library/vue": "8.0.3",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/eslint__js": "8.42.3",
|
"@types/eslint__js": "8.42.3",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
"@types/node": "20.12.8",
|
"@types/node": "20.12.13",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@vitest/coverage-v8": "1.5.3",
|
"@vitest/coverage-v8": "1.6.0",
|
||||||
"@vue/test-utils": "2.4.5",
|
"@vue/test-utils": "2.4.6",
|
||||||
"case-police": "0.6.1",
|
"case-police": "0.6.1",
|
||||||
"changelogen": "0.5.5",
|
"changelogen": "0.5.5",
|
||||||
"consola": "3.2.3",
|
"consola": "3.2.3",
|
||||||
"devalue": "5.0.0",
|
"devalue": "5.0.0",
|
||||||
"eslint": "9.1.1",
|
"eslint": "9.3.0",
|
||||||
"eslint-plugin-no-only-tests": "3.1.0",
|
"eslint-plugin-no-only-tests": "3.1.0",
|
||||||
"eslint-plugin-perfectionist": "2.10.0",
|
"eslint-plugin-perfectionist": "2.10.0",
|
||||||
"eslint-typegen": "0.2.4",
|
"eslint-typegen": "0.2.4",
|
||||||
"execa": "8.0.1",
|
"execa": "9.1.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"globby": "14.0.1",
|
"globby": "14.0.1",
|
||||||
"h3": "1.11.1",
|
"h3": "1.11.1",
|
||||||
"happy-dom": "14.7.1",
|
"happy-dom": "14.12.0",
|
||||||
"jiti": "1.21.0",
|
"jiti": "1.21.0",
|
||||||
"markdownlint-cli": "0.40.0",
|
"markdownlint-cli": "0.41.0",
|
||||||
"nitropack": "2.9.6",
|
"nitropack": "2.9.6",
|
||||||
"nuxi": "3.11.1",
|
"nuxi": "3.11.1",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"nuxt-content-twoslash": "0.0.10",
|
"nuxt-content-twoslash": "0.0.10",
|
||||||
"ofetch": "1.3.4",
|
"ofetch": "1.3.4",
|
||||||
"pathe": "1.1.2",
|
"pathe": "1.1.2",
|
||||||
"playwright-core": "1.43.1",
|
"playwright-core": "1.44.1",
|
||||||
"rimraf": "5.0.5",
|
"rimraf": "5.0.7",
|
||||||
"semver": "7.6.0",
|
"semver": "7.6.2",
|
||||||
"std-env": "3.7.0",
|
"std-env": "3.7.0",
|
||||||
"typescript": "5.4.5",
|
"typescript": "5.4.5",
|
||||||
"ufo": "1.5.3",
|
"ufo": "1.5.3",
|
||||||
"vitest": "1.5.3",
|
"vitest": "1.6.0",
|
||||||
"vitest-environment-nuxt": "1.0.0",
|
"vitest-environment-nuxt": "1.0.0",
|
||||||
"vue": "3.4.26",
|
"vue": "3.4.27",
|
||||||
"vue-router": "4.3.2",
|
"vue-router": "4.3.2",
|
||||||
"vue-tsc": "2.0.16"
|
"vue-tsc": "2.0.19"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.0.6",
|
"packageManager": "pnpm@9.1.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^16.10.0 || >=18.0.0"
|
||||||
},
|
},
|
||||||
"version": ""
|
"version": ""
|
||||||
}
|
}
|
||||||
|
@ -30,19 +30,21 @@
|
|||||||
"c12": "^1.10.0",
|
"c12": "^1.10.0",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
|
"destr": "^2.0.3",
|
||||||
"globby": "^14.0.1",
|
"globby": "^14.0.1",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"jiti": "^1.21.0",
|
"jiti": "^1.21.0",
|
||||||
|
"klona": "^2.0.6",
|
||||||
"knitwork": "^1.1.0",
|
"knitwork": "^1.1.0",
|
||||||
"mlly": "^1.7.0",
|
"mlly": "^1.7.0",
|
||||||
"pathe": "^1.1.2",
|
"pathe": "^1.1.2",
|
||||||
"pkg-types": "^1.1.0",
|
"pkg-types": "^1.1.1",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.2",
|
||||||
"ufo": "^1.5.3",
|
"ufo": "^1.5.3",
|
||||||
"unctx": "^2.3.1",
|
"unctx": "^2.3.1",
|
||||||
"unimport": "^3.7.1",
|
"unimport": "^3.7.2",
|
||||||
"untyped": "^1.4.2"
|
"untyped": "^1.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -52,8 +54,8 @@
|
|||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"nitropack": "2.9.6",
|
"nitropack": "2.9.6",
|
||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"vite": "5.2.11",
|
"vite": "5.2.12",
|
||||||
"vitest": "1.5.3",
|
"vitest": "1.6.0",
|
||||||
"webpack": "5.91.0"
|
"webpack": "5.91.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import satisfies from 'semver/functions/satisfies.js' // npm/node-semver#381
|
import satisfies from 'semver/functions/satisfies.js' // npm/node-semver#381
|
||||||
|
import { readPackageJSON } from 'pkg-types'
|
||||||
import type { Nuxt, NuxtCompatibility, NuxtCompatibilityIssues } from '@nuxt/schema'
|
import type { Nuxt, NuxtCompatibility, NuxtCompatibilityIssues } from '@nuxt/schema'
|
||||||
import { useNuxt } from './context'
|
import { useNuxt } from './context'
|
||||||
|
|
||||||
export function normalizeSemanticVersion (version: string) {
|
export function normalizeSemanticVersion (version: string) {
|
||||||
return version.replace(/-[0-9]+\.[0-9a-f]+/, '') // Remove edge prefix
|
return version.replace(/-\d+\.[0-9a-f]+/, '') // Remove edge prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
const builderMap = {
|
||||||
|
'@nuxt/vite-builder': 'vite',
|
||||||
|
'@nuxt/webpack-builder': 'webpack',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,6 +46,28 @@ export async function checkNuxtCompatibility (constraints: NuxtCompatibility, nu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Builder compatibility check
|
||||||
|
if (constraints.builder && typeof nuxt.options.builder === 'string') {
|
||||||
|
const currentBuilder = builderMap[nuxt.options.builder] || nuxt.options.builder
|
||||||
|
if (currentBuilder in constraints.builder) {
|
||||||
|
const constraint = constraints.builder[currentBuilder]!
|
||||||
|
if (constraint === false) {
|
||||||
|
issues.push({
|
||||||
|
name: 'builder',
|
||||||
|
message: `Not compatible with \`${nuxt.options.builder}\`.`,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const builderVersion = await readPackageJSON(nuxt.options.builder, { url: nuxt.options.modulesDir }).then(r => r.version).catch(() => undefined)
|
||||||
|
if (builderVersion && !satisfies(normalizeSemanticVersion(builderVersion), constraint, { includePrerelease: true })) {
|
||||||
|
issues.push({
|
||||||
|
name: 'builder',
|
||||||
|
message: `Not compatible with \`${builderVersion}\` of \`${currentBuilder}\`. This module requires \`${constraint}\`.`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Allow extending compatibility checks
|
// Allow extending compatibility checks
|
||||||
await nuxt.callHook('kit:compatibility', constraints, issues)
|
await nuxt.callHook('kit:compatibility', constraints, issues)
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ export * from './loader/nuxt'
|
|||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
export * from './imports'
|
export * from './imports'
|
||||||
|
export { updateRuntimeConfig, useRuntimeConfig } from './runtime-config'
|
||||||
export * from './build'
|
export * from './build'
|
||||||
export * from './compatibility'
|
export * from './compatibility'
|
||||||
export * from './components'
|
export * from './components'
|
||||||
|
@ -27,7 +27,7 @@ export async function compileTemplate<T> (template: NuxtTemplate<T>, ctx: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"{(.+)}"(?=,?$)/gm, r => JSON.parse(r).replace(/^{(.*)}$/, '$1'))
|
const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"\{(.+)\}"(?=,?$)/gm, r => JSON.parse(r).replace(/^\{(.*)\}$/, '$1'))
|
||||||
|
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
const importSources = (sources: string | string[], { lazy = false } = {}) => {
|
const importSources = (sources: string | string[], { lazy = false } = {}) => {
|
||||||
|
@ -4,6 +4,8 @@ import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12'
|
|||||||
import { loadConfig } from 'c12'
|
import { loadConfig } from 'c12'
|
||||||
import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
|
import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
|
||||||
import { NuxtConfigSchema } from '@nuxt/schema'
|
import { NuxtConfigSchema } from '@nuxt/schema'
|
||||||
|
import { globby } from 'globby'
|
||||||
|
import defu from 'defu'
|
||||||
|
|
||||||
export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}
|
export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}
|
||||||
|
|
||||||
@ -11,12 +13,19 @@ const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'dir']
|
|||||||
const layerSchema = Object.fromEntries(Object.entries(NuxtConfigSchema).filter(([key]) => layerSchemaKeys.includes(key)))
|
const layerSchema = Object.fromEntries(Object.entries(NuxtConfigSchema).filter(([key]) => layerSchemaKeys.includes(key)))
|
||||||
|
|
||||||
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
|
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
|
||||||
|
// Automatically detect and import layers from `~~/layers/` directory
|
||||||
|
opts.overrides = defu(opts.overrides, {
|
||||||
|
_extends: await globby('layers/*', {
|
||||||
|
onlyDirectories: true,
|
||||||
|
cwd: opts.cwd || process.cwd(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
(globalThis as any).defineNuxtConfig = (c: any) => c
|
(globalThis as any).defineNuxtConfig = (c: any) => c
|
||||||
const result = await loadConfig<NuxtConfig>({
|
const result = await loadConfig<NuxtConfig>({
|
||||||
name: 'nuxt',
|
name: 'nuxt',
|
||||||
configFile: 'nuxt.config',
|
configFile: 'nuxt.config',
|
||||||
rcFile: '.nuxtrc',
|
rcFile: '.nuxtrc',
|
||||||
extend: { extendKey: ['theme', 'extends'] },
|
extend: { extendKey: ['theme', 'extends', '_extends'] },
|
||||||
dotenv: true,
|
dotenv: true,
|
||||||
globalRc: true,
|
globalRc: true,
|
||||||
...opts,
|
...opts,
|
||||||
|
@ -73,8 +73,8 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
|
|||||||
const key = `nuxt:module:${uniqueKey || (Math.round(Math.random() * 10000))}`
|
const key = `nuxt:module:${uniqueKey || (Math.round(Math.random() * 10000))}`
|
||||||
const mark = performance.mark(key)
|
const mark = performance.mark(key)
|
||||||
const res = await module.setup?.call(null as any, _options, nuxt) ?? {}
|
const res = await module.setup?.call(null as any, _options, nuxt) ?? {}
|
||||||
const perf = performance.measure(key, mark?.name) // TODO: remove when Node 14 reaches EOL
|
const perf = performance.measure(key, mark.name)
|
||||||
const setupTime = perf ? Math.round((perf.duration * 100)) / 100 : 0 // TODO: remove when Node 14 reaches EOL
|
const setupTime = Math.round((perf.duration * 100)) / 100
|
||||||
|
|
||||||
// Measure setup time
|
// Measure setup time
|
||||||
if (setupTime > 5000 && uniqueKey !== '@nuxt/telemetry') {
|
if (setupTime > 5000 && uniqueKey !== '@nuxt/telemetry') {
|
||||||
|
@ -3,7 +3,6 @@ import type { NuxtPlugin, NuxtPluginTemplate } from '@nuxt/schema'
|
|||||||
import { useNuxt } from './context'
|
import { useNuxt } from './context'
|
||||||
import { addTemplate } from './template'
|
import { addTemplate } from './template'
|
||||||
import { resolveAlias } from './resolve'
|
import { resolveAlias } from './resolve'
|
||||||
import { logger } from './logger'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize a nuxt plugin object
|
* Normalize a nuxt plugin object
|
||||||
@ -20,12 +19,6 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
|
|||||||
throw new Error('Invalid plugin. src option is required: ' + JSON.stringify(plugin))
|
throw new Error('Invalid plugin. src option is required: ' + JSON.stringify(plugin))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: only scan top-level files #18418
|
|
||||||
const nonTopLevelPlugin = plugin.src.match(/\/plugins\/[^/]+\/index\.[^/]+$/i)
|
|
||||||
if (nonTopLevelPlugin && nonTopLevelPlugin.length > 0 && !useNuxt().options.plugins.find(i => (typeof i === 'string' ? i : i.src).endsWith(nonTopLevelPlugin[0]))) {
|
|
||||||
logger.warn(`[deprecation] You are using a plugin that is within a subfolder of your plugins directory without adding it to your config explicitly. You can move it to the top-level plugins directory, or include the file '~${nonTopLevelPlugin[0]}' in your plugins config (https://nuxt.com/docs/api/nuxt-config#plugins-1) to remove this warning.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize full path to plugin
|
// Normalize full path to plugin
|
||||||
plugin.src = normalize(resolveAlias(plugin.src))
|
plugin.src = normalize(resolveAlias(plugin.src))
|
||||||
|
|
||||||
|
103
packages/kit/src/runtime-config.ts
Normal file
103
packages/kit/src/runtime-config.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import process from 'node:process'
|
||||||
|
import destr from 'destr'
|
||||||
|
import { snakeCase } from 'scule'
|
||||||
|
import { klona } from 'klona'
|
||||||
|
|
||||||
|
import defu from 'defu'
|
||||||
|
import { useNuxt } from './context'
|
||||||
|
import { useNitro } from './nitro'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access 'resolved' Nuxt runtime configuration, with values updated from environment.
|
||||||
|
*
|
||||||
|
* This mirrors the runtime behavior of Nitro.
|
||||||
|
*/
|
||||||
|
export function useRuntimeConfig () {
|
||||||
|
const nuxt = useNuxt()
|
||||||
|
return applyEnv(klona(nuxt.options.nitro.runtimeConfig!), {
|
||||||
|
prefix: 'NITRO_',
|
||||||
|
altPrefix: 'NUXT_',
|
||||||
|
envExpansion: nuxt.options.nitro.experimental?.envExpansion ?? !!process.env.NITRO_ENV_EXPANSION,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Nuxt runtime configuration.
|
||||||
|
*/
|
||||||
|
export function updateRuntimeConfig (runtimeConfig: Record<string, unknown>) {
|
||||||
|
const nuxt = useNuxt()
|
||||||
|
Object.assign(nuxt.options.nitro.runtimeConfig as Record<string, unknown>, defu(runtimeConfig, nuxt.options.nitro.runtimeConfig))
|
||||||
|
|
||||||
|
try {
|
||||||
|
return useNitro().updateConfig({ runtimeConfig })
|
||||||
|
} catch {
|
||||||
|
// Nitro is not yet initialised - we can safely ignore this error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://github.com/unjs/nitro/blob/main/src/runtime/utils.env.ts.
|
||||||
|
*
|
||||||
|
* These utils will be replaced by util exposed from nitropack. See https://github.com/unjs/nitro/pull/2404
|
||||||
|
* for more context and future plans.)
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
|
||||||
|
type EnvOptions = {
|
||||||
|
prefix?: string
|
||||||
|
altPrefix?: string
|
||||||
|
envExpansion?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnv (key: string, opts: EnvOptions, env = process.env) {
|
||||||
|
const envKey = snakeCase(key).toUpperCase()
|
||||||
|
return destr(
|
||||||
|
env[opts.prefix + envKey] ?? env[opts.altPrefix + envKey],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isObject (input: unknown) {
|
||||||
|
return typeof input === 'object' && !Array.isArray(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyEnv (
|
||||||
|
obj: Record<string, any>,
|
||||||
|
opts: EnvOptions,
|
||||||
|
parentKey = '',
|
||||||
|
) {
|
||||||
|
for (const key in obj) {
|
||||||
|
const subKey = parentKey ? `${parentKey}_${key}` : key
|
||||||
|
const envValue = getEnv(subKey, opts)
|
||||||
|
if (_isObject(obj[key])) {
|
||||||
|
// Same as before
|
||||||
|
if (_isObject(envValue)) {
|
||||||
|
obj[key] = { ...(obj[key] as any), ...(envValue as any) }
|
||||||
|
applyEnv(obj[key], opts, subKey)
|
||||||
|
} else if (envValue === undefined) {
|
||||||
|
// If envValue is undefined
|
||||||
|
// Then proceed to nested properties
|
||||||
|
applyEnv(obj[key], opts, subKey)
|
||||||
|
} else {
|
||||||
|
// If envValue is a primitive other than undefined
|
||||||
|
// Then set objValue and ignore the nested properties
|
||||||
|
obj[key] = envValue ?? obj[key]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj[key] = envValue ?? obj[key]
|
||||||
|
}
|
||||||
|
// Experimental env expansion
|
||||||
|
if (opts.envExpansion && typeof obj[key] === 'string') {
|
||||||
|
obj[key] = _expandFromEnv(obj[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
const envExpandRx = /\{\{(.*?)\}\}/g
|
||||||
|
|
||||||
|
function _expandFromEnv (value: string, env: Record<string, any> = process.env) {
|
||||||
|
return value.replace(envExpandRx, (match, key) => {
|
||||||
|
return env[key] || match
|
||||||
|
})
|
||||||
|
}
|
@ -202,7 +202,7 @@ export async function _generateTypes (nuxt: Nuxt) {
|
|||||||
} else {
|
} else {
|
||||||
const path = stats?.isFile()
|
const path = stats?.isFile()
|
||||||
// remove extension
|
// remove extension
|
||||||
? relativePath.replace(/(?<=\w)\.\w+$/g, '')
|
? relativePath.replace(/\b\.\w+$/g, '')
|
||||||
// non-existent file probably shouldn't be resolved
|
// non-existent file probably shouldn't be resolved
|
||||||
: aliases[alias]
|
: aliases[alias]
|
||||||
|
|
||||||
@ -230,7 +230,7 @@ export async function _generateTypes (nuxt: Nuxt) {
|
|||||||
tsConfig.compilerOptions!.paths[alias] = await Promise.all(paths.map(async (path: string) => {
|
tsConfig.compilerOptions!.paths[alias] = await Promise.all(paths.map(async (path: string) => {
|
||||||
if (!isAbsolute(path)) { return path }
|
if (!isAbsolute(path)) { return path }
|
||||||
const stats = await fsp.stat(path).catch(() => null /* file does not exist */)
|
const stats = await fsp.stat(path).catch(() => null /* file does not exist */)
|
||||||
return relativeWithDot(nuxt.options.buildDir, stats?.isFile() ? path.replace(/(?<=\w)\.\w+$/g, '') /* remove extension */ : path)
|
return relativeWithDot(nuxt.options.buildDir, stats?.isFile() ? path.replace(/\b\.\w+$/g, '') /* remove extension */ : path)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,15 +60,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/devalue": "^2.0.2",
|
"@nuxt/devalue": "^2.0.2",
|
||||||
"@nuxt/devtools": "^1.2.0",
|
"@nuxt/devtools": "^1.3.2",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"@nuxt/telemetry": "^2.5.4",
|
"@nuxt/telemetry": "^2.5.4",
|
||||||
"@nuxt/vite-builder": "workspace:*",
|
"@nuxt/vite-builder": "workspace:*",
|
||||||
"@unhead/dom": "^1.9.8",
|
"@unhead/dom": "^1.9.11",
|
||||||
"@unhead/ssr": "^1.9.8",
|
"@unhead/ssr": "^1.9.11",
|
||||||
"@unhead/vue": "^1.9.8",
|
"@unhead/vue": "^1.9.11",
|
||||||
"@vue/shared": "^3.4.26",
|
"@vue/shared": "^3.4.27",
|
||||||
"acorn": "8.11.3",
|
"acorn": "8.11.3",
|
||||||
"c12": "^1.10.0",
|
"c12": "^1.10.0",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
@ -76,7 +76,7 @@
|
|||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"destr": "^2.0.3",
|
"destr": "^2.0.3",
|
||||||
"devalue": "^5.0.0",
|
"devalue": "^5.0.0",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.21.4",
|
||||||
"escape-string-regexp": "^5.0.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
"estree-walker": "^3.0.3",
|
"estree-walker": "^3.0.3",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
@ -96,9 +96,10 @@
|
|||||||
"ohash": "^1.1.3",
|
"ohash": "^1.1.3",
|
||||||
"pathe": "^1.1.2",
|
"pathe": "^1.1.2",
|
||||||
"perfect-debounce": "^1.0.0",
|
"perfect-debounce": "^1.0.0",
|
||||||
"pkg-types": "^1.1.0",
|
"pkg-types": "^1.1.1",
|
||||||
"radix3": "^1.1.2",
|
"radix3": "^1.1.2",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
|
"semver": "^7.6.2",
|
||||||
"std-env": "^3.7.0",
|
"std-env": "^3.7.0",
|
||||||
"strip-literal": "^2.1.0",
|
"strip-literal": "^2.1.0",
|
||||||
"ufo": "^1.5.3",
|
"ufo": "^1.5.3",
|
||||||
@ -106,25 +107,25 @@
|
|||||||
"uncrypto": "^0.1.3",
|
"uncrypto": "^0.1.3",
|
||||||
"unctx": "^2.3.1",
|
"unctx": "^2.3.1",
|
||||||
"unenv": "^1.9.0",
|
"unenv": "^1.9.0",
|
||||||
"unimport": "^3.7.1",
|
"unimport": "^3.7.2",
|
||||||
"unplugin": "^1.10.1",
|
"unplugin": "^1.10.1",
|
||||||
"unplugin-vue-router": "^0.7.0",
|
"unplugin-vue-router": "^0.7.0",
|
||||||
"unstorage": "^1.10.2",
|
"unstorage": "^1.10.2",
|
||||||
"untyped": "^1.4.2",
|
"untyped": "^1.4.2",
|
||||||
"vue": "^3.4.26",
|
"vue": "^3.4.27",
|
||||||
"vue-bundle-renderer": "^2.0.0",
|
"vue-bundle-renderer": "^2.1.0",
|
||||||
"vue-devtools-stub": "^0.1.0",
|
"vue-devtools-stub": "^0.1.0",
|
||||||
"vue-router": "^4.3.2"
|
"vue-router": "^4.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/ui-templates": "1.3.3",
|
"@nuxt/ui-templates": "1.3.4",
|
||||||
"@parcel/watcher": "2.4.1",
|
"@parcel/watcher": "2.4.1",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.5",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"vite": "5.2.11",
|
"vite": "5.2.12",
|
||||||
"vitest": "1.5.3"
|
"vitest": "1.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@parcel/watcher": "^2.1.0",
|
"@parcel/watcher": "^2.1.0",
|
||||||
|
@ -8,7 +8,7 @@ import type {
|
|||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref, resolveComponent } from 'vue'
|
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref, resolveComponent } from 'vue'
|
||||||
import type { RouteLocation, RouteLocationRaw, Router, RouterLink, RouterLinkProps, useLink } from '#vue-router'
|
import type { RouteLocation, RouteLocationRaw, Router, RouterLink, RouterLinkProps, useLink } from '#vue-router'
|
||||||
import { hasProtocol, joinURL, parseQuery, parseURL, withTrailingSlash, withoutTrailingSlash } from 'ufo'
|
import { hasProtocol, joinURL, parseQuery, withTrailingSlash, withoutTrailingSlash } from 'ufo'
|
||||||
import { preloadRouteComponents } from '../composables/preload'
|
import { preloadRouteComponents } from '../composables/preload'
|
||||||
import { onNuxtReady } from '../composables/ready'
|
import { onNuxtReady } from '../composables/ready'
|
||||||
import { navigateTo, useRouter } from '../composables/router'
|
import { navigateTo, useRouter } from '../composables/router'
|
||||||
@ -393,7 +393,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
get route () {
|
get route () {
|
||||||
if (!href.value) { return undefined }
|
if (!href.value) { return undefined }
|
||||||
|
|
||||||
const url = parseURL(href.value)
|
const url = new URL(href.value, import.meta.client ? window.location.href : 'http://localhost')
|
||||||
return {
|
return {
|
||||||
path: url.pathname,
|
path: url.pathname,
|
||||||
fullPath: url.pathname,
|
fullPath: url.pathname,
|
||||||
|
@ -20,20 +20,24 @@ export default defineComponent({
|
|||||||
type: [String, Boolean],
|
type: [String, Boolean],
|
||||||
default: 'repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)',
|
default: 'repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)',
|
||||||
},
|
},
|
||||||
|
errorColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'repeating-linear-gradient(to right,#f87171 0%,#ef4444 100%)',
|
||||||
|
},
|
||||||
estimatedProgress: {
|
estimatedProgress: {
|
||||||
type: Function as unknown as () => (duration: number, elapsed: number) => number,
|
type: Function as unknown as () => (duration: number, elapsed: number) => number,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup (props, { slots, expose }) {
|
setup (props, { slots, expose }) {
|
||||||
const { progress, isLoading, start, finish, clear } = useLoadingIndicator({
|
const { progress, isLoading, error, start, finish, clear } = useLoadingIndicator({
|
||||||
duration: props.duration,
|
duration: props.duration,
|
||||||
throttle: props.throttle,
|
throttle: props.throttle,
|
||||||
estimatedProgress: props.estimatedProgress,
|
estimatedProgress: props.estimatedProgress,
|
||||||
})
|
})
|
||||||
|
|
||||||
expose({
|
expose({
|
||||||
progress, isLoading, start, finish, clear,
|
progress, isLoading, error, start, finish, clear,
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => h('div', {
|
return () => h('div', {
|
||||||
@ -47,7 +51,7 @@ export default defineComponent({
|
|||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: `${props.height}px`,
|
height: `${props.height}px`,
|
||||||
opacity: isLoading.value ? 1 : 0,
|
opacity: isLoading.value ? 1 : 0,
|
||||||
background: props.color || undefined,
|
background: error.value ? props.errorColor : props.color || undefined,
|
||||||
backgroundSize: `${(100 / progress.value) * 100}% auto`,
|
backgroundSize: `${(100 / progress.value) * 100}% auto`,
|
||||||
transform: `scaleX(${progress.value}%)`,
|
transform: `scaleX(${progress.value}%)`,
|
||||||
transformOrigin: 'left',
|
transformOrigin: 'left',
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<Suspense @resolve="onResolve">
|
<Suspense @resolve="onResolve">
|
||||||
|
<div v-if="abortRender" />
|
||||||
<ErrorComponent
|
<ErrorComponent
|
||||||
v-if="error"
|
v-else-if="error"
|
||||||
:error="error"
|
:error="error"
|
||||||
/>
|
/>
|
||||||
<IslandRenderer
|
<IslandRenderer
|
||||||
@ -53,6 +54,8 @@ if (import.meta.dev && results && results.some(i => i && 'then' in i)) {
|
|||||||
|
|
||||||
// error handling
|
// error handling
|
||||||
const error = useError()
|
const error = useError()
|
||||||
|
// render an empty <div> when plugins have thrown an error but we're not yet rendering the error page
|
||||||
|
const abortRender = import.meta.server && error.value && !nuxtApp.ssrContext.error
|
||||||
onErrorCaptured((err, target, info) => {
|
onErrorCaptured((err, target, info) => {
|
||||||
nuxtApp.hooks.callHook('vue:error', err, target, info).catch(hookError => console.error('[nuxt] Error in `vue:error` hook', hookError))
|
nuxtApp.hooks.callHook('vue:error', err, target, info).catch(hookError => console.error('[nuxt] Error in `vue:error` hook', hookError))
|
||||||
if (import.meta.server || (isNuxtError(err) && (err.fatal || err.unhandled))) {
|
if (import.meta.server || (isNuxtError(err) && (err.fatal || err.unhandled))) {
|
||||||
|
@ -27,14 +27,6 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* ONLY used in dev mode since we use build:manifest result in production
|
|
||||||
* do not pass any value in production
|
|
||||||
*/
|
|
||||||
rootDir: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
setup (props, { slots }) {
|
setup (props, { slots }) {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { parseURL } from 'ufo'
|
|
||||||
import { defineComponent, h } from 'vue'
|
import { defineComponent, h } from 'vue'
|
||||||
import { parseQuery } from 'vue-router'
|
import { parseQuery } from 'vue-router'
|
||||||
import { resolve } from 'pathe'
|
import { resolve } from 'pathe'
|
||||||
@ -10,15 +9,15 @@ export default (url: string) => defineComponent({
|
|||||||
name: 'NuxtTestComponentWrapper',
|
name: 'NuxtTestComponentWrapper',
|
||||||
|
|
||||||
async setup (props, { attrs }) {
|
async setup (props, { attrs }) {
|
||||||
const query = parseQuery(parseURL(url).search)
|
const query = parseQuery(new URL(url, 'http://localhost').search)
|
||||||
const urlProps = query.props ? destr<Record<string, any>>(query.props as string) : {}
|
const urlProps = query.props ? destr<Record<string, any>>(query.props as string) : {}
|
||||||
const path = resolve(query.path as string)
|
const path = resolve(query.path as string)
|
||||||
if (!path.startsWith(devRootDir)) {
|
if (!path.startsWith(devRootDir)) {
|
||||||
throw new Error(`[nuxt] Cannot access path outside of project root directory: \`${path}\`.`)
|
throw new Error(`[nuxt] Cannot access path outside of project root directory: \`${path}\`.`)
|
||||||
}
|
}
|
||||||
const comp = await import(/* @vite-ignore */ query.path as string).then(r => r.default)
|
const comp = await import(/* @vite-ignore */ path as string).then(r => r.default)
|
||||||
return () => [
|
return () => [
|
||||||
h('div', 'Component Test Wrapper for ' + query.path),
|
h('div', 'Component Test Wrapper for ' + path),
|
||||||
h('div', { id: 'nuxt-component-root' }, [
|
h('div', { id: 'nuxt-component-root' }, [
|
||||||
h(comp, { ...attrs, ...props, ...urlProps }),
|
h(comp, { ...attrs, ...props, ...urlProps }),
|
||||||
]),
|
]),
|
||||||
|
@ -8,7 +8,10 @@ import { createError } from './error'
|
|||||||
import { onNuxtReady } from './ready'
|
import { onNuxtReady } from './ready'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { asyncDataDefaults } from '#build/nuxt.config.mjs'
|
import { asyncDataDefaults, resetAsyncDataToUndefined } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||||
|
|
||||||
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||||
|
|
||||||
@ -42,7 +45,7 @@ export interface AsyncDataOptions<
|
|||||||
ResT,
|
ResT,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> {
|
> {
|
||||||
/**
|
/**
|
||||||
* Whether to fetch on the server side.
|
* Whether to fetch on the server side.
|
||||||
@ -117,7 +120,7 @@ export interface _AsyncData<DataT, ErrorT> {
|
|||||||
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
clear: () => void
|
clear: () => void
|
||||||
error: Ref<ErrorT | null>
|
error: Ref<ErrorT | DefaultAsyncDataErrorValue>
|
||||||
status: Ref<AsyncDataRequestStatus>
|
status: Ref<AsyncDataRequestStatus>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,11 +141,11 @@ export function useAsyncData<
|
|||||||
NuxtErrorDataT = unknown,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
||||||
@ -158,7 +161,7 @@ export function useAsyncData<
|
|||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
||||||
@ -171,12 +174,12 @@ export function useAsyncData<
|
|||||||
NuxtErrorDataT = unknown,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
||||||
@ -194,14 +197,14 @@ export function useAsyncData<
|
|||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
export function useAsyncData<
|
export function useAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
NuxtErrorDataT = unknown,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys>, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null> {
|
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys>, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue> {
|
||||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||||
|
|
||||||
@ -226,14 +229,14 @@ export function useAsyncData<
|
|||||||
const value = nuxtApp.ssrContext!._sharedPrerenderCache!.get(key)
|
const value = nuxtApp.ssrContext!._sharedPrerenderCache!.get(key)
|
||||||
if (value) { return value as Promise<ResT> }
|
if (value) { return value as Promise<ResT> }
|
||||||
|
|
||||||
const promise = nuxtApp.runWithContext(_handler)
|
const promise = Promise.resolve().then(() => nuxtApp.runWithContext(_handler))
|
||||||
|
|
||||||
nuxtApp.ssrContext!._sharedPrerenderCache!.set(key, promise)
|
nuxtApp.ssrContext!._sharedPrerenderCache!.set(key, promise)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to get default values
|
// Used to get default values
|
||||||
const getDefault = () => null
|
const getDefault = () => asyncDataDefaults.value
|
||||||
const getDefaultCachedData = () => nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]
|
const getDefaultCachedData = () => nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]
|
||||||
|
|
||||||
// Apply defaults
|
// Apply defaults
|
||||||
@ -250,11 +253,12 @@ export function useAsyncData<
|
|||||||
console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.')
|
console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: make more precise when v4 lands
|
||||||
const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null
|
const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null
|
||||||
|
|
||||||
// Create or use a shared asyncData entity
|
// Create or use a shared asyncData entity
|
||||||
if (!nuxtApp._asyncData[key] || !options.immediate) {
|
if (!nuxtApp._asyncData[key] || !options.immediate) {
|
||||||
nuxtApp.payload._errors[key] ??= null
|
nuxtApp.payload._errors[key] ??= asyncDataDefaults.errorValue
|
||||||
|
|
||||||
const _ref = options.deep ? ref : shallowRef
|
const _ref = options.deep ? ref : shallowRef
|
||||||
|
|
||||||
@ -263,11 +267,15 @@ export function useAsyncData<
|
|||||||
pending: ref(!hasCachedData()),
|
pending: ref(!hasCachedData()),
|
||||||
error: toRef(nuxtApp.payload._errors, key),
|
error: toRef(nuxtApp.payload._errors, key),
|
||||||
status: ref('idle'),
|
status: ref('idle'),
|
||||||
|
_default: options.default!,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher
|
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher
|
||||||
const asyncData = { ...nuxtApp._asyncData[key] } as AsyncData<DataT | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)>
|
const asyncData = { ...nuxtApp._asyncData[key] } as { _default?: unknown } & AsyncData<DataT | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)>
|
||||||
|
|
||||||
|
// Don't expose default function to end user
|
||||||
|
delete asyncData._default
|
||||||
|
|
||||||
asyncData.refresh = asyncData.execute = (opts = {}) => {
|
asyncData.refresh = asyncData.execute = (opts = {}) => {
|
||||||
if (nuxtApp._asyncDataPromises[key]) {
|
if (nuxtApp._asyncDataPromises[key]) {
|
||||||
@ -307,7 +315,7 @@ export function useAsyncData<
|
|||||||
nuxtApp.payload.data[key] = result
|
nuxtApp.payload.data[key] = result
|
||||||
|
|
||||||
asyncData.data.value = result
|
asyncData.data.value = result
|
||||||
asyncData.error.value = null
|
asyncData.error.value = asyncDataDefaults.errorValue
|
||||||
asyncData.status.value = 'success'
|
asyncData.status.value = 'success'
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@ -378,9 +386,7 @@ export function useAsyncData<
|
|||||||
const hasScope = getCurrentScope()
|
const hasScope = getCurrentScope()
|
||||||
if (options.watch) {
|
if (options.watch) {
|
||||||
const unsub = watch(options.watch, () => asyncData.refresh())
|
const unsub = watch(options.watch, () => asyncData.refresh())
|
||||||
if (instance) {
|
if (hasScope) {
|
||||||
onUnmounted(unsub)
|
|
||||||
} else if (hasScope) {
|
|
||||||
onScopeDispose(unsub)
|
onScopeDispose(unsub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -389,9 +395,7 @@ export function useAsyncData<
|
|||||||
await asyncData.refresh()
|
await asyncData.refresh()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (instance) {
|
if (hasScope) {
|
||||||
onUnmounted(off)
|
|
||||||
} else if (hasScope) {
|
|
||||||
onScopeDispose(off)
|
onScopeDispose(off)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,11 +412,11 @@ export function useLazyAsyncData<
|
|||||||
DataE = Error,
|
DataE = Error,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
@ -422,18 +426,18 @@ export function useLazyAsyncData<
|
|||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
@ -444,15 +448,15 @@ export function useLazyAsyncData<
|
|||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
|
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null> {
|
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue> {
|
||||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||||
const [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise<ResT>, AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>]
|
const [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise<ResT>, AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>]
|
||||||
@ -467,12 +471,12 @@ export function useLazyAsyncData<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @since 3.1.0 */
|
/** @since 3.1.0 */
|
||||||
export function useNuxtData<DataT = any> (key: string): { data: Ref<DataT | null> } {
|
export function useNuxtData<DataT = any> (key: string): { data: Ref<DataT | DefaultAsyncDataValue> } {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
|
||||||
// Initialize value when key is not already set
|
// Initialize value when key is not already set
|
||||||
if (!(key in nuxtApp.payload.data)) {
|
if (!(key in nuxtApp.payload.data)) {
|
||||||
nuxtApp.payload.data[key] = null
|
nuxtApp.payload.data[key] = asyncDataDefaults.value
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -524,12 +528,12 @@ function clearNuxtDataByKey (nuxtApp: NuxtApp, key: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key in nuxtApp.payload._errors) {
|
if (key in nuxtApp.payload._errors) {
|
||||||
nuxtApp.payload._errors[key] = null
|
nuxtApp.payload._errors[key] = asyncDataDefaults.errorValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nuxtApp._asyncData[key]) {
|
if (nuxtApp._asyncData[key]) {
|
||||||
nuxtApp._asyncData[key]!.data.value = undefined
|
nuxtApp._asyncData[key]!.data.value = resetAsyncDataToUndefined ? undefined : nuxtApp._asyncData[key]!._default()
|
||||||
nuxtApp._asyncData[key]!.error.value = null
|
nuxtApp._asyncData[key]!.error.value = asyncDataDefaults.errorValue
|
||||||
nuxtApp._asyncData[key]!.pending.value = false
|
nuxtApp._asyncData[key]!.pending.value = false
|
||||||
nuxtApp._asyncData[key]!.status.value = 'idle'
|
nuxtApp._asyncData[key]!.status.value = 'idle'
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@ import { toRef } from 'vue'
|
|||||||
import { useNuxtApp } from '../nuxt'
|
import { useNuxtApp } from '../nuxt'
|
||||||
import { useRouter } from './router'
|
import { useRouter } from './router'
|
||||||
|
|
||||||
|
// @ts-expect-error virtual file
|
||||||
|
import { nuxtDefaultErrorValue } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
|
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
|
||||||
|
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
@ -47,7 +50,7 @@ export const clearError = async (options: { redirect?: string } = {}) => {
|
|||||||
await useRouter().replace(options.redirect)
|
await useRouter().replace(options.redirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
error.value = null
|
error.value = nuxtDefaultErrorValue
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
|
@ -8,6 +8,9 @@ import { useRequestFetch } from './ssr'
|
|||||||
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom } from './asyncData'
|
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom } from './asyncData'
|
||||||
import { useAsyncData } from './asyncData'
|
import { useAsyncData } from './asyncData'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { fetchDefaults } from '#build/nuxt.config.mjs'
|
import { fetchDefaults } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
@ -30,7 +33,7 @@ export interface UseFetchOptions<
|
|||||||
ResT,
|
ResT,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
R extends NitroFetchRequest = string & {},
|
R extends NitroFetchRequest = string & {},
|
||||||
M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>,
|
M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>,
|
||||||
> extends Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'watch'>, ComputedFetchOptions<R, M> {
|
> extends Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'watch'>, ComputedFetchOptions<R, M> {
|
||||||
@ -54,11 +57,11 @@ export function useFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Fetch data from an API endpoint with an SSR-friendly composable.
|
* Fetch data from an API endpoint with an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-fetch}
|
* See {@link https://nuxt.com/docs/api/composables/use-fetch}
|
||||||
@ -77,7 +80,7 @@ export function useFetch<
|
|||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
export function useFetch<
|
export function useFetch<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
ErrorT = FetchError,
|
ErrorT = FetchError,
|
||||||
@ -86,7 +89,7 @@ export function useFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
|
arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
|
||||||
@ -161,8 +164,10 @@ export function useFetch<
|
|||||||
* @see https://github.com/unjs/ofetch/blob/bb2d72baa5d3f332a2185c20fc04e35d2c3e258d/src/fetch.ts#L152
|
* @see https://github.com/unjs/ofetch/blob/bb2d72baa5d3f332a2185c20fc04e35d2c3e258d/src/fetch.ts#L152
|
||||||
*/
|
*/
|
||||||
const timeoutLength = toValue(opts.timeout)
|
const timeoutLength = toValue(opts.timeout)
|
||||||
|
let timeoutId: NodeJS.Timeout
|
||||||
if (timeoutLength) {
|
if (timeoutLength) {
|
||||||
setTimeout(() => controller.abort(), timeoutLength)
|
timeoutId = setTimeout(() => controller.abort(), timeoutLength)
|
||||||
|
controller.signal.onabort = () => clearTimeout(timeoutId)
|
||||||
}
|
}
|
||||||
|
|
||||||
let _$fetch = opts.$fetch || globalThis.$fetch
|
let _$fetch = opts.$fetch || globalThis.$fetch
|
||||||
@ -175,7 +180,7 @@ export function useFetch<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _$fetch(_request.value, { signal: controller.signal, ..._fetchOptions } as any) as Promise<_ResT>
|
return _$fetch(_request.value, { signal: controller.signal, ..._fetchOptions } as any).finally(() => { clearTimeout(timeoutId) }) as Promise<_ResT>
|
||||||
}, _asyncDataOptions)
|
}, _asyncDataOptions)
|
||||||
|
|
||||||
return asyncData
|
return asyncData
|
||||||
@ -190,11 +195,11 @@ export function useLazyFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
export function useLazyFetch<
|
export function useLazyFetch<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
ErrorT = FetchError,
|
ErrorT = FetchError,
|
||||||
@ -207,7 +212,7 @@ export function useLazyFetch<
|
|||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
export function useLazyFetch<
|
export function useLazyFetch<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
ErrorT = FetchError,
|
ErrorT = FetchError,
|
||||||
@ -216,7 +221,7 @@ export function useLazyFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
arg1?: string | Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>,
|
arg1?: string | Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>,
|
||||||
|
@ -24,7 +24,7 @@ export { useFetch, useLazyFetch } from './fetch'
|
|||||||
export type { FetchResult, UseFetchOptions } from './fetch'
|
export type { FetchResult, UseFetchOptions } from './fetch'
|
||||||
export { useCookie, refreshCookie } from './cookie'
|
export { useCookie, refreshCookie } from './cookie'
|
||||||
export type { CookieOptions, CookieRef } from './cookie'
|
export type { CookieOptions, CookieRef } from './cookie'
|
||||||
export { prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus } from './ssr'
|
export { onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus } from './ssr'
|
||||||
export { onNuxtReady } from './ready'
|
export { onNuxtReady } from './ready'
|
||||||
export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter } from './router'
|
export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter } from './router'
|
||||||
export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router'
|
export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router'
|
||||||
|
@ -23,9 +23,10 @@ export type LoadingIndicator = {
|
|||||||
_cleanup: () => void
|
_cleanup: () => void
|
||||||
progress: Ref<number>
|
progress: Ref<number>
|
||||||
isLoading: Ref<boolean>
|
isLoading: Ref<boolean>
|
||||||
|
error: Ref<boolean>
|
||||||
start: () => void
|
start: () => void
|
||||||
set: (value: number) => void
|
set: (value: number) => void
|
||||||
finish: (opts?: { force?: boolean }) => void
|
finish: (opts?: { force?: boolean, error?: boolean }) => void
|
||||||
clear: () => void
|
clear: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
|||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const progress = ref(0)
|
const progress = ref(0)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
const error = ref(false)
|
||||||
let done = false
|
let done = false
|
||||||
let rafId: number
|
let rafId: number
|
||||||
|
|
||||||
@ -47,7 +49,10 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
|||||||
let hideTimeout: number | NodeJS.Timeout
|
let hideTimeout: number | NodeJS.Timeout
|
||||||
let resetTimeout: number | NodeJS.Timeout
|
let resetTimeout: number | NodeJS.Timeout
|
||||||
|
|
||||||
const start = () => set(0)
|
const start = () => {
|
||||||
|
error.value = false
|
||||||
|
set(0)
|
||||||
|
}
|
||||||
|
|
||||||
function set (at = 0) {
|
function set (at = 0) {
|
||||||
if (nuxtApp.isHydrating) {
|
if (nuxtApp.isHydrating) {
|
||||||
@ -76,11 +81,14 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function finish (opts: { force?: boolean } = {}) {
|
function finish (opts: { force?: boolean, error?: boolean } = {}) {
|
||||||
progress.value = 100
|
progress.value = 100
|
||||||
done = true
|
done = true
|
||||||
clear()
|
clear()
|
||||||
_clearTimeouts()
|
_clearTimeouts()
|
||||||
|
if (opts.error) {
|
||||||
|
error.value = true
|
||||||
|
}
|
||||||
if (opts.force) {
|
if (opts.force) {
|
||||||
progress.value = 0
|
progress.value = 0
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
@ -145,6 +153,7 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
|||||||
_cleanup,
|
_cleanup,
|
||||||
progress: computed(() => progress.value),
|
progress: computed(() => progress.value),
|
||||||
isLoading: computed(() => isLoading.value),
|
isLoading: computed(() => isLoading.value),
|
||||||
|
error: computed(() => error.value),
|
||||||
start,
|
start,
|
||||||
set,
|
set,
|
||||||
finish,
|
finish,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type { MatcherExport, RouteMatcher } from 'radix3'
|
import type { MatcherExport, RouteMatcher } from 'radix3'
|
||||||
import { createMatcherFromExport, createRouter as createRadixRouter, toRouteMatcher } from 'radix3'
|
import { createMatcherFromExport, createRouter as createRadixRouter, toRouteMatcher } from 'radix3'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import { useAppConfig } from '../config'
|
|
||||||
import { useRuntimeConfig } from '../nuxt'
|
import { useRuntimeConfig } from '../nuxt'
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { appManifest as isAppManifestEnabled } from '#build/nuxt.config.mjs'
|
import { appManifest as isAppManifestEnabled } from '#build/nuxt.config.mjs'
|
||||||
@ -25,9 +24,7 @@ function fetchManifest () {
|
|||||||
if (!isAppManifestEnabled) {
|
if (!isAppManifestEnabled) {
|
||||||
throw new Error('[nuxt] app manifest should be enabled with `experimental.appManifest`')
|
throw new Error('[nuxt] app manifest should be enabled with `experimental.appManifest`')
|
||||||
}
|
}
|
||||||
// @ts-expect-error private property
|
manifest = $fetch<NuxtAppManifest>(buildAssetsURL(`builds/meta/${useRuntimeConfig().app.buildId}.json`))
|
||||||
const buildId = useAppConfig().nuxt?.buildId
|
|
||||||
manifest = $fetch<NuxtAppManifest>(buildAssetsURL(`builds/meta/${buildId}.json`))
|
|
||||||
manifest.then((m) => {
|
manifest.then((m) => {
|
||||||
matcher = createMatcherFromExport(m.matcher)
|
matcher = createMatcherFromExport(m.matcher)
|
||||||
})
|
})
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { hasProtocol, joinURL, withoutTrailingSlash } from 'ufo'
|
import { hasProtocol, joinURL, withoutTrailingSlash } from 'ufo'
|
||||||
import { parse } from 'devalue'
|
import { parse } from 'devalue'
|
||||||
import { useHead } from '@unhead/vue'
|
import { useHead } from '@unhead/vue'
|
||||||
import { getCurrentInstance } from 'vue'
|
import { getCurrentInstance, onServerPrefetch } from 'vue'
|
||||||
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
||||||
import { useAppConfig } from '../config'
|
|
||||||
|
|
||||||
import { useRoute } from './router'
|
import { useRoute } from './router'
|
||||||
import { getAppManifest, getRouteRules } from './manifest'
|
import { getAppManifest, getRouteRules } from './manifest'
|
||||||
@ -17,9 +16,9 @@ interface LoadPayloadOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
export function loadPayload (url: string, opts: LoadPayloadOptions = {}): Record<string, any> | Promise<Record<string, any>> | null {
|
export async function loadPayload (url: string, opts: LoadPayloadOptions = {}): Promise<Record<string, any> | null> {
|
||||||
if (import.meta.server || !payloadExtraction) { return null }
|
if (import.meta.server || !payloadExtraction) { return null }
|
||||||
const payloadURL = _getPayloadURL(url, opts)
|
const payloadURL = await _getPayloadURL(url, opts)
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {}
|
const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {}
|
||||||
if (payloadURL in cache) {
|
if (payloadURL in cache) {
|
||||||
@ -40,25 +39,34 @@ export function loadPayload (url: string, opts: LoadPayloadOptions = {}): Record
|
|||||||
return cache[payloadURL]
|
return cache[payloadURL]
|
||||||
}
|
}
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
export function preloadPayload (url: string, opts: LoadPayloadOptions = {}) {
|
export function preloadPayload (url: string, opts: LoadPayloadOptions = {}): Promise<void> {
|
||||||
const payloadURL = _getPayloadURL(url, opts)
|
const nuxtApp = useNuxtApp()
|
||||||
useHead({
|
const promise = _getPayloadURL(url, opts).then((payloadURL) => {
|
||||||
link: [
|
nuxtApp.runWithContext(() => useHead({
|
||||||
{ rel: 'modulepreload', href: payloadURL },
|
link: [
|
||||||
],
|
{ rel: 'modulepreload', href: payloadURL },
|
||||||
|
],
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
if (import.meta.server) {
|
||||||
|
onServerPrefetch(() => promise)
|
||||||
|
}
|
||||||
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Internal ---
|
// --- Internal ---
|
||||||
|
|
||||||
const filename = renderJsonPayloads ? '_payload.json' : '_payload.js'
|
const filename = renderJsonPayloads ? '_payload.json' : '_payload.js'
|
||||||
function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
|
async function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
|
||||||
const u = new URL(url, 'http://localhost')
|
const u = new URL(url, 'http://localhost')
|
||||||
if (u.host !== 'localhost' || hasProtocol(u.pathname, { acceptRelative: true })) {
|
if (u.host !== 'localhost' || hasProtocol(u.pathname, { acceptRelative: true })) {
|
||||||
throw new Error('Payload URL must not include hostname: ' + url)
|
throw new Error('Payload URL must not include hostname: ' + url)
|
||||||
}
|
}
|
||||||
const hash = opts.hash || (opts.fresh ? Date.now() : (useAppConfig().nuxt as any)?.buildId)
|
const config = useRuntimeConfig()
|
||||||
return joinURL(useRuntimeConfig().app.baseURL, u.pathname, filename + (hash ? `?${hash}` : ''))
|
const hash = opts.hash || (opts.fresh ? Date.now() : config.app.buildId)
|
||||||
|
const cdnURL = config.app.cdnURL
|
||||||
|
const baseOrCdnURL = cdnURL && await isPrerendered(url) ? cdnURL : config.app.baseURL
|
||||||
|
return joinURL(baseOrCdnURL, u.pathname, filename + (hash ? `?${hash}` : ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _importPayload (payloadURL: string) {
|
async function _importPayload (payloadURL: string) {
|
||||||
|
@ -2,7 +2,7 @@ import { getCurrentInstance, hasInjectionContext, inject, onScopeDispose } from
|
|||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationPathRaw, RouteLocationRaw, Router, useRoute as _useRoute, useRouter as _useRouter } from '#vue-router'
|
import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationPathRaw, RouteLocationRaw, Router, useRoute as _useRoute, useRouter as _useRouter } from '#vue-router'
|
||||||
import { sanitizeStatusCode } from 'h3'
|
import { sanitizeStatusCode } from 'h3'
|
||||||
import { hasProtocol, isScriptProtocol, joinURL, parseURL, withQuery } from 'ufo'
|
import { hasProtocol, isScriptProtocol, joinURL, withQuery } from 'ufo'
|
||||||
|
|
||||||
import type { PageMeta } from '../../pages/runtime/composables'
|
import type { PageMeta } from '../../pages/runtime/composables'
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
|
|||||||
if (!options?.external) {
|
if (!options?.external) {
|
||||||
throw new Error('Navigating to an external URL is not allowed by default. Use `navigateTo(url, { external: true })`.')
|
throw new Error('Navigating to an external URL is not allowed by default. Use `navigateTo(url, { external: true })`.')
|
||||||
}
|
}
|
||||||
const protocol = parseURL(toPath).protocol
|
const { protocol } = new URL(toPath, import.meta.client ? window.location.href : 'http://localhost')
|
||||||
if (protocol && isScriptProtocol(protocol)) {
|
if (protocol && isScriptProtocol(protocol)) {
|
||||||
throw new Error(`Cannot navigate to a URL with '${protocol}' protocol.`)
|
throw new Error(`Cannot navigate to a URL with '${protocol}' protocol.`)
|
||||||
}
|
}
|
||||||
@ -169,8 +169,7 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
|
|||||||
nuxtApp.ssrContext!._renderResponse = {
|
nuxtApp.ssrContext!._renderResponse = {
|
||||||
statusCode: sanitizeStatusCode(options?.redirectCode || 302, 302),
|
statusCode: sanitizeStatusCode(options?.redirectCode || 302, 302),
|
||||||
body: `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`,
|
body: `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`,
|
||||||
// do not encode as this would break some modules and some environments
|
headers: { location: encodeURI(location) },
|
||||||
headers: { location },
|
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
102
packages/nuxt/src/app/composables/script-stubs.ts
Normal file
102
packages/nuxt/src/app/composables/script-stubs.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import type { UseScriptInput } from '@unhead/vue'
|
||||||
|
import { createError } from './error'
|
||||||
|
|
||||||
|
function renderStubMessage (name: string) {
|
||||||
|
const message = `\`${name}\` is provided by @nuxt/scripts. Check your console to install it or run 'npx nuxi@latest module add @nuxt/scripts' to install it.`
|
||||||
|
if (import.meta.client) {
|
||||||
|
throw createError({
|
||||||
|
fatal: true,
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScript<T extends Record<string | symbol, any>> (input: UseScriptInput, options?: Record<string, unknown>) {
|
||||||
|
renderStubMessage('useScript')
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useElementScriptTrigger (...args: unknown[]) {
|
||||||
|
renderStubMessage('useElementScriptTrigger')
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useConsentScriptTrigger (...args: unknown[]) {
|
||||||
|
renderStubMessage('useConsentScriptTrigger')
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useAnalyticsPageEvent (...args: unknown[]) {
|
||||||
|
renderStubMessage('useAnalyticsPageEvent')
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptGoogleAnalytics (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptGoogleAnalytics')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptPlausibleAnalytics (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptPlausibleAnalytics')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptCloudflareWebAnalytics (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptCloudflareWebAnalytics')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptFathomAnalytics (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptFathomAnalytics')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptMatomoAnalytics (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptMatomoAnalytics')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptGoogleTagManager (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptGoogleTagManager')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptSegment (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptSegment')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptFacebookPixel (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptFacebookPixel')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptXPixel (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptXPixel')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptIntercom (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptIntercom')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptHotjar (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptHotjar')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptStripe (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptStripe')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptLemonSqueezy (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptLemonSqueezy')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptVimeoPlayer (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptVimeoPlayer')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptYouTubeIframe (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptYouTubeIframe')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptGoogleMaps (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptGoogleMaps')
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export function useScriptNpm (...args: unknown[]) {
|
||||||
|
renderStubMessage('useScriptNpm')
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
import type { H3Event } from 'h3'
|
import type { H3Event } from 'h3'
|
||||||
import { setResponseStatus as _setResponseStatus, appendHeader, getRequestHeader, getRequestHeaders } from 'h3'
|
import { setResponseStatus as _setResponseStatus, appendHeader, getRequestHeader, getRequestHeaders } from 'h3'
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
import { useServerHead } from '@unhead/vue'
|
||||||
|
|
||||||
import type { NuxtApp } from '../nuxt'
|
import type { NuxtApp } from '../nuxt'
|
||||||
import { useNuxtApp } from '../nuxt'
|
import { useNuxtApp } from '../nuxt'
|
||||||
import { toArray } from '../utils'
|
import { toArray } from '../utils'
|
||||||
@ -65,3 +68,47 @@ export function prerenderRoutes (path: string | string[]) {
|
|||||||
const paths = toArray(path)
|
const paths = toArray(path)
|
||||||
appendHeader(useRequestEvent()!, 'x-nitro-prerender', paths.map(p => encodeURIComponent(p)).join(', '))
|
appendHeader(useRequestEvent()!, 'x-nitro-prerender', paths.map(p => encodeURIComponent(p)).join(', '))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PREHYDRATE_ATTR_KEY = 'data-prehydrate-id'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `onPrehydrate` is a composable lifecycle hook that allows you to run a callback on the client immediately before
|
||||||
|
* Nuxt hydrates the page. This is an advanced feature.
|
||||||
|
*
|
||||||
|
* The callback will be stringified and inlined in the HTML so it should not have any external
|
||||||
|
* dependencies (such as auto-imports) or refer to variables defined outside the callback.
|
||||||
|
*
|
||||||
|
* The callback will run before Nuxt runtime initializes so it should not rely on the Nuxt or Vue context.
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
export function onPrehydrate (callback: (el: HTMLElement) => void): void
|
||||||
|
export function onPrehydrate (callback: string | ((el: HTMLElement) => void), key?: string): undefined | string {
|
||||||
|
if (import.meta.client) { return }
|
||||||
|
|
||||||
|
if (typeof callback !== 'string') {
|
||||||
|
throw new TypeError('[nuxt] To transform a callback into a string, `onPrehydrate` must be processed by the Nuxt build pipeline. If it is called in a third-party library, make sure to add the library to `build.transpile`.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const vm = getCurrentInstance()
|
||||||
|
if (vm && key) {
|
||||||
|
vm.attrs[PREHYDRATE_ATTR_KEY] ||= ''
|
||||||
|
key = ':' + key + ':'
|
||||||
|
if (!(vm.attrs[PREHYDRATE_ATTR_KEY] as string).includes(key)) {
|
||||||
|
vm.attrs[PREHYDRATE_ATTR_KEY] += key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const code = vm && key
|
||||||
|
? `document.querySelectorAll('[${PREHYDRATE_ATTR_KEY}*=${JSON.stringify(key)}]').forEach` + callback
|
||||||
|
: (callback + '()')
|
||||||
|
|
||||||
|
useServerHead({
|
||||||
|
script: [{
|
||||||
|
key: vm && key ? key : code,
|
||||||
|
tagPosition: 'bodyClose',
|
||||||
|
tagPriority: 'critical',
|
||||||
|
innerHTML: code,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
|
||||||
|
return vm && key ? vm.attrs[PREHYDRATE_ATTR_KEY] as string : undefined
|
||||||
|
}
|
||||||
|
7
packages/nuxt/src/app/defaults.ts
Normal file
7
packages/nuxt/src/app/defaults.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
|
||||||
|
export type DefaultAsyncDataErrorValue = null
|
||||||
|
export type DefaultAsyncDataValue = null
|
||||||
|
export type DefaultErrorValue = null
|
||||||
|
|
||||||
|
export {}
|
@ -1,4 +1,4 @@
|
|||||||
import { effectScope, getCurrentInstance, hasInjectionContext, reactive } from 'vue'
|
import { effectScope, getCurrentInstance, getCurrentScope, hasInjectionContext, reactive, shallowReactive } from 'vue'
|
||||||
import type { App, EffectScope, Ref, VNode, onErrorCaptured } from 'vue'
|
import type { App, EffectScope, Ref, VNode, onErrorCaptured } from 'vue'
|
||||||
import type { RouteLocationNormalizedLoaded } from '#vue-router'
|
import type { RouteLocationNormalizedLoaded } from '#vue-router'
|
||||||
import type { HookCallback, Hookable } from 'hookable'
|
import type { HookCallback, Hookable } from 'hookable'
|
||||||
@ -20,11 +20,18 @@ import type { LoadingIndicator } from '../app/composables/loading-indicator'
|
|||||||
import type { RouteAnnouncer } from '../app/composables/route-announcer'
|
import type { RouteAnnouncer } from '../app/composables/route-announcer'
|
||||||
import type { ViewTransition } from './plugins/view-transitions.client'
|
import type { ViewTransition } from './plugins/view-transitions.client'
|
||||||
|
|
||||||
|
// @ts-expect-error virtual file
|
||||||
|
import { appId } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultErrorValue } from '#app/defaults'
|
||||||
import type { NuxtAppLiterals } from '#app'
|
import type { NuxtAppLiterals } from '#app'
|
||||||
|
|
||||||
const nuxtAppCtx = /* @__PURE__ */ getContext<NuxtApp>('nuxt-app', {
|
function getNuxtAppCtx (appName = appId || 'nuxt-app') {
|
||||||
asyncContext: !!__NUXT_ASYNC_CONTEXT__ && import.meta.server,
|
return getContext<NuxtApp>(appName, {
|
||||||
})
|
asyncContext: !!__NUXT_ASYNC_CONTEXT__ && import.meta.server,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type HookResult = Promise<void> | void
|
type HookResult = Promise<void> | void
|
||||||
|
|
||||||
@ -87,12 +94,14 @@ export interface NuxtPayload {
|
|||||||
state: Record<string, any>
|
state: Record<string, any>
|
||||||
once: Set<string>
|
once: Set<string>
|
||||||
config?: Pick<RuntimeConfig, 'public' | 'app'>
|
config?: Pick<RuntimeConfig, 'public' | 'app'>
|
||||||
error?: NuxtError | null
|
error?: NuxtError | DefaultErrorValue
|
||||||
_errors: Record<string, NuxtError | null>
|
_errors: Record<string, NuxtError | DefaultAsyncDataErrorValue>
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
interface _NuxtApp {
|
interface _NuxtApp {
|
||||||
|
/** @internal */
|
||||||
|
_name: string
|
||||||
vueApp: App<Element>
|
vueApp: App<Element>
|
||||||
globalName: string
|
globalName: string
|
||||||
versions: Record<string, string>
|
versions: Record<string, string>
|
||||||
@ -113,10 +122,12 @@ interface _NuxtApp {
|
|||||||
_asyncDataPromises: Record<string, Promise<any> | undefined>
|
_asyncDataPromises: Record<string, Promise<any> | undefined>
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_asyncData: Record<string, {
|
_asyncData: Record<string, {
|
||||||
data: Ref<any>
|
data: Ref<unknown>
|
||||||
pending: Ref<boolean>
|
pending: Ref<boolean>
|
||||||
error: Ref<Error | null>
|
error: Ref<Error | DefaultAsyncDataErrorValue>
|
||||||
status: Ref<AsyncDataRequestStatus>
|
status: Ref<AsyncDataRequestStatus>
|
||||||
|
/** @internal */
|
||||||
|
_default: () => unknown
|
||||||
} | undefined>
|
} | undefined>
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@ -237,6 +248,7 @@ export interface CreateOptions {
|
|||||||
export function createNuxtApp (options: CreateOptions) {
|
export function createNuxtApp (options: CreateOptions) {
|
||||||
let hydratingCount = 0
|
let hydratingCount = 0
|
||||||
const nuxtApp: NuxtApp = {
|
const nuxtApp: NuxtApp = {
|
||||||
|
_name: appId || 'nuxt-app',
|
||||||
_scope: effectScope(),
|
_scope: effectScope(),
|
||||||
provide: undefined,
|
provide: undefined,
|
||||||
globalName: 'nuxt',
|
globalName: 'nuxt',
|
||||||
@ -244,18 +256,17 @@ export function createNuxtApp (options: CreateOptions) {
|
|||||||
get nuxt () { return __NUXT_VERSION__ },
|
get nuxt () { return __NUXT_VERSION__ },
|
||||||
get vue () { return nuxtApp.vueApp.version },
|
get vue () { return nuxtApp.vueApp.version },
|
||||||
},
|
},
|
||||||
payload: reactive({
|
payload: shallowReactive({
|
||||||
data: {},
|
data: shallowReactive({}),
|
||||||
state: {},
|
state: reactive({}),
|
||||||
once: new Set<string>(),
|
once: new Set<string>(),
|
||||||
_errors: {},
|
_errors: shallowReactive({}),
|
||||||
...(import.meta.client ? window.__NUXT__ ?? {} : { serverRendered: true }),
|
|
||||||
}),
|
}),
|
||||||
static: {
|
static: {
|
||||||
data: {},
|
data: {},
|
||||||
},
|
},
|
||||||
runWithContext (fn: any) {
|
runWithContext (fn: any) {
|
||||||
if (nuxtApp._scope.active) {
|
if (nuxtApp._scope.active && !getCurrentScope()) {
|
||||||
return nuxtApp._scope.run(() => callWithNuxt(nuxtApp, fn))
|
return nuxtApp._scope.run(() => callWithNuxt(nuxtApp, fn))
|
||||||
}
|
}
|
||||||
return callWithNuxt(nuxtApp, fn)
|
return callWithNuxt(nuxtApp, fn)
|
||||||
@ -280,11 +291,32 @@ export function createNuxtApp (options: CreateOptions) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
_asyncDataPromises: {},
|
_asyncDataPromises: {},
|
||||||
_asyncData: {},
|
_asyncData: shallowReactive({}),
|
||||||
_payloadRevivers: {},
|
_payloadRevivers: {},
|
||||||
...options,
|
...options,
|
||||||
} as any as NuxtApp
|
} as any as NuxtApp
|
||||||
|
|
||||||
|
if (import.meta.server) {
|
||||||
|
nuxtApp.payload.serverRendered = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove/refactor in https://github.com/nuxt/nuxt/issues/25336
|
||||||
|
if (import.meta.client && window.__NUXT__) {
|
||||||
|
for (const key in window.__NUXT__) {
|
||||||
|
switch (key) {
|
||||||
|
case 'data':
|
||||||
|
case 'state':
|
||||||
|
case '_errors':
|
||||||
|
// Preserve reactivity for non-rich payload support
|
||||||
|
Object.assign(nuxtApp.payload[key], window.__NUXT__[key])
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
nuxtApp.payload[key] = window.__NUXT__[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nuxtApp.hooks = createHooks<RuntimeNuxtHooks>()
|
nuxtApp.hooks = createHooks<RuntimeNuxtHooks>()
|
||||||
nuxtApp.hook = nuxtApp.hooks.hook
|
nuxtApp.hook = nuxtApp.hooks.hook
|
||||||
|
|
||||||
@ -447,6 +479,7 @@ export function isNuxtPlugin (plugin: unknown) {
|
|||||||
*/
|
*/
|
||||||
export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp | _NuxtApp, setup: T, args?: Parameters<T>) {
|
export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp | _NuxtApp, setup: T, args?: Parameters<T>) {
|
||||||
const fn: () => ReturnType<T> = () => args ? setup(...args as Parameters<T>) : setup()
|
const fn: () => ReturnType<T> = () => args ? setup(...args as Parameters<T>) : setup()
|
||||||
|
const nuxtAppCtx = getNuxtAppCtx(nuxt._name)
|
||||||
if (import.meta.server) {
|
if (import.meta.server) {
|
||||||
return nuxt.vueApp.runWithContext(() => nuxtAppCtx.callAsync(nuxt as NuxtApp, fn))
|
return nuxt.vueApp.runWithContext(() => nuxtAppCtx.callAsync(nuxt as NuxtApp, fn))
|
||||||
} else {
|
} else {
|
||||||
@ -463,13 +496,14 @@ export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp |
|
|||||||
* Returns `null` if Nuxt instance is unavailable.
|
* Returns `null` if Nuxt instance is unavailable.
|
||||||
* @since 3.10.0
|
* @since 3.10.0
|
||||||
*/
|
*/
|
||||||
export function tryUseNuxtApp (): NuxtApp | null {
|
export function tryUseNuxtApp (): NuxtApp | null
|
||||||
|
export function tryUseNuxtApp (appName?: string): NuxtApp | null {
|
||||||
let nuxtAppInstance
|
let nuxtAppInstance
|
||||||
if (hasInjectionContext()) {
|
if (hasInjectionContext()) {
|
||||||
nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt
|
nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt
|
||||||
}
|
}
|
||||||
|
|
||||||
nuxtAppInstance = nuxtAppInstance || nuxtAppCtx.tryUse()
|
nuxtAppInstance = nuxtAppInstance || getNuxtAppCtx(appName).tryUse()
|
||||||
|
|
||||||
return nuxtAppInstance || null
|
return nuxtAppInstance || null
|
||||||
}
|
}
|
||||||
@ -481,8 +515,10 @@ export function tryUseNuxtApp (): NuxtApp | null {
|
|||||||
* Throws an error if Nuxt instance is unavailable.
|
* Throws an error if Nuxt instance is unavailable.
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
export function useNuxtApp (): NuxtApp {
|
export function useNuxtApp (): NuxtApp
|
||||||
const nuxtAppInstance = tryUseNuxtApp()
|
export function useNuxtApp (appName?: string): NuxtApp {
|
||||||
|
// @ts-expect-error internal usage of appName
|
||||||
|
const nuxtAppInstance = tryUseNuxtApp(appName)
|
||||||
|
|
||||||
if (!nuxtAppInstance) {
|
if (!nuxtAppInstance) {
|
||||||
if (import.meta.dev) {
|
if (import.meta.dev) {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { parseURL } from 'ufo'
|
|
||||||
import { useHead } from '@unhead/vue'
|
import { useHead } from '@unhead/vue'
|
||||||
import { defineNuxtPlugin } from '../nuxt'
|
import { defineNuxtPlugin } from '../nuxt'
|
||||||
|
|
||||||
|
const SUPPORTED_PROTOCOLS = ['http:', 'https:']
|
||||||
|
|
||||||
export default defineNuxtPlugin({
|
export default defineNuxtPlugin({
|
||||||
name: 'nuxt:cross-origin-prefetch',
|
name: 'nuxt:cross-origin-prefetch',
|
||||||
setup (nuxtApp) {
|
setup (nuxtApp) {
|
||||||
@ -26,8 +27,7 @@ export default defineNuxtPlugin({
|
|||||||
script: [generateRules()],
|
script: [generateRules()],
|
||||||
})
|
})
|
||||||
nuxtApp.hook('link:prefetch', (url) => {
|
nuxtApp.hook('link:prefetch', (url) => {
|
||||||
const { protocol } = parseURL(url)
|
if (SUPPORTED_PROTOCOLS.some(p => url.startsWith(p)) && SUPPORTED_PROTOCOLS.includes(new URL(url).protocol)) {
|
||||||
if (protocol && ['http:', 'https:'].includes(protocol)) {
|
|
||||||
externalURLs.value.add(url)
|
externalURLs.value.add(url)
|
||||||
head?.patch({
|
head?.patch({
|
||||||
script: [generateRules()],
|
script: [generateRules()],
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
import { consola, createConsola } from 'consola'
|
import { createConsola } from 'consola'
|
||||||
import type { LogObject } from 'consola'
|
import type { LogObject } from 'consola'
|
||||||
import { parse } from 'devalue'
|
import { parse } from 'devalue'
|
||||||
|
|
||||||
|
import { h } from 'vue'
|
||||||
import { defineNuxtPlugin } from '../nuxt'
|
import { defineNuxtPlugin } from '../nuxt'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { devLogs, devRootDir } from '#build/nuxt.config.mjs'
|
import { devLogs, devRootDir } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
const devRevivers: Record<string, (data: any) => any> = import.meta.server
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
VNode: data => h(data.type, data.props),
|
||||||
|
URL: data => new URL(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(async (nuxtApp) => {
|
||||||
if (import.meta.test) { return }
|
if (import.meta.test) { return }
|
||||||
|
|
||||||
if (import.meta.server) {
|
if (import.meta.server) {
|
||||||
@ -23,42 +31,18 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
date: true,
|
date: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const hydrationLogs = new Set<string>()
|
|
||||||
consola.wrapConsole()
|
|
||||||
consola.addReporter({
|
|
||||||
log (logObj) {
|
|
||||||
try {
|
|
||||||
hydrationLogs.add(JSON.stringify(logObj.args))
|
|
||||||
} catch {
|
|
||||||
// silently ignore - the worst case is a user gets log twice
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
nuxtApp.hook('dev:ssr-logs', (logs) => {
|
nuxtApp.hook('dev:ssr-logs', (logs) => {
|
||||||
for (const log of logs) {
|
for (const log of logs) {
|
||||||
// deduplicate so we don't print out things that are logged on client
|
logger.log(normalizeServerLog({ ...log }))
|
||||||
try {
|
|
||||||
if (!hydrationLogs.size || !hydrationLogs.has(JSON.stringify(log.args))) {
|
|
||||||
logger.log(normalizeServerLog({ ...log }))
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
logger.log(normalizeServerLog({ ...log }))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
nuxtApp.hooks.hook('app:suspense:resolve', () => consola.restoreAll())
|
|
||||||
nuxtApp.hooks.hookOnce('dev:ssr-logs', () => hydrationLogs.clear())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass SSR logs after hydration
|
if (typeof window !== 'undefined') {
|
||||||
nuxtApp.hooks.hook('app:suspense:resolve', async () => {
|
const content = document.getElementById('__NUXT_LOGS__')?.textContent
|
||||||
if (typeof window !== 'undefined') {
|
const logs = content ? parse(content, { ...devRevivers, ...nuxtApp._payloadRevivers }) as LogObject[] : []
|
||||||
const content = document.getElementById('__NUXT_LOGS__')?.textContent
|
await nuxtApp.hooks.callHook('dev:ssr-logs', logs)
|
||||||
const logs = content ? parse(content, nuxtApp._payloadRevivers) as LogObject[] : []
|
}
|
||||||
await nuxtApp.hooks.callHook('dev:ssr-logs', logs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function normalizeFilenames (stack?: string) {
|
function normalizeFilenames (stack?: string) {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { parseURL } from 'ufo'
|
|
||||||
import { defineNuxtPlugin } from '../nuxt'
|
import { defineNuxtPlugin } from '../nuxt'
|
||||||
import { loadPayload } from '../composables/payload'
|
import { loadPayload } from '../composables/payload'
|
||||||
import { onNuxtReady } from '../composables/ready'
|
import { onNuxtReady } from '../composables/ready'
|
||||||
@ -24,7 +23,8 @@ export default defineNuxtPlugin({
|
|||||||
onNuxtReady(() => {
|
onNuxtReady(() => {
|
||||||
// Load payload into cache
|
// Load payload into cache
|
||||||
nuxtApp.hooks.hook('link:prefetch', async (url) => {
|
nuxtApp.hooks.hook('link:prefetch', async (url) => {
|
||||||
if (!parseURL(url).protocol) {
|
const { hostname } = new URL(url, window.location.href)
|
||||||
|
if (hostname === window.location.hostname) {
|
||||||
await loadPayload(url)
|
await loadPayload(url)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import { computed, defineComponent, h, isReadonly, reactive } from 'vue'
|
import { computed, defineComponent, h, isReadonly, reactive } from 'vue'
|
||||||
import { isEqual, joinURL, parseQuery, parseURL, stringifyParsedURL, stringifyQuery, withoutBase } from 'ufo'
|
import { isEqual, joinURL, parseQuery, stringifyParsedURL, stringifyQuery, withoutBase } from 'ufo'
|
||||||
import { createError } from 'h3'
|
import { createError } from 'h3'
|
||||||
import { defineNuxtPlugin, useRuntimeConfig } from '../nuxt'
|
import { defineNuxtPlugin, useRuntimeConfig } from '../nuxt'
|
||||||
import { getRouteRules } from '../composables/manifest'
|
import { getRouteRules } from '../composables/manifest'
|
||||||
@ -45,7 +45,7 @@ function getRouteFromPath (fullPath: string | Partial<Route>) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = parseURL(fullPath.toString())
|
const url = new URL(fullPath.toString(), import.meta.client ? window.location.href : 'http://localhost')
|
||||||
return {
|
return {
|
||||||
path: url.pathname,
|
path: url.pathname,
|
||||||
fullPath,
|
fullPath,
|
||||||
|
29
packages/nuxt/src/app/types/augments.d.ts
vendored
29
packages/nuxt/src/app/types/augments.d.ts
vendored
@ -1,20 +1,29 @@
|
|||||||
import type { UseHeadInput } from '@unhead/vue'
|
import type { UseHeadInput } from '@unhead/vue'
|
||||||
import type { NuxtApp, useNuxtApp } from '../nuxt'
|
import type { NuxtApp, useNuxtApp } from '../nuxt'
|
||||||
|
|
||||||
interface NuxtStaticBuildFlags {
|
|
||||||
browser: boolean
|
|
||||||
client: boolean
|
|
||||||
dev: boolean
|
|
||||||
server: boolean
|
|
||||||
test: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface Process extends NuxtStaticBuildFlags {}
|
interface Process {
|
||||||
|
/** @deprecated Use `import.meta.browser` instead. This may be removed in Nuxt v5 or a future major version. */
|
||||||
|
browser: boolean
|
||||||
|
/** @deprecated Use `import.meta.client` instead. This may be removed in Nuxt v5 or a future major version. */
|
||||||
|
client: boolean
|
||||||
|
/** @deprecated Use `import.meta.dev` instead. This may be removed in Nuxt v5 or a future major version. */
|
||||||
|
dev: boolean
|
||||||
|
/** @deprecated Use `import.meta.server` instead. This may be removed in Nuxt v5 or a future major version. */
|
||||||
|
server: boolean
|
||||||
|
/** @deprecated Use `import.meta.test` instead. This may be removed in Nuxt v5 or a future major version. */
|
||||||
|
test: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta extends NuxtStaticBuildFlags {}
|
interface ImportMeta extends NuxtStaticBuildFlags {
|
||||||
|
browser: boolean
|
||||||
|
client: boolean
|
||||||
|
dev: boolean
|
||||||
|
server: boolean
|
||||||
|
test: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
__NUXT__?: Record<string, any>
|
__NUXT__?: Record<string, any>
|
||||||
|
@ -10,7 +10,7 @@ interface LoaderOptions {
|
|||||||
transform?: ComponentsOptions['transform']
|
transform?: ComponentsOptions['transform']
|
||||||
rootDir: string
|
rootDir: string
|
||||||
}
|
}
|
||||||
const CLIENT_FALLBACK_RE = /<(NuxtClientFallback|nuxt-client-fallback)( [^>]*)?>/
|
const CLIENT_FALLBACK_RE = /<(?:NuxtClientFallback|nuxt-client-fallback)(?: [^>]*)?>/
|
||||||
const CLIENT_FALLBACK_GLOBAL_RE = /<(NuxtClientFallback|nuxt-client-fallback)( [^>]*)?>/g
|
const CLIENT_FALLBACK_GLOBAL_RE = /<(NuxtClientFallback|nuxt-client-fallback)( [^>]*)?>/g
|
||||||
export const clientFallbackAutoIdPlugin = createUnplugin((options: LoaderOptions) => {
|
export const clientFallbackAutoIdPlugin = createUnplugin((options: LoaderOptions) => {
|
||||||
const exclude = options.transform?.exclude || []
|
const exclude = options.transform?.exclude || []
|
||||||
@ -37,7 +37,7 @@ export const clientFallbackAutoIdPlugin = createUnplugin((options: LoaderOptions
|
|||||||
|
|
||||||
s.replace(CLIENT_FALLBACK_GLOBAL_RE, (full, name, attrs) => {
|
s.replace(CLIENT_FALLBACK_GLOBAL_RE, (full, name, attrs) => {
|
||||||
count++
|
count++
|
||||||
if (/ :?uid=/g.test(attrs)) { return full }
|
if (/ :?uid=/.test(attrs)) { return full }
|
||||||
return `<${name} :uid="'${hash(relativeID)}' + JSON.stringify($props) + '${count}'" ${attrs ?? ''}>`
|
return `<${name} :uid="'${hash(relativeID)}' + JSON.stringify($props) + '${count}'" ${attrs ?? ''}>`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -13,12 +13,6 @@ import { isVue } from '../core/utils'
|
|||||||
|
|
||||||
interface ServerOnlyComponentTransformPluginOptions {
|
interface ServerOnlyComponentTransformPluginOptions {
|
||||||
getComponents: () => Component[]
|
getComponents: () => Component[]
|
||||||
/**
|
|
||||||
* passed down to `NuxtTeleportIslandComponent`
|
|
||||||
* should be done only in dev mode as we use build:manifest result in production
|
|
||||||
*/
|
|
||||||
rootDir?: string
|
|
||||||
isDev?: boolean
|
|
||||||
/**
|
/**
|
||||||
* allow using `nuxt-client` attribute on components
|
* allow using `nuxt-client` attribute on components
|
||||||
*/
|
*/
|
||||||
@ -31,7 +25,7 @@ interface ComponentChunkOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SCRIPT_RE = /<script[^>]*>/g
|
const SCRIPT_RE = /<script[^>]*>/g
|
||||||
const HAS_SLOT_OR_CLIENT_RE = /(<slot[^>]*>)|(nuxt-client)/
|
const HAS_SLOT_OR_CLIENT_RE = /<slot[^>]*>|nuxt-client/
|
||||||
const TEMPLATE_RE = /<template>([\s\S]*)<\/template>/
|
const TEMPLATE_RE = /<template>([\s\S]*)<\/template>/
|
||||||
const NUXTCLIENT_ATTR_RE = /\s:?nuxt-client(="[^"]*")?/g
|
const NUXTCLIENT_ATTR_RE = /\s:?nuxt-client(="[^"]*")?/g
|
||||||
const IMPORT_CODE = '\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 IMPORT_CODE = '\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\''
|
||||||
@ -43,7 +37,6 @@ function wrapWithVForDiv (code: string, vfor: string): string {
|
|||||||
|
|
||||||
export const islandsTransform = createUnplugin((options: ServerOnlyComponentTransformPluginOptions, meta) => {
|
export const islandsTransform = createUnplugin((options: ServerOnlyComponentTransformPluginOptions, meta) => {
|
||||||
const isVite = meta.framework === 'vite'
|
const isVite = meta.framework === 'vite'
|
||||||
const { isDev, rootDir } = options
|
|
||||||
return {
|
return {
|
||||||
name: 'server-only-component-transform',
|
name: 'server-only-component-transform',
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
@ -115,7 +108,7 @@ export const islandsTransform = createUnplugin((options: ServerOnlyComponentTran
|
|||||||
startTag = startTag.replaceAll(EXTRACTED_ATTRS_RE, '')
|
startTag = startTag.replaceAll(EXTRACTED_ATTRS_RE, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
s.appendLeft(startingIndex + loc[0].start, `<NuxtTeleportIslandComponent${attributeToString(wrapperAttributes)} to="${node.name}-${uid}" ${rootDir && isDev ? `root-dir="${rootDir}"` : ''} :nuxt-client="${attributeValue}">`)
|
s.appendLeft(startingIndex + loc[0].start, `<NuxtTeleportIslandComponent${attributeToString(wrapperAttributes)} to="${node.name}-${uid}" :nuxt-client="${attributeValue}">`)
|
||||||
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[0].end, startTag)
|
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[0].end, startTag)
|
||||||
s.appendRight(startingIndex + loc[1].end, '</NuxtTeleportIslandComponent>')
|
s.appendRight(startingIndex + loc[1].end, '</NuxtTeleportIslandComponent>')
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ export const loaderPlugin = createUnplugin((options: LoaderOptions) => {
|
|||||||
const s = new MagicString(code)
|
const s = new MagicString(code)
|
||||||
|
|
||||||
// replace `_resolveComponent("...")` to direct import
|
// replace `_resolveComponent("...")` to direct import
|
||||||
s.replace(/(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy)?([^'"]*?)["'][\s,]*[^)]*\)/g, (full: string, lazy: string, name: string) => {
|
s.replace(/(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy)?([^'"]*)["'][^)]*\)/g, (full: string, lazy: string, name: string) => {
|
||||||
const component = findComponent(components, name, options.mode)
|
const component = findComponent(components, name, options.mode)
|
||||||
if (component) {
|
if (component) {
|
||||||
// @ts-expect-error TODO: refactor to nuxi
|
// @ts-expect-error TODO: refactor to nuxi
|
||||||
|
@ -18,7 +18,7 @@ function compareDirByPathLength ({ path: pathA }: { path: string }, { path: path
|
|||||||
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
|
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_COMPONENTS_DIRS_RE = /\/components(\/global|\/islands)?$/
|
const DEFAULT_COMPONENTS_DIRS_RE = /\/components(?:\/(?:global|islands))?$/
|
||||||
|
|
||||||
export type getComponentsT = (mode?: 'client' | 'server' | 'all') => Component[]
|
export type getComponentsT = (mode?: 'client' | 'server' | 'all') => Component[]
|
||||||
|
|
||||||
@ -260,8 +260,6 @@ export default defineNuxtModule<ComponentsOptions>({
|
|||||||
if (isServer) {
|
if (isServer) {
|
||||||
config.plugins.push(islandsTransform.vite({
|
config.plugins.push(islandsTransform.vite({
|
||||||
getComponents,
|
getComponents,
|
||||||
rootDir: nuxt.options.rootDir,
|
|
||||||
isDev: nuxt.options.dev,
|
|
||||||
selectiveClient,
|
selectiveClient,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -83,8 +83,8 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
|||||||
*/
|
*/
|
||||||
let fileName = basename(filePath, extname(filePath))
|
let fileName = basename(filePath, extname(filePath))
|
||||||
|
|
||||||
const island = /\.(island)(\.global)?$/.test(fileName) || dir.island
|
const island = /\.island(?:\.global)?$/.test(fileName) || dir.island
|
||||||
const global = /\.(global)(\.island)?$/.test(fileName) || dir.global
|
const global = /\.global(?:\.island)?$/.test(fileName) || dir.global
|
||||||
const mode = island ? 'server' : (fileName.match(/(?<=\.)(client|server)(\.global|\.island)*$/)?.[1] || 'all') as 'client' | 'server' | 'all'
|
const mode = island ? 'server' : (fileName.match(/(?<=\.)(client|server)(\.global|\.island)*$/)?.[1] || 'all') as 'client' | 'server' | 'all'
|
||||||
fileName = fileName.replace(/(\.(client|server))?(\.global|\.island)*$/, '')
|
fileName = fileName.replace(/(\.(client|server))?(\.global|\.island)*$/, '')
|
||||||
|
|
||||||
|
@ -102,14 +102,20 @@ export const componentsTypeTemplate = {
|
|||||||
filename: 'components.d.ts' as const,
|
filename: 'components.d.ts' as const,
|
||||||
getContents: ({ app, nuxt }) => {
|
getContents: ({ app, nuxt }) => {
|
||||||
const buildDir = nuxt.options.buildDir
|
const buildDir = nuxt.options.buildDir
|
||||||
const componentTypes = app.components.filter(c => !c.island).map(c => [
|
const componentTypes = app.components.filter(c => !c.island).map((c) => {
|
||||||
c.pascalName,
|
const type = `typeof ${genDynamicImport(isAbsolute(c.filePath)
|
||||||
`typeof ${genDynamicImport(isAbsolute(c.filePath)
|
? relative(buildDir, c.filePath).replace(/\b\.(?!vue)\w+$/g, '')
|
||||||
? relative(buildDir, c.filePath).replace(/(?<=\w)\.(?!vue)\w+$/g, '')
|
: c.filePath.replace(/\b\.(?!vue)\w+$/g, ''), { wrapper: false })}['${c.export}']`
|
||||||
: c.filePath.replace(/(?<=\w)\.(?!vue)\w+$/g, ''), { wrapper: false })}['${c.export}']`,
|
return [
|
||||||
])
|
c.pascalName,
|
||||||
|
c.island || c.mode === 'server' ? `IslandComponent<${type}>` : type,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const islandType = 'type IslandComponent<T extends DefineComponent> = T & DefineComponent<{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, SlotsType<{ fallback: { error: unknown } }>>'
|
||||||
return `
|
return `
|
||||||
|
import type { DefineComponent, SlotsType } from 'vue'
|
||||||
|
${nuxt.options.experimental.componentIslands ? islandType : ''}
|
||||||
interface _GlobalComponents {
|
interface _GlobalComponents {
|
||||||
${componentTypes.map(([pascalName, type]) => ` '${pascalName}': ${type}`).join('\n')}
|
${componentTypes.map(([pascalName, type]) => ` '${pascalName}': ${type}`).join('\n')}
|
||||||
${componentTypes.map(([pascalName, type]) => ` 'Lazy${pascalName}': ${type}`).join('\n')}
|
${componentTypes.map(([pascalName, type]) => ` 'Lazy${pascalName}': ${type}`).join('\n')}
|
||||||
|
@ -16,7 +16,7 @@ interface TreeShakeTemplatePluginOptions {
|
|||||||
type AcornNode<N extends Node> = N & { start: number, end: number }
|
type AcornNode<N extends Node> = N & { start: number, end: number }
|
||||||
|
|
||||||
const SSR_RENDER_RE = /ssrRenderComponent/
|
const SSR_RENDER_RE = /ssrRenderComponent/
|
||||||
const PLACEHOLDER_EXACT_RE = /^(fallback|placeholder)$/
|
const PLACEHOLDER_EXACT_RE = /^(?:fallback|placeholder)$/
|
||||||
const CLIENT_ONLY_NAME_RE = /^(?:_unref\()?(?:_component_)?(?:Lazy|lazy_)?(?:client_only|ClientOnly\)?)$/
|
const CLIENT_ONLY_NAME_RE = /^(?:_unref\()?(?:_component_)?(?:Lazy|lazy_)?(?:client_only|ClientOnly\)?)$/
|
||||||
const PARSER_OPTIONS = { sourceType: 'module', ecmaVersion: 'latest' }
|
const PARSER_OPTIONS = { sourceType: 'module', ecmaVersion: 'latest' }
|
||||||
|
|
||||||
|
@ -85,8 +85,8 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
|||||||
changedTemplates.push(template)
|
changedTemplates.push(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
const perf = performance.measure(fullPath, mark?.name) // TODO: remove when Node 14 reaches EOL
|
const perf = performance.measure(fullPath, mark.name)
|
||||||
const setupTime = perf ? Math.round((perf.duration * 100)) / 100 : 0 // TODO: remove when Node 14 reaches EOL
|
const setupTime = Math.round((perf.duration * 100)) / 100
|
||||||
|
|
||||||
if (nuxt.options.debug || setupTime > 500) {
|
if (nuxt.options.debug || setupTime > 500) {
|
||||||
logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`)
|
logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`)
|
||||||
@ -179,7 +179,12 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
|||||||
app.middleware = []
|
app.middleware = []
|
||||||
for (const config of reversedConfigs) {
|
for (const config of reversedConfigs) {
|
||||||
const middlewareDir = (config.rootDir === nuxt.options.rootDir ? nuxt.options : config).dir?.middleware || 'middleware'
|
const middlewareDir = (config.rootDir === nuxt.options.rootDir ? nuxt.options : config).dir?.middleware || 'middleware'
|
||||||
const middlewareFiles = await resolveFiles(config.srcDir, `${middlewareDir}/*{${nuxt.options.extensions.join(',')}}`)
|
const middlewareFiles = await resolveFiles(config.srcDir, [
|
||||||
|
`${middlewareDir}/*{${nuxt.options.extensions.join(',')}}`,
|
||||||
|
...nuxt.options.future.compatibilityVersion === 4
|
||||||
|
? [`${middlewareDir}/*/index{${nuxt.options.extensions.join(',')}}`]
|
||||||
|
: [],
|
||||||
|
])
|
||||||
for (const file of middlewareFiles) {
|
for (const file of middlewareFiles) {
|
||||||
const name = getNameFromPath(file)
|
const name = getNameFromPath(file)
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@ -200,7 +205,7 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
|||||||
...config.srcDir
|
...config.srcDir
|
||||||
? await resolveFiles(config.srcDir, [
|
? await resolveFiles(config.srcDir, [
|
||||||
`${pluginDir}/*{${nuxt.options.extensions.join(',')}}`,
|
`${pluginDir}/*{${nuxt.options.extensions.join(',')}}`,
|
||||||
`${pluginDir}/*/index{${nuxt.options.extensions.join(',')}}`, // TODO: remove, only scan top-level plugins #18418
|
`${pluginDir}/*/index{${nuxt.options.extensions.join(',')}}`,
|
||||||
])
|
])
|
||||||
: [],
|
: [],
|
||||||
].map(plugin => normalizePlugin(plugin as NuxtPlugin)))
|
].map(plugin => normalizePlugin(plugin as NuxtPlugin)))
|
||||||
|
@ -24,7 +24,7 @@ export async function build (nuxt: Nuxt) {
|
|||||||
if (event === 'change') { return }
|
if (event === 'change') { return }
|
||||||
const path = resolve(nuxt.options.srcDir, relativePath)
|
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||||
const relativePaths = nuxt.options._layers.map(l => relative(l.config.srcDir || l.cwd, path))
|
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 => /^(?:app\.|error\.|plugins\/|middleware\/|layouts\/)/i.test(relativePath))
|
||||||
if (restartPath) {
|
if (restartPath) {
|
||||||
if (restartPath.startsWith('app')) {
|
if (restartPath.startsWith('app')) {
|
||||||
app.mainComponent = undefined
|
app.mainComponent = undefined
|
||||||
|
@ -3,7 +3,6 @@ import { existsSync, promises as fsp, readFileSync } from 'node:fs'
|
|||||||
import { cpus } from 'node:os'
|
import { cpus } from 'node:os'
|
||||||
import { join, relative, resolve } from 'pathe'
|
import { join, relative, resolve } from 'pathe'
|
||||||
import { createRouter as createRadixRouter, exportMatcher, toRouteMatcher } from 'radix3'
|
import { createRouter as createRadixRouter, exportMatcher, toRouteMatcher } from 'radix3'
|
||||||
import { randomUUID } from 'uncrypto'
|
|
||||||
import { joinURL, withTrailingSlash } from 'ufo'
|
import { joinURL, withTrailingSlash } from 'ufo'
|
||||||
import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack'
|
import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack'
|
||||||
import type { Nitro, NitroConfig, NitroOptions } from 'nitropack'
|
import type { Nitro, NitroConfig, NitroOptions } from 'nitropack'
|
||||||
@ -13,7 +12,7 @@ import { defu } from 'defu'
|
|||||||
import fsExtra from 'fs-extra'
|
import fsExtra from 'fs-extra'
|
||||||
import { dynamicEventHandler } from 'h3'
|
import { dynamicEventHandler } from 'h3'
|
||||||
import { isWindows } from 'std-env'
|
import { isWindows } from 'std-env'
|
||||||
import type { Nuxt, NuxtOptions, RuntimeConfig } from 'nuxt/schema'
|
import type { Nuxt, NuxtOptions } from 'nuxt/schema'
|
||||||
import { version as nuxtVersion } from '../../package.json'
|
import { version as nuxtVersion } from '../../package.json'
|
||||||
import { distDir } from '../dirs'
|
import { distDir } from '../dirs'
|
||||||
import { toArray } from '../utils'
|
import { toArray } from '../utils'
|
||||||
@ -28,8 +27,6 @@ const logLevelMapReverse = {
|
|||||||
|
|
||||||
export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||||
// Resolve config
|
// Resolve config
|
||||||
const _nitroConfig = ((nuxt.options as any).nitro || {}) as NitroConfig
|
|
||||||
|
|
||||||
const excludePaths = nuxt.options._layers
|
const excludePaths = nuxt.options._layers
|
||||||
.flatMap(l => [
|
.flatMap(l => [
|
||||||
l.cwd.match(/(?<=\/)node_modules\/(.+)$/)?.[1],
|
l.cwd.match(/(?<=\/)node_modules\/(.+)$/)?.[1],
|
||||||
@ -49,7 +46,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
.map(m => m.entryPath),
|
.map(m => m.entryPath),
|
||||||
)
|
)
|
||||||
|
|
||||||
const nitroConfig: NitroConfig = defu(_nitroConfig, {
|
const nitroConfig: NitroConfig = defu(nuxt.options.nitro, {
|
||||||
debug: nuxt.options.debug,
|
debug: nuxt.options.debug,
|
||||||
rootDir: nuxt.options.rootDir,
|
rootDir: nuxt.options.rootDir,
|
||||||
workspaceDir: nuxt.options.workspaceDir,
|
workspaceDir: nuxt.options.workspaceDir,
|
||||||
@ -58,7 +55,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
buildDir: nuxt.options.buildDir,
|
buildDir: nuxt.options.buildDir,
|
||||||
experimental: {
|
experimental: {
|
||||||
asyncContext: nuxt.options.experimental.asyncContext,
|
asyncContext: nuxt.options.experimental.asyncContext,
|
||||||
typescriptBundlerResolution: nuxt.options.future.typescriptBundlerResolution || nuxt.options.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler' || _nitroConfig.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler',
|
typescriptBundlerResolution: nuxt.options.future.typescriptBundlerResolution || nuxt.options.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler' || nuxt.options.nitro.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler',
|
||||||
},
|
},
|
||||||
framework: {
|
framework: {
|
||||||
name: 'nuxt',
|
name: 'nuxt',
|
||||||
@ -111,20 +108,6 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
routeRules: {
|
routeRules: {
|
||||||
'/__nuxt_error': { cache: false },
|
'/__nuxt_error': { cache: false },
|
||||||
},
|
},
|
||||||
runtimeConfig: {
|
|
||||||
...nuxt.options.runtimeConfig,
|
|
||||||
app: {
|
|
||||||
...nuxt.options.runtimeConfig.app,
|
|
||||||
baseURL: nuxt.options.runtimeConfig.app.baseURL.startsWith('./')
|
|
||||||
? nuxt.options.runtimeConfig.app.baseURL.slice(1)
|
|
||||||
: nuxt.options.runtimeConfig.app.baseURL,
|
|
||||||
},
|
|
||||||
nitro: {
|
|
||||||
envPrefix: 'NUXT_',
|
|
||||||
// TODO: address upstream issue with defu types...?
|
|
||||||
...nuxt.options.runtimeConfig.nitro satisfies RuntimeConfig['nitro'] as any,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
appConfig: nuxt.options.appConfig,
|
appConfig: nuxt.options.appConfig,
|
||||||
appConfigFiles: nuxt.options._layers.map(
|
appConfigFiles: nuxt.options._layers.map(
|
||||||
layer => resolve(layer.config.srcDir, 'app.config'),
|
layer => resolve(layer.config.srcDir, 'app.config'),
|
||||||
@ -178,6 +161,8 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
'nuxt3/dist',
|
'nuxt3/dist',
|
||||||
'nuxt-nightly/dist',
|
'nuxt-nightly/dist',
|
||||||
distDir,
|
distDir,
|
||||||
|
// Ensure app config files have auto-imports injected even if they are pure .js files
|
||||||
|
...nuxt.options._layers.map(layer => resolve(layer.config.srcDir, 'app.config')),
|
||||||
],
|
],
|
||||||
traceInclude: [
|
traceInclude: [
|
||||||
// force include files used in generated code from the runtime-compiler
|
// force include files used in generated code from the runtime-compiler
|
||||||
@ -235,9 +220,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
|
|
||||||
// Add app manifest handler and prerender configuration
|
// Add app manifest handler and prerender configuration
|
||||||
if (nuxt.options.experimental.appManifest) {
|
if (nuxt.options.experimental.appManifest) {
|
||||||
// @ts-expect-error untyped nuxt property
|
const buildId = nuxt.options.runtimeConfig.app.buildId ||= nuxt.options.buildId
|
||||||
const buildId = nuxt.options.appConfig.nuxt!.buildId ||=
|
|
||||||
(nuxt.options.dev ? 'dev' : nuxt.options.test ? 'test' : randomUUID())
|
|
||||||
const buildTimestamp = Date.now()
|
const buildTimestamp = Date.now()
|
||||||
|
|
||||||
const manifestPrefix = joinURL(nuxt.options.app.buildAssetsDir, 'builds')
|
const manifestPrefix = joinURL(nuxt.options.app.buildAssetsDir, 'builds')
|
||||||
@ -400,7 +383,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
tsConfig.compilerOptions.paths[alias] = [absolutePath]
|
tsConfig.compilerOptions.paths[alias] = [absolutePath]
|
||||||
tsConfig.compilerOptions.paths[`${alias}/*`] = [`${absolutePath}/*`]
|
tsConfig.compilerOptions.paths[`${alias}/*`] = [`${absolutePath}/*`]
|
||||||
} else {
|
} else {
|
||||||
tsConfig.compilerOptions.paths[alias] = [absolutePath.replace(/(?<=\w)\.\w+$/g, '')] /* remove extension */
|
tsConfig.compilerOptions.paths[alias] = [absolutePath.replace(/\b\.\w+$/g, '')] /* remove extension */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -589,7 +572,7 @@ async function spaLoadingTemplate (nuxt: Nuxt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nuxt.options.spaLoadingTemplate === true) {
|
if (nuxt.options.spaLoadingTemplate === true) {
|
||||||
return defaultSpaLoadingTemplate({})
|
return defaultSpaLoadingTemplate()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nuxt.options.spaLoadingTemplate) {
|
if (nuxt.options.spaLoadingTemplate) {
|
||||||
|
@ -4,15 +4,16 @@ import ignore from 'ignore'
|
|||||||
import type { LoadNuxtOptions } from '@nuxt/kit'
|
import type { LoadNuxtOptions } from '@nuxt/kit'
|
||||||
import { addBuildPlugin, addComponent, addPlugin, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit'
|
import { addBuildPlugin, addComponent, addPlugin, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit'
|
||||||
import { resolvePath as _resolvePath } from 'mlly'
|
import { resolvePath as _resolvePath } from 'mlly'
|
||||||
import type { Nuxt, NuxtHooks, NuxtOptions } from 'nuxt/schema'
|
import type { Nuxt, NuxtHooks, NuxtOptions, RuntimeConfig } from 'nuxt/schema'
|
||||||
import type { PackageJson } from 'pkg-types'
|
import type { PackageJson } from 'pkg-types'
|
||||||
import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
|
import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
|
||||||
|
|
||||||
import escapeRE from 'escape-string-regexp'
|
import escapeRE from 'escape-string-regexp'
|
||||||
import fse from 'fs-extra'
|
import fse from 'fs-extra'
|
||||||
import { withoutLeadingSlash } from 'ufo'
|
import { withTrailingSlash, withoutLeadingSlash } from 'ufo'
|
||||||
|
|
||||||
import defu from 'defu'
|
import defu from 'defu'
|
||||||
|
import { gt, satisfies } from 'semver'
|
||||||
import pagesModule from '../pages/module'
|
import pagesModule from '../pages/module'
|
||||||
import metaModule from '../head/module'
|
import metaModule from '../head/module'
|
||||||
import componentsModule from '../components/module'
|
import componentsModule from '../components/module'
|
||||||
@ -20,6 +21,7 @@ import importsModule from '../imports/module'
|
|||||||
|
|
||||||
import { distDir, pkgDir } from '../dirs'
|
import { distDir, pkgDir } from '../dirs'
|
||||||
import { version } from '../../package.json'
|
import { version } from '../../package.json'
|
||||||
|
import { scriptsStubsPreset } from '../imports/presets'
|
||||||
import { ImportProtectionPlugin, nuxtImportProtections } from './plugins/import-protection'
|
import { ImportProtectionPlugin, nuxtImportProtections } from './plugins/import-protection'
|
||||||
import type { UnctxTransformPluginOptions } from './plugins/unctx'
|
import type { UnctxTransformPluginOptions } from './plugins/unctx'
|
||||||
import { UnctxTransformPlugin } from './plugins/unctx'
|
import { UnctxTransformPlugin } from './plugins/unctx'
|
||||||
@ -33,6 +35,7 @@ import schemaModule from './schema'
|
|||||||
import { RemovePluginMetadataPlugin } from './plugins/plugin-metadata'
|
import { RemovePluginMetadataPlugin } from './plugins/plugin-metadata'
|
||||||
import { AsyncContextInjectionPlugin } from './plugins/async-context'
|
import { AsyncContextInjectionPlugin } from './plugins/async-context'
|
||||||
import { resolveDeepImportsPlugin } from './plugins/resolve-deep-imports'
|
import { resolveDeepImportsPlugin } from './plugins/resolve-deep-imports'
|
||||||
|
import { prehydrateTransformPlugin } from './plugins/prehydrate'
|
||||||
|
|
||||||
export function createNuxt (options: NuxtOptions): Nuxt {
|
export function createNuxt (options: NuxtOptions): Nuxt {
|
||||||
const hooks = createHooks<NuxtHooks>()
|
const hooks = createHooks<NuxtHooks>()
|
||||||
@ -61,6 +64,11 @@ const nightlies = {
|
|||||||
'@nuxt/kit': '@nuxt/kit-nightly',
|
'@nuxt/kit': '@nuxt/kit-nightly',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keyDependencies = [
|
||||||
|
'@nuxt/kit',
|
||||||
|
'@nuxt/schema',
|
||||||
|
]
|
||||||
|
|
||||||
async function initNuxt (nuxt: Nuxt) {
|
async function initNuxt (nuxt: Nuxt) {
|
||||||
// Register user hooks
|
// Register user hooks
|
||||||
for (const config of nuxt.options._layers.map(layer => layer.config).reverse()) {
|
for (const config of nuxt.options._layers.map(layer => layer.config).reverse()) {
|
||||||
@ -69,6 +77,17 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restart Nuxt when layer directories are added or removed
|
||||||
|
const layersDir = withTrailingSlash(resolve(nuxt.options.rootDir, 'layers'))
|
||||||
|
nuxt.hook('builder:watch', (event, relativePath) => {
|
||||||
|
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||||
|
if (event === 'addDir' || event === 'unlinkDir') {
|
||||||
|
if (path.startsWith(layersDir)) {
|
||||||
|
return nuxt.callHook('restart', { hard: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Set nuxt instance for useNuxt
|
// Set nuxt instance for useNuxt
|
||||||
nuxtCtx.set(nuxt)
|
nuxtCtx.set(nuxt)
|
||||||
nuxt.hook('close', () => nuxtCtx.unset())
|
nuxt.hook('close', () => nuxtCtx.unset())
|
||||||
@ -110,6 +129,8 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
if (nuxt.options.typescript.shim) {
|
if (nuxt.options.typescript.shim) {
|
||||||
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/vue-shim.d.ts') })
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/vue-shim.d.ts') })
|
||||||
}
|
}
|
||||||
|
// Add shims for `#build/*` imports that do not already have matching types
|
||||||
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/build.d.ts') })
|
||||||
// Add module augmentations directly to NuxtConfig
|
// Add module augmentations directly to NuxtConfig
|
||||||
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/schema.d.ts') })
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/schema.d.ts') })
|
||||||
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/app.config.d.ts') })
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/app.config.d.ts') })
|
||||||
@ -125,6 +146,14 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Prompt to install `@nuxt/scripts` if user has configured it
|
||||||
|
// @ts-expect-error scripts types are not present as the module is not installed
|
||||||
|
if (nuxt.options.scripts) {
|
||||||
|
if (!nuxt.options._modules.some(m => m === '@nuxt/scripts' || m === '@nuxt/scripts-nightly')) {
|
||||||
|
await import('../core/features').then(({ installNuxtModule }) => installNuxtModule('@nuxt/scripts'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add plugin normalization plugin
|
// Add plugin normalization plugin
|
||||||
addBuildPlugin(RemovePluginMetadataPlugin(nuxt))
|
addBuildPlugin(RemovePluginMetadataPlugin(nuxt))
|
||||||
|
|
||||||
@ -141,6 +170,9 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
// add resolver for modules used in virtual files
|
// add resolver for modules used in virtual files
|
||||||
addVitePlugin(() => resolveDeepImportsPlugin(nuxt))
|
addVitePlugin(() => resolveDeepImportsPlugin(nuxt))
|
||||||
|
|
||||||
|
// Add transform for `onPrehydrate` lifecycle hook
|
||||||
|
addBuildPlugin(prehydrateTransformPlugin(nuxt))
|
||||||
|
|
||||||
if (nuxt.options.experimental.localLayerAliases) {
|
if (nuxt.options.experimental.localLayerAliases) {
|
||||||
// Add layer aliasing support for ~, ~~, @ and @@ aliases
|
// Add layer aliasing support for ~, ~~, @ and @@ aliases
|
||||||
addVitePlugin(() => LayerAliasingPlugin.vite({
|
addVitePlugin(() => LayerAliasingPlugin.vite({
|
||||||
@ -240,6 +272,9 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
...nuxt.options._layers.filter(i => i.cwd.includes('node_modules')).map(i => i.cwd as string),
|
...nuxt.options._layers.filter(i => i.cwd.includes('node_modules')).map(i => i.cwd as string),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Ensure we can resolve dependencies within layers
|
||||||
|
nuxt.options.modulesDir.push(...nuxt.options._layers.map(l => resolve(l.cwd, 'node_modules')))
|
||||||
|
|
||||||
// Init user modules
|
// Init user modules
|
||||||
await nuxt.callHook('modules:before')
|
await nuxt.callHook('modules:before')
|
||||||
const modulesToInstall = []
|
const modulesToInstall = []
|
||||||
@ -527,6 +562,12 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/payload.client'))
|
addPlugin(resolve(nuxt.options.appDir, 'plugins/payload.client'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show compatibility version banner when Nuxt is running with a compatibility version
|
||||||
|
// that is different from the current major version
|
||||||
|
if (!(satisfies(nuxt._version, nuxt.options.future.compatibilityVersion + '.x'))) {
|
||||||
|
console.info(`Running with compatibility version \`${nuxt.options.future.compatibilityVersion}\``)
|
||||||
|
}
|
||||||
|
|
||||||
await nuxt.callHook('ready', nuxt)
|
await nuxt.callHook('ready', nuxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,6 +591,12 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!options._modules.some(m => m === '@nuxt/scripts' || m === '@nuxt/scripts-nightly')) {
|
||||||
|
options.imports = defu(options.imports, {
|
||||||
|
presets: [scriptsStubsPreset],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Nuxt Webpack Builder is currently opt-in
|
// Nuxt Webpack Builder is currently opt-in
|
||||||
if (options.builder === '@nuxt/webpack-builder') {
|
if (options.builder === '@nuxt/webpack-builder') {
|
||||||
if (!await import('./features').then(r => r.ensurePackageInstalled('@nuxt/webpack-builder', {
|
if (!await import('./features').then(r => r.ensurePackageInstalled('@nuxt/webpack-builder', {
|
||||||
@ -581,8 +628,13 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
|||||||
options._modules.push('@nuxt/telemetry')
|
options._modules.push('@nuxt/telemetry')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we share runtime config between Nuxt and Nitro
|
||||||
|
options.runtimeConfig = options.nitro.runtimeConfig as RuntimeConfig
|
||||||
|
|
||||||
const nuxt = createNuxt(options)
|
const nuxt = createNuxt(options)
|
||||||
|
|
||||||
|
await Promise.all(keyDependencies.map(dependency => checkDependencyVersion(dependency, nuxt._version)))
|
||||||
|
|
||||||
// We register hooks layer-by-layer so any overrides need to be registered separately
|
// We register hooks layer-by-layer so any overrides need to be registered separately
|
||||||
if (opts.overrides?.hooks) {
|
if (opts.overrides?.hooks) {
|
||||||
nuxt.hooks.addHooks(opts.overrides.hooks)
|
nuxt.hooks.addHooks(opts.overrides.hooks)
|
||||||
@ -599,4 +651,15 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
|||||||
return nuxt
|
return nuxt
|
||||||
}
|
}
|
||||||
|
|
||||||
const RESTART_RE = /^(app|error|app\.config)\.(js|ts|mjs|jsx|tsx|vue)$/i
|
async function checkDependencyVersion (name: string, nuxtVersion: string): Promise<void> {
|
||||||
|
const path = await resolvePath(name).catch(() => null)
|
||||||
|
|
||||||
|
if (!path) { return }
|
||||||
|
const { version } = await readPackageJSON(path)
|
||||||
|
|
||||||
|
if (version && gt(nuxtVersion, version)) {
|
||||||
|
console.warn(`[nuxt] Expected \`${name}\` to be at least \`${nuxtVersion}\` but got \`${version}\`. This might lead to unexpected behavior. Check your package.json or refresh your lockfile.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RESTART_RE = /^(?:app|error|app\.config)\.(?:js|ts|mjs|jsx|tsx|vue)$/i
|
||||||
|
@ -22,7 +22,7 @@ export const nuxtImportProtections = (nuxt: { options: NuxtOptions }, options: {
|
|||||||
])
|
])
|
||||||
|
|
||||||
patterns.push([
|
patterns.push([
|
||||||
/^((|~|~~|@|@@)\/)?nuxt\.config(\.|$)/,
|
/^((~|~~|@|@@)?\/)?nuxt\.config(\.|$)/,
|
||||||
'Importing directly from a `nuxt.config` file is not allowed. Instead, use runtime config or a module.',
|
'Importing directly from a `nuxt.config` file is not allowed. Instead, use runtime config or a module.',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
67
packages/nuxt/src/core/plugins/prehydrate.ts
Normal file
67
packages/nuxt/src/core/plugins/prehydrate.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { transform } from 'esbuild'
|
||||||
|
import { parse } from 'acorn'
|
||||||
|
import { walk } from 'estree-walker'
|
||||||
|
import type { Node } from 'estree-walker'
|
||||||
|
import type { Nuxt } from '@nuxt/schema'
|
||||||
|
import { createUnplugin } from 'unplugin'
|
||||||
|
import type { SimpleCallExpression } from 'estree'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
|
||||||
|
import { hash } from 'ohash'
|
||||||
|
import { isJS, isVue } from '../utils'
|
||||||
|
|
||||||
|
export function prehydrateTransformPlugin (nuxt: Nuxt) {
|
||||||
|
return createUnplugin(() => ({
|
||||||
|
name: 'nuxt:prehydrate-transform',
|
||||||
|
transformInclude (id) {
|
||||||
|
return isJS(id) || isVue(id, { type: ['script'] })
|
||||||
|
},
|
||||||
|
async transform (code, id) {
|
||||||
|
if (!code.includes('onPrehydrate(')) { return }
|
||||||
|
|
||||||
|
const s = new MagicString(code)
|
||||||
|
const promises: Array<Promise<any>> = []
|
||||||
|
|
||||||
|
walk(parse(code, {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
ranges: true,
|
||||||
|
}) as Node, {
|
||||||
|
enter (_node) {
|
||||||
|
if (_node.type !== 'CallExpression' || _node.callee.type !== 'Identifier') { return }
|
||||||
|
const node = _node as SimpleCallExpression & { start: number, end: number }
|
||||||
|
const name = 'name' in node.callee && node.callee.name
|
||||||
|
if (name === 'onPrehydrate') {
|
||||||
|
if (node.arguments[0].type !== 'ArrowFunctionExpression' && node.arguments[0].type !== 'FunctionExpression') { return }
|
||||||
|
|
||||||
|
const needsAttr = node.arguments[0].params.length > 0
|
||||||
|
const { start, end } = node.arguments[0] as Node & { start: number, end: number }
|
||||||
|
|
||||||
|
const p = transform(`forEach(${code.slice(start, end)})`, { loader: 'ts', minify: true })
|
||||||
|
promises.push(p.then(({ code: result }) => {
|
||||||
|
const cleaned = result.slice('forEach'.length).replace(/;\s+$/, '')
|
||||||
|
const args = [JSON.stringify(cleaned)]
|
||||||
|
if (needsAttr) {
|
||||||
|
args.push(JSON.stringify(hash(result)))
|
||||||
|
}
|
||||||
|
s.overwrite(start, end, args.join(', '))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(promises).catch((e) => {
|
||||||
|
console.error(`[nuxt] Could not transform onPrehydrate in \`${id}\`:`, e)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (s.hasChanged()) {
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client
|
||||||
|
? s.generateMap({ hires: true })
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
@ -6,11 +6,17 @@ import type { H3Event } from 'h3'
|
|||||||
import { withTrailingSlash } from 'ufo'
|
import { withTrailingSlash } from 'ufo'
|
||||||
import { getContext } from 'unctx'
|
import { getContext } from 'unctx'
|
||||||
|
|
||||||
|
import { isVNode } from 'vue'
|
||||||
import type { NitroApp } from '#internal/nitro/app'
|
import type { NitroApp } from '#internal/nitro/app'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { rootDir } from '#internal/dev-server-logs-options'
|
import { rootDir } from '#internal/dev-server-logs-options'
|
||||||
|
|
||||||
|
const devReducers: Record<string, (data: any) => any> = {
|
||||||
|
VNode: data => isVNode(data) ? { type: data.type, props: data.props } : undefined,
|
||||||
|
URL: data => data instanceof URL ? data.toString() : undefined,
|
||||||
|
}
|
||||||
|
|
||||||
interface NuxtDevAsyncContext {
|
interface NuxtDevAsyncContext {
|
||||||
logs: LogObject[]
|
logs: LogObject[]
|
||||||
event: H3Event
|
event: H3Event
|
||||||
@ -54,9 +60,10 @@ export default (nitroApp: NitroApp) => {
|
|||||||
const ctx = asyncContext.tryUse()
|
const ctx = asyncContext.tryUse()
|
||||||
if (!ctx) { return }
|
if (!ctx) { return }
|
||||||
try {
|
try {
|
||||||
htmlContext.bodyAppend.unshift(`<script type="application/json" id="__NUXT_LOGS__">${stringify(ctx.logs, ctx.event.context._payloadReducers)}</script>`)
|
htmlContext.bodyAppend.unshift(`<script type="application/json" id="__NUXT_LOGS__">${stringify(ctx.logs, { ...devReducers, ...ctx.event.context._payloadReducers })}</script>`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[nuxt] Failed to stringify dev server logs. You can define your own reducer/reviver for rich types following the instructions in https://nuxt.com/docs/api/composables/use-nuxt-app#payload.', e)
|
const shortError = e instanceof Error && 'toString' in e ? ` Received \`${e.toString()}\`.` : ''
|
||||||
|
console.warn(`[nuxt] Failed to stringify dev server logs.${shortError} You can define your own reducer/reviver for rich types following the instructions in https://nuxt.com/docs/api/composables/use-nuxt-app#payload.`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -70,8 +77,8 @@ function getStack () {
|
|||||||
return stack.stack?.replace(EXCLUDE_TRACE_RE, '').replace(/^Error.*\n/, '') || ''
|
return stack.stack?.replace(EXCLUDE_TRACE_RE, '').replace(/^Error.*\n/, '') || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const FILENAME_RE = /at.*\(([^:)]+)[):]/
|
const FILENAME_RE = /at[^(]*\(([^:)]+)[):]/
|
||||||
const FILENAME_RE_GLOBAL = /at.*\(([^)]+)\)/g
|
const FILENAME_RE_GLOBAL = /at[^(]*\(([^)]+)\)/g
|
||||||
function extractFilenameFromStack (stacktrace: string) {
|
function extractFilenameFromStack (stacktrace: string) {
|
||||||
return stacktrace.match(FILENAME_RE)?.[1].replace(withTrailingSlash(rootDir), '')
|
return stacktrace.match(FILENAME_RE)?.[1].replace(withTrailingSlash(rootDir), '')
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
../../../../../ui-templates/dist/templates/error-500.d.ts
|
|
@ -1 +0,0 @@
|
|||||||
../../../../../ui-templates/dist/templates/error-500.js
|
|
1
packages/nuxt/src/core/runtime/nitro/error-500.ts
Symbolic link
1
packages/nuxt/src/core/runtime/nitro/error-500.ts
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../../ui-templates/dist/templates/error-500.ts
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user