mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
Merge branch 'main' into patch-21
This commit is contained in:
commit
ad6bef5f70
@ -2,7 +2,7 @@
|
|||||||
// https://containers.dev/implementors/json_reference/
|
// https://containers.dev/implementors/json_reference/
|
||||||
{
|
{
|
||||||
"name": "nuxt-devcontainer",
|
"name": "nuxt-devcontainer",
|
||||||
"dockerFile": "Dockerfile",
|
"build": { "dockerfile": "Dockerfile" },
|
||||||
"features": {},
|
"features": {},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -11,7 +11,7 @@ Before creating the pull request, please make sure you do the following:
|
|||||||
|
|
||||||
- Check that there isn't already a PR that solves the problem the same way. If you find a duplicate, please help us reviewing it.
|
- Check that there isn't already a PR that solves the problem the same way. If you find a duplicate, please help us reviewing it.
|
||||||
- Read the contribution docs at https://nuxt.com/docs/community/contribution
|
- Read the contribution docs at https://nuxt.com/docs/community/contribution
|
||||||
- Ensure that PR title follows conventional commits (https://conventionalcommits.org)
|
- Ensure that PR title follows conventional commits (https://www.conventionalcommits.org)
|
||||||
- Update the corresponding documentation if needed.
|
- Update the corresponding documentation if needed.
|
||||||
- Include relevant tests that fail without this PR but pass with it.
|
- Include relevant tests that fail without this PR but pass with it.
|
||||||
|
|
||||||
|
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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- 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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
|
4
.github/workflows/benchmark.yml
vendored
4
.github/workflows/benchmark.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -46,7 +46,7 @@ jobs:
|
|||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Run benchmarks
|
- name: Run benchmarks
|
||||||
uses: CodSpeedHQ/action@1dbf41f0ae41cebfe61e084e535aebe533409b4d # v2.3.0
|
uses: CodSpeedHQ/action@0b631f8998f2389eb5144632b6f9f8fabd33a86e # v2.4.1
|
||||||
with:
|
with:
|
||||||
run: pnpm vitest bench
|
run: pnpm vitest bench
|
||||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||||
|
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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
|
|
||||||
- name: Lychee link checker
|
- name: Lychee link checker
|
||||||
uses: lycheeverse/lychee-action@1e92115388e88fdc331019d99c8ab8dfe97ddd13 # for v1.8.0
|
uses: lycheeverse/lychee-action@054a8e8c7a88ada133165c6633a49825a32174e2 # for v1.8.0
|
||||||
with:
|
with:
|
||||||
# arguments with file types to check
|
# arguments with file types to check
|
||||||
args: >-
|
args: >-
|
||||||
|
36
.github/workflows/ci.yml
vendored
36
.github/workflows/ci.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -55,7 +55,7 @@ jobs:
|
|||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Cache dist
|
- name: Cache dist
|
||||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||||
with:
|
with:
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
name: dist
|
name: dist
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
- build
|
- build
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- 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,19 +83,19 @@ jobs:
|
|||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10
|
uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
|
||||||
with:
|
with:
|
||||||
languages: javascript
|
languages: javascript
|
||||||
queries: +security-and-quality
|
queries: +security-and-quality
|
||||||
|
|
||||||
- name: Restore dist cache
|
- name: Restore dist cache
|
||||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: packages
|
path: packages
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10
|
uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
|
||||||
with:
|
with:
|
||||||
category: "/language:javascript"
|
category: "/language:javascript"
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ jobs:
|
|||||||
module: ["bundler", "node"]
|
module: ["bundler", "node"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -122,7 +122,7 @@ jobs:
|
|||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Restore dist cache
|
- name: Restore dist cache
|
||||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: packages
|
path: packages
|
||||||
@ -142,7 +142,7 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- 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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -198,6 +198,7 @@ jobs:
|
|||||||
builder: ["vite", "webpack"]
|
builder: ["vite", "webpack"]
|
||||||
context: ["async", "default"]
|
context: ["async", "default"]
|
||||||
manifest: ["manifest-on", "manifest-off"]
|
manifest: ["manifest-on", "manifest-off"]
|
||||||
|
version: ["v4", "v3"]
|
||||||
node: [18]
|
node: [18]
|
||||||
exclude:
|
exclude:
|
||||||
- env: "dev"
|
- env: "dev"
|
||||||
@ -208,7 +209,7 @@ jobs:
|
|||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -222,7 +223,7 @@ jobs:
|
|||||||
run: pnpm playwright-core install chromium
|
run: pnpm playwright-core install chromium
|
||||||
|
|
||||||
- name: Restore dist cache
|
- name: Restore dist cache
|
||||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: packages
|
path: packages
|
||||||
@ -234,9 +235,10 @@ jobs:
|
|||||||
TEST_BUILDER: ${{ matrix.builder }}
|
TEST_BUILDER: ${{ matrix.builder }}
|
||||||
TEST_MANIFEST: ${{ matrix.manifest }}
|
TEST_MANIFEST: ${{ matrix.manifest }}
|
||||||
TEST_CONTEXT: ${{ matrix.context }}
|
TEST_CONTEXT: ${{ matrix.context }}
|
||||||
|
TEST_V4: ${{ matrix.version == 'v4' }}
|
||||||
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || runner.os == 'Windows' }}
|
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || runner.os == 'Windows' }}
|
||||||
|
|
||||||
- uses: codecov/codecov-action@84508663e988701840491b86de86b666e8a86bed # v4.3.0
|
- uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # v4.3.1
|
||||||
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
|
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
@ -258,7 +260,7 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
@ -271,7 +273,7 @@ jobs:
|
|||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Restore dist cache
|
- name: Restore dist cache
|
||||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: packages
|
path: packages
|
||||||
@ -297,7 +299,7 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
@ -310,7 +312,7 @@ jobs:
|
|||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Restore dist cache
|
- name: Restore dist cache
|
||||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: packages
|
path: packages
|
||||||
|
4
.github/workflows/dependency-review.yml
vendored
4
.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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
uses: actions/dependency-review-action@5bbc3ba658137598168acb2ab73b21c432dd411b # v4.2.5
|
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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- 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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
# 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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
with:
|
||||||
ref: '2.x'
|
ref: '2.x'
|
||||||
fetch-depth: 0 # All history
|
fetch-depth: 0 # All history
|
||||||
|
2
.github/workflows/release-pr.yml
vendored
2
.github/workflows/release-pr.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
1
.github/workflows/reproduire-close.yml
vendored
1
.github/workflows/reproduire-close.yml
vendored
@ -10,6 +10,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'workflow_dispatch' || github.repository == 'nuxt/nuxt'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
||||||
with:
|
with:
|
||||||
|
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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
|
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
|
||||||
with:
|
with:
|
||||||
label: needs reproduction
|
label: needs reproduction
|
||||||
|
9
.github/workflows/scorecards.yml
vendored
9
.github/workflows/scorecards.yml
vendored
@ -28,10 +28,11 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
|
if: github.event_name == 'push' || github.repository == 'nuxt/nuxt'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@ -58,7 +59,8 @@ jobs:
|
|||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
# format to the repository Actions tab.
|
# format to the repository Actions tab.
|
||||||
- name: "Upload artifact"
|
- name: "Upload artifact"
|
||||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||||
|
if: github.repository == 'nuxt/nuxt' && success()
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
path: results.sarif
|
path: results.sarif
|
||||||
@ -66,6 +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@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10
|
uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
|
||||||
|
if: github.repository == 'nuxt/nuxt' && success()
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
3
.github/workflows/semantic-pull-requests.yml
vendored
3
.github/workflows/semantic-pull-requests.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
name: Semantic pull request
|
name: Semantic pull request
|
||||||
steps:
|
steps:
|
||||||
- name: Validate PR title
|
- name: Validate PR title
|
||||||
uses: amannn/action-semantic-pull-request@e9fabac35e210fea40ca5b14c0da95a099eff26f # v5.4.0
|
uses: amannn/action-semantic-pull-request@cfb60706e18bc85e8aec535e3c577abe8f70378e # v5.5.2
|
||||||
with:
|
with:
|
||||||
scopes: |
|
scopes: |
|
||||||
kit
|
kit
|
||||||
@ -28,6 +28,7 @@ jobs:
|
|||||||
nuxt
|
nuxt
|
||||||
schema
|
schema
|
||||||
test-utils
|
test-utils
|
||||||
|
ui-templates
|
||||||
vite
|
vite
|
||||||
webpack
|
webpack
|
||||||
deps
|
deps
|
||||||
|
@ -93,7 +93,7 @@ We invite you to contribute and help improve Nuxt 💚
|
|||||||
|
|
||||||
Here are a few ways you can get involved:
|
Here are a few ways you can get involved:
|
||||||
- **Reporting Bugs:** If you come across any bugs or issues, please check out the [reporting bugs guide](https://nuxt.com/docs/community/reporting-bugs) to learn how to submit a bug report.
|
- **Reporting Bugs:** If you come across any bugs or issues, please check out the [reporting bugs guide](https://nuxt.com/docs/community/reporting-bugs) to learn how to submit a bug report.
|
||||||
- **Suggestions:** Have ideas to enhance Nuxt? We'd love to hear them! Check out the [contribution guide](https://nuxt.com/docs/community/contribution#creating-an-issue) to share your suggestions.
|
- **Suggestions:** Have ideas to enhance Nuxt? We'd love to hear them! Check out the [contribution guide](https://nuxt.com/docs/community/contribution) to share your suggestions.
|
||||||
- **Questions:** If you have questions or need assistance, the [getting help guide](https://nuxt.com/docs/community/getting-help) provides resources to help you out.
|
- **Questions:** If you have questions or need assistance, the [getting help guide](https://nuxt.com/docs/community/getting-help) provides resources to help you out.
|
||||||
|
|
||||||
## <a name="local-development">🏠 Local Development</a>
|
## <a name="local-development">🏠 Local Development</a>
|
||||||
|
@ -10,6 +10,10 @@ We made everything so you can start writing `.vue` files from the beginning whil
|
|||||||
|
|
||||||
Nuxt has no vendor lock-in, allowing you to deploy your application [**everywhere, even on the edge**](/blog/nuxt-on-the-edge).
|
Nuxt has no vendor lock-in, allowing you to deploy your application [**everywhere, even on the edge**](/blog/nuxt-on-the-edge).
|
||||||
|
|
||||||
|
::tip
|
||||||
|
If you want to play around with Nuxt in your browser, you can [try it out in one of our online sandboxes](/docs/getting-started/installation#play-online).
|
||||||
|
::
|
||||||
|
|
||||||
## Automation and Conventions
|
## Automation and Conventions
|
||||||
|
|
||||||
Nuxt uses conventions and an opinionated directory structure to automate repetitive tasks and allow developers to focus on pushing features. The configuration file can still customize and override its default behaviors.
|
Nuxt uses conventions and an opinionated directory structure to automate repetitive tasks and allow developers to focus on pushing features. The configuration file can still customize and override its default behaviors.
|
||||||
|
@ -59,6 +59,11 @@ We currently ship an environment for unit testing code that needs a [Nuxt](https
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::tip
|
||||||
|
When importing `@nuxt/test-utils` in your vitest config, It is necessary to have `"type": "module"` specified in your `package.json` or rename your vitest config file appropriately.
|
||||||
|
> ie. `vitest.config.m{ts,js}`.
|
||||||
|
::
|
||||||
|
|
||||||
### Using a Nuxt Runtime Environment
|
### Using a Nuxt Runtime Environment
|
||||||
|
|
||||||
By default, `@nuxt/test-utils` will not change your default Vitest environment, so you can do fine-grained opt-in and run Nuxt tests together with other unit tests.
|
By default, `@nuxt/test-utils` will not change your default Vitest environment, so you can do fine-grained opt-in and run Nuxt tests together with other unit tests.
|
||||||
@ -337,8 +342,8 @@ For example, to mock `/test/` endpoint, you can do:
|
|||||||
```ts twoslash
|
```ts twoslash
|
||||||
import { registerEndpoint } from '@nuxt/test-utils/runtime'
|
import { registerEndpoint } from '@nuxt/test-utils/runtime'
|
||||||
|
|
||||||
registerEndpoint("/test/", () => ({
|
registerEndpoint('/test/', () => ({
|
||||||
test: "test-field"
|
test: 'test-field'
|
||||||
}))
|
}))
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -347,9 +352,9 @@ By default, your request will be made using the `GET` method. You may use anothe
|
|||||||
```ts twoslash
|
```ts twoslash
|
||||||
import { registerEndpoint } from '@nuxt/test-utils/runtime'
|
import { registerEndpoint } from '@nuxt/test-utils/runtime'
|
||||||
|
|
||||||
registerEndpoint("/test/", {
|
registerEndpoint('/test/', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
handler: () => ({ test: "test-field" })
|
handler: () => ({ test: 'test-field' })
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -364,7 +369,7 @@ If you would like to use both the end-to-end and unit testing functionality of `
|
|||||||
`app.nuxt.spec.ts`
|
`app.nuxt.spec.ts`
|
||||||
|
|
||||||
```ts twoslash
|
```ts twoslash
|
||||||
import { mockNuxtImport } from "@nuxt/test-utils/runtime"
|
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
|
||||||
|
|
||||||
mockNuxtImport('useStorage', () => {
|
mockNuxtImport('useStorage', () => {
|
||||||
return () => {
|
return () => {
|
||||||
@ -386,6 +391,95 @@ await setup({
|
|||||||
// ...
|
// ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using `@vue/test-utils`
|
||||||
|
|
||||||
|
If you prefer to use `@vue/test-utils` on its own for unit testing in Nuxt, and you are only testing components which do not rely on Nuxt composables, auto-imports or context, you can follow these steps to set it up.
|
||||||
|
|
||||||
|
1. Install the needed dependencies
|
||||||
|
|
||||||
|
::code-group
|
||||||
|
```bash [yarn]
|
||||||
|
yarn add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
|
||||||
|
```
|
||||||
|
```bash [npm]
|
||||||
|
npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
|
||||||
|
```
|
||||||
|
```bash [pnpm]
|
||||||
|
pnpm add -D vitest @vue/test-utils happy-dom @vitejs/plugin-vue
|
||||||
|
```
|
||||||
|
```bash [bun]
|
||||||
|
bun add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
2. Create a `vitest.config.ts` with the following content:
|
||||||
|
|
||||||
|
```ts twoslash
|
||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
test: {
|
||||||
|
environment: 'happy-dom',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Add a new command for test in your `package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
...
|
||||||
|
"test": "vitest"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create a simple `<HelloWorld>` component `components/HelloWorld.vue` with the following content:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<p>Hello world</p>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Create a simple unit test for this newly created component `~/components/HelloWorld.spec.ts`
|
||||||
|
|
||||||
|
```ts twoslash
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
|
||||||
|
import HelloWorld from './HelloWorld.vue'
|
||||||
|
|
||||||
|
describe('HelloWorld', () => {
|
||||||
|
it('component renders Hello world properly', () => {
|
||||||
|
const wrapper = mount(HelloWorld)
|
||||||
|
expect(wrapper.text()).toContain('Hello world')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Run vitest command
|
||||||
|
|
||||||
|
::code-group
|
||||||
|
```bash [yarn]
|
||||||
|
yarn test
|
||||||
|
```
|
||||||
|
```bash [npm]
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
```bash [pnpm]
|
||||||
|
pnpm run test
|
||||||
|
```
|
||||||
|
```bash [bun]
|
||||||
|
bun run test
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
Congratulations, you're all set to start unit testing with `@vue/test-utils` in Nuxt! Happy testing!
|
||||||
|
|
||||||
## End-To-End Testing
|
## End-To-End Testing
|
||||||
|
|
||||||
For end-to-end testing, we support [Vitest](https://github.com/vitest-dev/vitest), [Jest](https://jestjs.io), [Cucumber](https://cucumber.io/) and [Playwright](https://playwright.dev/) as test runners.
|
For end-to-end testing, we support [Vitest](https://github.com/vitest-dev/vitest), [Jest](https://jestjs.io), [Cucumber](https://cucumber.io/) and [Playwright](https://playwright.dev/) as test runners.
|
||||||
|
@ -6,14 +6,14 @@ navigation.icon: i-ph-play-duotone
|
|||||||
|
|
||||||
## Play Online
|
## Play Online
|
||||||
|
|
||||||
You can start playing with Nuxt 3 in your browser using our online sandboxes:
|
If you just want to play around with Nuxt in your browser without setting up a project, you can use one of our online sandboxes:
|
||||||
|
|
||||||
::card-group
|
::card-group
|
||||||
:card{title="Open on StackBlitz" icon="i-simple-icons-stackblitz" to="https://nuxt.new/s/v3" target="_blank"}
|
:card{title="Open on StackBlitz" icon="i-simple-icons-stackblitz" to="https://nuxt.new/s/v3" target="_blank"}
|
||||||
:card{title="Open on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://nuxt.new/c/v3" target="_blank"}
|
:card{title="Open on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://nuxt.new/c/v3" target="_blank"}
|
||||||
::
|
::
|
||||||
|
|
||||||
Start with one of our starters and themes directly by opening [nuxt.new](https://nuxt.new).
|
Or follow the steps below to set up a new Nuxt project on your computer.
|
||||||
|
|
||||||
## New Project
|
## New Project
|
||||||
|
|
||||||
@ -51,6 +51,10 @@ bunx nuxi@latest init <project-name>
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::tip
|
||||||
|
Alternatively, you can find other starters or themes by opening [nuxt.new](https://nuxt.new) and following the instructions there.
|
||||||
|
::
|
||||||
|
|
||||||
Open your project folder in Visual Studio Code:
|
Open your project folder in Visual Studio Code:
|
||||||
|
|
||||||
```bash [Terminal]
|
```bash [Terminal]
|
||||||
|
@ -135,7 +135,7 @@ Non primitive JS types | ❌ No | ✅ Yes
|
|||||||
|
|
||||||
## External Configuration Files
|
## External Configuration Files
|
||||||
|
|
||||||
Nuxt uses [`nuxt.config.ts`](/docs/guide/directory-structure/nuxt-config) file as the single source of trust for configurations and skips reading external configuration files. During the course of building your project, you may have a need to configure those. The following table highlights common configurations and, where applicable, how they can be configured with Nuxt.
|
Nuxt uses [`nuxt.config.ts`](/docs/guide/directory-structure/nuxt-config) file as the single source of truth for configurations and skips reading external configuration files. During the course of building your project, you may have a need to configure those. The following table highlights common configurations and, where applicable, how they can be configured with Nuxt.
|
||||||
|
|
||||||
Name | Config File | How To Configure
|
Name | Config File | How To Configure
|
||||||
---------------------------------------------|---------------------------|-------------------------
|
---------------------------------------------|---------------------------|-------------------------
|
||||||
@ -149,7 +149,7 @@ Here is a list of other common config files:
|
|||||||
Name | Config File | How To Configure
|
Name | Config File | How To Configure
|
||||||
---------------------------------------------|-------------------------|--------------------------
|
---------------------------------------------|-------------------------|--------------------------
|
||||||
[TypeScript](https://www.typescriptlang.org) | `tsconfig.json` | [More Info](/docs/guide/concepts/typescript#nuxttsconfigjson)
|
[TypeScript](https://www.typescriptlang.org) | `tsconfig.json` | [More Info](/docs/guide/concepts/typescript#nuxttsconfigjson)
|
||||||
[ESLint](https://eslint.org) | `.eslintrc.js` | [More Info](https://eslint.org/docs/latest/use/configure/configuration-files)
|
[ESLint](https://eslint.org) | `eslint.config.js` | [More Info](https://eslint.org/docs/latest/use/configure/configuration-files)
|
||||||
[Prettier](https://prettier.io) | `.prettierrc.json` | [More Info](https://prettier.io/docs/en/configuration.html)
|
[Prettier](https://prettier.io) | `.prettierrc.json` | [More Info](https://prettier.io/docs/en/configuration.html)
|
||||||
[Stylelint](https://stylelint.io) | `.stylelintrc.json` | [More Info](https://stylelint.io/user-guide/configure)
|
[Stylelint](https://stylelint.io) | `.stylelintrc.json` | [More Info](https://stylelint.io/user-guide/configure)
|
||||||
[TailwindCSS](https://tailwindcss.com) | `tailwind.config.js` | [More Info](https://tailwindcss.nuxtjs.org/tailwind/config)
|
[TailwindCSS](https://tailwindcss.com) | `tailwind.config.js` | [More Info](https://tailwindcss.nuxtjs.org/tailwind/config)
|
||||||
|
@ -96,6 +96,16 @@ If you only have a single layout in your application, we recommend using [`app.v
|
|||||||
|
|
||||||
::code-group
|
::code-group
|
||||||
|
|
||||||
|
```vue [app.vue]
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLayout>
|
||||||
|
<NuxtPage />
|
||||||
|
</NuxtLayout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
```vue [layouts/default.vue]
|
```vue [layouts/default.vue]
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
@ -113,7 +113,8 @@ export default defineNuxtConfig({
|
|||||||
head: {
|
head: {
|
||||||
link: [{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }]
|
link: [{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }]
|
||||||
}
|
}
|
||||||
}})
|
}
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dynamically Adding Stylesheets
|
### Dynamically Adding Stylesheets
|
||||||
@ -153,15 +154,15 @@ To use a preprocessor like SCSS, Sass, Less or Stylus, install it first.
|
|||||||
::code-group
|
::code-group
|
||||||
|
|
||||||
```bash [Sass & SCSS]
|
```bash [Sass & SCSS]
|
||||||
npm install sass
|
npm install -D sass
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [Less]
|
```bash [Less]
|
||||||
npm install less
|
npm install -D less
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [Stylus]
|
```bash [Stylus]
|
||||||
npm install stylus
|
npm install -D stylus
|
||||||
```
|
```
|
||||||
|
|
||||||
::
|
::
|
||||||
|
@ -48,7 +48,11 @@ Read more about layers in the **Layer Author Guide**.
|
|||||||
::
|
::
|
||||||
|
|
||||||
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=lnFCM7c9f7I" target="_blank"}
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=lnFCM7c9f7I" target="_blank"}
|
||||||
Watch Learn Vue video 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"}
|
||||||
|
Watch a video from Alexander Lichter about Nuxt Layers.
|
||||||
::
|
::
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 'Vue.js Development'
|
title: 'Vue.js Development'
|
||||||
description: "Nuxt uses Vue.js and adds features such as component auto-imports, file-based routing and composables for a SSR-friendly usage."
|
description: "Nuxt uses Vue.js and adds features such as component auto-imports, file-based routing and composables for an SSR-friendly usage."
|
||||||
---
|
---
|
||||||
|
|
||||||
Nuxt integrates Vue 3, the new major release of Vue that enables new patterns for Nuxt users.
|
Nuxt integrates Vue 3, the new major release of Vue that enables new patterns for Nuxt users.
|
||||||
|
@ -76,18 +76,18 @@ Overwriting options such as `"compilerOptions.paths"` with your own configuratio
|
|||||||
In case you need to extend options provided by `./.nuxt/tsconfig.json` further, you can use the [`alias` property](/docs/api/nuxt-config#alias) within your `nuxt.config`. `nuxi` will pick them up and extend `./.nuxt/tsconfig.json` accordingly.
|
In case you need to extend options provided by `./.nuxt/tsconfig.json` further, you can use the [`alias` property](/docs/api/nuxt-config#alias) within your `nuxt.config`. `nuxi` will pick them up and extend `./.nuxt/tsconfig.json` accordingly.
|
||||||
::
|
::
|
||||||
|
|
||||||
## Stricter Checks
|
## Strict Checks
|
||||||
|
|
||||||
TypeScript comes with certain checks to give you more safety and analysis of your program.
|
TypeScript comes with certain checks to give you more safety and analysis of your program.
|
||||||
|
|
||||||
Once you’ve converted your codebase to TypeScript and felt familiar with it, you can start enabling these checks for greater safety ([read more](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html#getting-stricter-checks)).
|
[Strict checks](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html#getting-stricter-checks) are enabled by default in Nuxt 3 to give you greater type safety.
|
||||||
|
|
||||||
In order to enable strict type checking, you have to update `nuxt.config`:
|
If you are currently converting your codebase to TypeScript, you may want to temporarily disable strict checks by setting `strict` to `false` in your `nuxt.config`:
|
||||||
|
|
||||||
```ts twoslash [nuxt.config.ts]
|
```ts twoslash [nuxt.config.ts]
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
typescript: {
|
typescript: {
|
||||||
strict: true
|
strict: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
@ -5,76 +5,20 @@ description: "Nuxt supports ESLint out of the box"
|
|||||||
|
|
||||||
## ESLint
|
## ESLint
|
||||||
|
|
||||||
The recommended approach for Nuxt is to enable ESLint support using [`@nuxt/eslint-config`](https://github.com/nuxt/eslint-config).
|
The recommended approach for Nuxt is to enable ESLint support using the [`@nuxt/eslint`](https://eslint.nuxt.com/packages/module) module, that will setup project-aware ESLint configuration for you.
|
||||||
|
|
||||||
At the moment, this configuration will not format your files; you can set up Prettier or another tool to do so.
|
:::callout{icon="i-ph-lightbulb-duotone"}
|
||||||
|
The module is designed for the [new ESLint flat config format](https://eslint.org/docs/latest/use/configure/configuration-files-new) with is the [default format since ESLint v9](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/).
|
||||||
|
|
||||||
::alert{type=info}
|
If you are using the legacy `.eslintrc` config, you will need to [configure manually with `@nuxt/eslint-config`](https://eslint.nuxt.com/packages/config#legacy-config-format). We highly recommend you to migrate over the flat config to be future-proof.
|
||||||
We're currently working to refactor the Nuxt ESLint configuration. Subscribe to the [Nuxt ESLint roadmap](https://github.com/nuxt/eslint-config/issues/303) to follow updates.
|
:::
|
||||||
::
|
|
||||||
|
|
||||||
### Install Dependencies
|
## Quick Setup
|
||||||
|
|
||||||
Install both ESLint and the Nuxt configuration as development dependencies.
|
```bash
|
||||||
|
npx nuxi module add eslint
|
||||||
::code-group
|
|
||||||
|
|
||||||
```bash [yarn]
|
|
||||||
yarn add --dev eslint @nuxt/eslint-config
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [npm]
|
Start your Nuxt app, a `eslint.config.mjs` file will be generated under your project root. You can customize it as needed.
|
||||||
npm install --save-dev eslint @nuxt/eslint-config
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash [pnpm]
|
You can learn more about the module and customizations in [Nuxt ESLint's documentation](https://eslint.nuxt.com/packages/module).
|
||||||
pnpm add -D eslint @nuxt/eslint-config
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash [bun]
|
|
||||||
bun add -D eslint @nuxt/eslint-config
|
|
||||||
```
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
Add `.eslintrc.cjs` to the root folder of your Nuxt app.
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
extends: ['@nuxt/eslint-config'],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Modify package.json
|
|
||||||
|
|
||||||
Add the below to lint commands to your `package.json` script section:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"scripts": {
|
|
||||||
...
|
|
||||||
"lint": "eslint .",
|
|
||||||
"lint:fix": "eslint . --fix",
|
|
||||||
...
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
Run the `lint` command to check if the code style is correct or run `lint:fix` to automatically fix issues.
|
|
||||||
|
|
||||||
### Configuring VS Code
|
|
||||||
|
|
||||||
Install the [VS Code ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint).
|
|
||||||
|
|
||||||
In VS Code press `ctrl+shift+p` (`cmd+shift+p` on Mac) to open the command prompt, find `Open Workspace Settings (JSON)`, add the below lines to the JSON and save:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.fixAll.eslint": "explicit"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You're good to go! On save, your files will be linted and auto-fixed.
|
|
||||||
|
@ -6,7 +6,7 @@ navigation.icon: i-ph-folder-duotone
|
|||||||
---
|
---
|
||||||
|
|
||||||
::note
|
::note
|
||||||
To reduce your application's bundle size, this directory is **optional**, meaning that [`vue-router`](https://router.vuejs.org) won't be included if you only use [`app.vue`](/docs/guide/directory-structure/app). To force the pages system, set `pages: true` in `nuxt.config` or have a [`app/router.options.ts`](/docs/guide/directory-structure/pages#router-options).
|
To reduce your application's bundle size, this directory is **optional**, meaning that [`vue-router`](https://router.vuejs.org) won't be included if you only use [`app.vue`](/docs/guide/directory-structure/app). To force the pages system, set `pages: true` in `nuxt.config` or have a [`app/router.options.ts`](/docs/guide/going-further/custom-routing#using-approuteroptions).
|
||||||
::
|
::
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@ -193,6 +193,14 @@ To display the `child.vue` component, you have to insert the `<NuxtPage>` compon
|
|||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```vue {}[pages/parent/child.vue]
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps(['foobar'])
|
||||||
|
|
||||||
|
console.log(props.foobar)
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
### Child Route Keys
|
### Child Route Keys
|
||||||
|
|
||||||
If you want more control over when the `<NuxtPage>` component is re-rendered (for example, for transitions), you can either pass a string or function via the `pageKey` prop, or you can define a `key` value via `definePageMeta`:
|
If you want more control over when the `<NuxtPage>` component is re-rendered (for example, for transitions), you can either pass a string or function via the `pageKey` prop, or you can define a `key` value via `definePageMeta`:
|
||||||
@ -208,7 +216,7 @@ If you want more control over when the `<NuxtPage>` component is re-rendered (fo
|
|||||||
|
|
||||||
Or alternatively:
|
Or alternatively:
|
||||||
|
|
||||||
```vue twoslash {}[pages/child.vue]
|
```vue twoslash {}[pages/parent/child.vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
key: route => route.fullPath
|
key: route => route.fullPath
|
||||||
|
@ -37,6 +37,42 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
There is also a `future` namespace for early opting-in to new features that will become default in a future (possibly major) version of the framework.
|
There is also a `future` namespace for early opting-in to new features that will become default in a future (possibly major) version of the framework.
|
||||||
|
|
||||||
|
### compatibilityVersion
|
||||||
|
|
||||||
|
::important
|
||||||
|
This configuration option is available in Nuxt v3.12+.
|
||||||
|
::
|
||||||
|
|
||||||
|
This enables early access to Nuxt features or flags.
|
||||||
|
|
||||||
|
Setting `compatibilityVersion` to `4` changes defaults throughout your
|
||||||
|
Nuxt configuration to opt-in to Nuxt v4 behaviour, but you can granularly re-enable Nuxt v3 behaviour
|
||||||
|
when testing (see example). Please file issues if so, so that we can
|
||||||
|
address in Nuxt or in the ecosystem.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
future: {
|
||||||
|
compatibilityVersion: 4,
|
||||||
|
},
|
||||||
|
// To re-enable _all_ Nuxt v3 behaviour, set the following options:
|
||||||
|
srcDir: '.',
|
||||||
|
dir: {
|
||||||
|
app: 'app'
|
||||||
|
},
|
||||||
|
experimental: {
|
||||||
|
compileTemplate: true,
|
||||||
|
templateUtils: true,
|
||||||
|
relativeWatchPaths: true,
|
||||||
|
defaults: {
|
||||||
|
useAsyncData: {
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
### typescriptBundlerResolution
|
### typescriptBundlerResolution
|
||||||
|
|
||||||
This enables 'Bundler' module resolution mode for TypeScript, which is the recommended setting
|
This enables 'Bundler' module resolution mode for TypeScript, which is the recommended setting
|
||||||
|
@ -76,7 +76,7 @@ Before publishing your module to npm, makes sure you have an [npmjs.com](https:/
|
|||||||
|
|
||||||
While you can publish your module by bumping its version and using the `npm publish` command, the module starter comes with a release script that helps you make sure you publish a working version of your module to npm and more.
|
While you can publish your module by bumping its version and using the `npm publish` command, the module starter comes with a release script that helps you make sure you publish a working version of your module to npm and more.
|
||||||
|
|
||||||
To use the release script, first, commit all your changes (we recommend you follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) to also take advantage of automatic version bump and changelog update), then run the release script with `npm run release`.
|
To use the release script, first, commit all your changes (we recommend you follow [Conventional Commits](https://www.conventionalcommits.org) to also take advantage of automatic version bump and changelog update), then run the release script with `npm run release`.
|
||||||
|
|
||||||
When running the release script, the following will happen:
|
When running the release script, the following will happen:
|
||||||
|
|
||||||
|
@ -100,6 +100,10 @@ export default defineNuxtConfig({
|
|||||||
If you want to extend a private remote source, you need to add the environment variable `GIGET_AUTH=<token>` to provide a token.
|
If you want to extend a private remote source, you need to add the environment variable `GIGET_AUTH=<token>` to provide a token.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::tip
|
||||||
|
If you want to extend a remote source from a self-hosted GitHub or GitLab instance, you need to supply its URL with the `GIGET_GITHUB_URL=<url>` or `GIGET_GITLAB_URL=<url>` environment variable - or directly configure it with [the `auth` option](https://github.com/unjs/c12#extending-config-layer-from-remote-sources) in your `nuxt.config`.
|
||||||
|
::
|
||||||
|
|
||||||
::note
|
::note
|
||||||
When using git remote sources, if a layer has npm dependencies and you wish to install them, you can do so by specifying `install: true` in your layer options.
|
When using git remote sources, if a layer has npm dependencies and you wish to install them, you can do so by specifying `install: true` in your layer options.
|
||||||
|
|
||||||
|
65
docs/2.guide/4.recipes/2.vite-plugin.md
Normal file
65
docs/2.guide/4.recipes/2.vite-plugin.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
navigation.title: 'Vite Plugins'
|
||||||
|
title: Using Vite Plugins in Nuxt
|
||||||
|
description: Learn how to integrate Vite plugins into your Nuxt project.
|
||||||
|
---
|
||||||
|
|
||||||
|
While Nuxt modules offer extensive functionality, sometimes a specific Vite plugin might meet your needs more directly.
|
||||||
|
|
||||||
|
First, we need to install the Vite plugin, for our example, we'll use `@rollup/plugin-yaml`:
|
||||||
|
|
||||||
|
::code-group
|
||||||
|
|
||||||
|
```bash [npm]
|
||||||
|
npm install @rollup/plugin-yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash [yarn]
|
||||||
|
yarn add @rollup/plugin-yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash [pnpm]
|
||||||
|
pnpm add @rollup/plugin-yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash [bun]
|
||||||
|
bun add @rollup/plugin-yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Next, we need to import and add it to our [`nuxt.config.ts`](/docs/guide/directory-structure/nuxt-config) file:
|
||||||
|
|
||||||
|
```ts [nuxt.config.ts]
|
||||||
|
import yaml from '@rollup/plugin-yaml'
|
||||||
|
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
vite: {
|
||||||
|
plugins: [
|
||||||
|
yaml()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we installed and configured our Vite plugin, we can start using YAML files directly in our project.
|
||||||
|
|
||||||
|
For example, we can have a `config.yaml` that stores configuration data and import this data in our Nuxt components:
|
||||||
|
|
||||||
|
::code-group
|
||||||
|
|
||||||
|
```yaml [data/hello.yaml]
|
||||||
|
greeting: "Hello, Nuxt with Vite!"
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue [components/Hello.vue]
|
||||||
|
<script setup>
|
||||||
|
import config from '~/data/hello.yaml'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>{{ config.greeting }}</h1>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
::
|
3
docs/2.guide/4.recipes/_dir.yml
Normal file
3
docs/2.guide/4.recipes/_dir.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
title: Recipes
|
||||||
|
titleTemplate: '%s · Recipes'
|
||||||
|
icon: i-ph-cooking-pot-duotone
|
56
docs/3.api/1.components/12.nuxt-route-announcer.md
Normal file
56
docs/3.api/1.components/12.nuxt-route-announcer.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: '<NuxtRouteAnnouncer>'
|
||||||
|
description: 'Add a hidden element with the page title for assistive technologies.'
|
||||||
|
navigation:
|
||||||
|
badge: New
|
||||||
|
links:
|
||||||
|
- label: Source
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/components/nuxt-route-announcer.ts
|
||||||
|
size: xs
|
||||||
|
---
|
||||||
|
|
||||||
|
::important
|
||||||
|
This component will be available in Nuxt v3.12.
|
||||||
|
::
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add `<NuxtRouteAnnouncer/>` in your [`app.vue`](/docs/guide/directory-structure/app) or [`layouts/`](/docs/guide/directory-structure/layouts) to enhance accessibility by informing assistive technologies about page's title changes. This ensures that navigational changes are announced to users relying on screen readers.
|
||||||
|
|
||||||
|
```vue [app.vue]
|
||||||
|
<template>
|
||||||
|
<NuxtRouteAnnouncer />
|
||||||
|
<NuxtLayout>
|
||||||
|
<NuxtPage />
|
||||||
|
</NuxtLayout>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Slots
|
||||||
|
|
||||||
|
You can pass custom HTML or components through the route announcer default slot.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<NuxtRouteAnnouncer>
|
||||||
|
<template #default="{ message }">
|
||||||
|
<p>{{ message }} was loaded.</p>
|
||||||
|
</template>
|
||||||
|
</NuxtRouteAnnouncer>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
- `atomic`: Controls if screen readers announce only changes or the entire content. Set to true for full content readout on updates, false for changes only. (default `false`)
|
||||||
|
- `politeness`: Sets the urgency for screen reader announcements: `off` (disable the announcement), `polite` (waits for silence), or `assertive` (interrupts immediately). (default `polite`)
|
||||||
|
|
||||||
|
::callout
|
||||||
|
This component is optional. :br
|
||||||
|
To achieve full customization, you can implement your own one based on [its source code](https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/components/nuxt-route-announcer.ts).
|
||||||
|
::
|
||||||
|
|
||||||
|
::callout
|
||||||
|
You can hook into the underlying announcer instance using [the `useRouteAnnouncer` composable](/docs/api/composables/use-route-announcer), which allows you to set a custom announcement message.
|
||||||
|
::
|
@ -86,6 +86,18 @@ function logFoo () {
|
|||||||
</template>
|
</template>
|
||||||
````
|
````
|
||||||
|
|
||||||
|
````vue [my-page.vue]
|
||||||
|
<script setup lang="ts">
|
||||||
|
const foo = () => {
|
||||||
|
console.log('foo method called')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
foo,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
````
|
||||||
|
|
||||||
## Custom Props
|
## Custom Props
|
||||||
|
|
||||||
In addition, `<NuxtPage>` also accepts custom props that you may need to pass further down the hierarchy.
|
In addition, `<NuxtPage>` also accepts custom props that you may need to pass further down the hierarchy.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 'useAsyncData'
|
title: 'useAsyncData'
|
||||||
description: useAsyncData provides access to data that resolves asynchronously in a SSR-friendly composable.
|
description: useAsyncData provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 'useFetch'
|
title: 'useFetch'
|
||||||
description: 'Fetch data from an API endpoint with a SSR-friendly composable.'
|
description: 'Fetch data from an API endpoint with an SSR-friendly composable.'
|
||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
@ -97,7 +97,7 @@ All fetch options can be given a `computed` or `ref` value. These will be watche
|
|||||||
- `transform`: a function that can be used to alter `handler` function result after resolving
|
- `transform`: a function that can be used to alter `handler` function result after resolving
|
||||||
- `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled.
|
- `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled.
|
||||||
- `pick`: only pick specified keys in this array from the `handler` function result
|
- `pick`: only pick specified keys in this array from the `handler` function result
|
||||||
- `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`.
|
- `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`. (You can [see an example here](/docs/getting-started/data-fetching#watch) of using `watch`.)
|
||||||
- `deep`: return data in a deep ref object (it is `true` by default). It can be set to `false` to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive.
|
- `deep`: return data in a deep ref object (it is `true` by default). It can be set to `false` to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive.
|
||||||
- `dedupe`: avoid fetching same key more than once at a time (defaults to `cancel`). Possible options:
|
- `dedupe`: avoid fetching same key more than once at a time (defaults to `cancel`). Possible options:
|
||||||
- `cancel` - cancels existing requests when a new one is made
|
- `cancel` - cancels existing requests when a new one is made
|
||||||
@ -107,6 +107,10 @@ All fetch options can be given a `computed` or `ref` value. These will be watche
|
|||||||
If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the `useFetch` call will not match other `useFetch` calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`.
|
If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the `useFetch` call will not match other `useFetch` calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::note
|
||||||
|
If you use `useFetch` to call an (external) HTTPS URL with a self-signed certificate in development, you will need to set `NODE_TLS_REJECT_UNAUTHORIZED=0` in your environment.
|
||||||
|
::
|
||||||
|
|
||||||
::tip{icon="i-simple-icons-youtube" color="gray" to="https://www.youtube.com/watch?v=aQPR0xn-MMk" target="_blank"}
|
::tip{icon="i-simple-icons-youtube" color="gray" to="https://www.youtube.com/watch?v=aQPR0xn-MMk" target="_blank"}
|
||||||
Learn how to use `transform` and `getCachedData` to avoid superfluous calls to an API and cache data for visitors on the client.
|
Learn how to use `transform` and `getCachedData` to avoid superfluous calls to an API and cache data for visitors on the client.
|
||||||
::
|
::
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: "useId"
|
title: "useId"
|
||||||
description: Generate an SSR-friendly unique identifier that can be passed to accessibility attributes.
|
description: Generate an SSR-friendly unique identifier that can be passed to accessibility attributes.
|
||||||
|
links:
|
||||||
|
- label: Source
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/id.ts
|
||||||
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
::important
|
::important
|
||||||
|
@ -44,4 +44,4 @@ watch(count, (newCount) => {
|
|||||||
`useLazyAsyncData` is a reserved function name transformed by the compiler, so you should not name your own function `useLazyAsyncData`.
|
`useLazyAsyncData` is a reserved function name transformed by the compiler, so you should not name your own function `useLazyAsyncData`.
|
||||||
::
|
::
|
||||||
|
|
||||||
:read-more{to="/docs/getting-started/data-fetching#uselazyasyncdata"}
|
:read-more{to="/docs/getting-started/data-fetching"}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
---
|
---
|
||||||
title: "usePreviewMode"
|
title: "usePreviewMode"
|
||||||
description: "Use usePreviewMode to check and control preview mode in Nuxt"
|
description: "Use usePreviewMode to check and control preview mode in Nuxt"
|
||||||
|
links:
|
||||||
|
- label: Source
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/preview.ts
|
||||||
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
# `usePreviewMode`
|
# `usePreviewMode`
|
||||||
|
|
||||||
|
Preview mode allows you to see how your changes would be displayed on a live site without revealing them to users.
|
||||||
|
|
||||||
You can use the built-in `usePreviewMode` composable to access and control preview state in Nuxt. If the composable detects preview mode it will automatically force any updates necessary for [`useAsyncData`](/docs/api/composables/use-async-data) and [`useFetch`](/docs/api/composables/use-fetch) to rerender preview content.
|
You can use the built-in `usePreviewMode` composable to access and control preview state in Nuxt. If the composable detects preview mode it will automatically force any updates necessary for [`useAsyncData`](/docs/api/composables/use-async-data) and [`useFetch`](/docs/api/composables/use-fetch) to rerender preview content.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -47,15 +54,11 @@ The `getState` function will append returned values to current state, so be care
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
The example below creates a page where part of a content is rendered only in preview mode.
|
||||||
|
|
||||||
```vue [pages/some-page.vue]
|
```vue [pages/some-page.vue]
|
||||||
<script setup>
|
<script setup>
|
||||||
const route = useRoute()
|
const { enabled, state } = usePreviewMode()
|
||||||
|
|
||||||
const { enabled, state } = usePreviewMode({
|
|
||||||
shouldEnable: () => {
|
|
||||||
return route.query.customPreview === 'true'
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const { data } = await useFetch('/api/preview', {
|
const { data } = await useFetch('/api/preview', {
|
||||||
query: {
|
query: {
|
||||||
@ -67,12 +70,9 @@ const { data } = await useFetch('/api/preview', {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
Some base content
|
Some base content
|
||||||
|
|
||||||
<p v-if="enabled">
|
<p v-if="enabled">
|
||||||
Only preview content: {{ state.token }}
|
Only preview content: {{ state.token }}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<button @click="enabled = false">
|
<button @click="enabled = false">
|
||||||
disable preview mode
|
disable preview mode
|
||||||
</button>
|
</button>
|
||||||
@ -80,3 +80,20 @@ const { data } = await useFetch('/api/preview', {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Now you can generate your site and serve it:
|
||||||
|
|
||||||
|
```bash [Terminal]
|
||||||
|
npx nuxi generate
|
||||||
|
npx nuxi preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can see your preview page by adding the query param `preview` to the end of the page you want to see once:
|
||||||
|
|
||||||
|
```js
|
||||||
|
?preview=true
|
||||||
|
```
|
||||||
|
|
||||||
|
::note
|
||||||
|
`usePreviewMode` should be tested locally with `nuxi generate` and then `nuxi preview` rather than `nuxi dev`. (The [preview command](/docs/api/commands/preview) is not related to preview mode.)
|
||||||
|
::
|
||||||
|
60
docs/3.api/2.composables/use-route-announcer.md
Normal file
60
docs/3.api/2.composables/use-route-announcer.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: 'useRouteAnnouncer'
|
||||||
|
description: This composable observes the page title changes and updates the announcer message accordingly.
|
||||||
|
navigation:
|
||||||
|
badge: New
|
||||||
|
links:
|
||||||
|
- label: Source
|
||||||
|
icon: i-simple-icons-github
|
||||||
|
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/route-announcer.ts
|
||||||
|
size: xs
|
||||||
|
---
|
||||||
|
|
||||||
|
::important
|
||||||
|
This composable will be available in Nuxt v3.12.
|
||||||
|
::
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
A composable which observes the page title changes and updates the announcer message accordingly. Used by [`<NuxtRouteAnnouncer>`](/docs/api/components/nuxt-route-announcer) and controllable.
|
||||||
|
It hooks into Unhead's [`dom:rendered`](https://unhead.unjs.io/api/core/hooks#dom-hooks) to read the page's title and set it as the announcer message.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- `politeness`: Sets the urgency for screen reader announcements: `off` (disable the announcement), `polite` (waits for silence), or `assertive` (interrupts immediately). (default `polite`).
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### `message`
|
||||||
|
|
||||||
|
- **type**: `Ref<string>`
|
||||||
|
- **description**: The message to announce
|
||||||
|
|
||||||
|
### `politeness`
|
||||||
|
|
||||||
|
- **type**: `Ref<string>`
|
||||||
|
- **description**: Screen reader announcement urgency level `off`, `polite`, or `assertive`
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### `set(message, politeness = "polite")`
|
||||||
|
|
||||||
|
Sets the message to announce with its urgency level.
|
||||||
|
|
||||||
|
### `polite(message)`
|
||||||
|
|
||||||
|
Sets the message with `politeness = "polite"`
|
||||||
|
|
||||||
|
### `assertive(message)`
|
||||||
|
|
||||||
|
Sets the message with `politeness = "assertive"`
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { message, politeness, set, polite, assertive } = useRouteAnnouncer({
|
||||||
|
politeness: 'assertive'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
@ -55,3 +55,7 @@ function contactForm() {
|
|||||||
::tip
|
::tip
|
||||||
`$fetch` is the preferred way to make HTTP calls in Nuxt instead of [@nuxt/http](https://github.com/nuxt/http) and [@nuxtjs/axios](https://github.com/nuxt-community/axios-module) that are made for Nuxt 2.
|
`$fetch` is the preferred way to make HTTP calls in Nuxt instead of [@nuxt/http](https://github.com/nuxt/http) and [@nuxtjs/axios](https://github.com/nuxt-community/axios-module) that are made for Nuxt 2.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::note
|
||||||
|
If you use `$fetch` to call an (external) HTTPS URL with a self-signed certificate in development, you will need to set `NODE_TLS_REJECT_UNAUTHORIZED=0` in your environment.
|
||||||
|
::
|
||||||
|
@ -129,7 +129,7 @@ interface PageMeta {
|
|||||||
|
|
||||||
- **Type**: `boolean | (to: RouteLocationNormalized, from: RouteLocationNormalized) => boolean`
|
- **Type**: `boolean | (to: RouteLocationNormalized, from: RouteLocationNormalized) => boolean`
|
||||||
|
|
||||||
Tell Nuxt to scroll to the top before rendering the page or not. If you want to overwrite the default scroll behavior of Nuxt, you can do so in `~/app/router.options.ts` (see [docs](/docs/guide/directory-structure/pages/#router-options)) for more info.
|
Tell Nuxt to scroll to the top before rendering the page or not. If you want to overwrite the default scroll behavior of Nuxt, you can do so in `~/app/router.options.ts` (see [custom routing](/docs/guide/going-further/custom-routing#using-approuteroptions)) for more info.
|
||||||
|
|
||||||
**`[key: string]`**
|
**`[key: string]`**
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ You can also run `pnpm lint:docs:fix` to highlight and resolve any lint issues.
|
|||||||
|
|
||||||
### Open a PR
|
### Open a PR
|
||||||
|
|
||||||
Please make sure your PR title adheres to the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0) guidelines.
|
Please make sure your PR title adheres to the [conventional commits](https://www.conventionalcommits.org) guidelines.
|
||||||
|
|
||||||
```bash [Example of PR title]
|
```bash [Example of PR title]
|
||||||
docs: update the section about the nuxt.config.ts file
|
docs: update the section about the nuxt.config.ts file
|
||||||
|
@ -20,6 +20,9 @@ export default createConfigForNuxt({
|
|||||||
// Don't add other attributes to this object
|
// Don't add other attributes to this object
|
||||||
ignores: [
|
ignores: [
|
||||||
'packages/schema/schema/**',
|
'packages/schema/schema/**',
|
||||||
|
'packages/nuxt/src/app/components/welcome.vue',
|
||||||
|
'packages/nuxt/src/app/components/error-*.vue',
|
||||||
|
'packages/nuxt/src/core/runtime/nitro/error-*',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -207,6 +210,12 @@ export default createConfigForNuxt({
|
|||||||
'perfectionist/sort-objects': 'error',
|
'perfectionist/sort-objects': 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ['packages/nuxt/src/app/components/welcome.vue'],
|
||||||
|
rules: {
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Generate type definitions for the eslint config
|
// Generate type definitions for the eslint config
|
||||||
|
@ -11,6 +11,8 @@ exclude = [
|
|||||||
"https://twitter.nuxt.dev/",
|
"https://twitter.nuxt.dev/",
|
||||||
"https://github.com/nuxt/translations/discussions/4",
|
"https://github.com/nuxt/translations/discussions/4",
|
||||||
"https://stackoverflow.com/help/minimal-reproducible-example",
|
"https://stackoverflow.com/help/minimal-reproducible-example",
|
||||||
|
# TODO: remove when their SSL certificate is valid again
|
||||||
|
"https://www.conventionalcommits.org",
|
||||||
# single-quotes are required for regexp
|
# single-quotes are required for regexp
|
||||||
'(https?:\/\/github\.com\/)(.*\/)(generate)',
|
'(https?:\/\/github\.com\/)(.*\/)(generate)',
|
||||||
]
|
]
|
||||||
|
47
package.json
47
package.json
@ -8,11 +8,11 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm --filter './packages/**' prepack",
|
"build": "pnpm --filter @nuxt/ui-templates prepack && pnpm --filter './packages/[^u]**' 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 './packages/**' prepack --stub",
|
"dev:prepare": "pnpm --filter @nuxt/ui-templates prepack && pnpm --filter './packages/[^u]**' 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",
|
||||||
@ -26,51 +26,52 @@
|
|||||||
"test:fixtures": "pnpm test:prepare && vitest run --dir test",
|
"test:fixtures": "pnpm test:prepare && vitest run --dir test",
|
||||||
"test:fixtures:dev": "TEST_ENV=dev pnpm test:fixtures",
|
"test:fixtures:dev": "TEST_ENV=dev pnpm test:fixtures",
|
||||||
"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 --coverage",
|
"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": "vitest run packages/ --coverage",
|
"test:unit": "JITI_CACHE=0 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"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
|
"@nuxt/ui-templates": "workspace:*",
|
||||||
"@nuxt/vite-builder": "workspace:*",
|
"@nuxt/vite-builder": "workspace:*",
|
||||||
"@nuxt/webpack-builder": "workspace:*",
|
"@nuxt/webpack-builder": "workspace:*",
|
||||||
"rollup": "^4.14.2",
|
"magic-string": "^0.30.10",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"vite": "5.2.8",
|
"rollup": "^4.17.2",
|
||||||
"vue": "3.4.21",
|
"vite": "5.2.11",
|
||||||
"magic-string": "^0.30.9"
|
"vue": "3.4.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.0.0",
|
"@eslint/js": "9.1.1",
|
||||||
"@nuxt/eslint-config": "0.3.6",
|
"@nuxt/eslint-config": "0.3.10",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@nuxt/test-utils": "3.12.0",
|
"@nuxt/test-utils": "3.12.1",
|
||||||
"@nuxt/webpack-builder": "workspace:*",
|
"@nuxt/webpack-builder": "workspace:*",
|
||||||
"@testing-library/vue": "8.0.3",
|
"@testing-library/vue": "8.0.3",
|
||||||
"@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.7",
|
"@types/node": "20.12.8",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@vitest/coverage-v8": "1.4.0",
|
"@vitest/coverage-v8": "1.5.3",
|
||||||
"@vue/test-utils": "2.4.5",
|
"@vue/test-utils": "2.4.5",
|
||||||
"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": "4.3.2",
|
"devalue": "5.0.0",
|
||||||
"eslint": "9.0.0",
|
"eslint": "9.1.1",
|
||||||
"eslint-plugin-no-only-tests": "3.1.0",
|
"eslint-plugin-no-only-tests": "3.1.0",
|
||||||
"eslint-plugin-perfectionist": "2.8.0",
|
"eslint-plugin-perfectionist": "2.10.0",
|
||||||
"eslint-typegen": "0.2.2",
|
"eslint-typegen": "0.2.4",
|
||||||
"execa": "8.0.1",
|
"execa": "8.0.1",
|
||||||
"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.7.1",
|
||||||
"jiti": "1.21.0",
|
"jiti": "1.21.0",
|
||||||
"markdownlint-cli": "0.39.0",
|
"markdownlint-cli": "0.40.0",
|
||||||
"nitropack": "2.9.6",
|
"nitropack": "2.9.6",
|
||||||
"nuxi": "3.11.1",
|
"nuxi": "3.11.1",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
@ -83,13 +84,13 @@
|
|||||||
"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.4.0",
|
"vitest": "1.5.3",
|
||||||
"vitest-environment-nuxt": "1.0.0",
|
"vitest-environment-nuxt": "1.0.0",
|
||||||
"vue": "3.4.21",
|
"vue": "3.4.26",
|
||||||
"vue-router": "4.3.0",
|
"vue-router": "4.3.2",
|
||||||
"vue-tsc": "2.0.13"
|
"vue-tsc": "2.0.16"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.15.6",
|
"packageManager": "pnpm@9.0.6",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
},
|
},
|
||||||
|
@ -35,9 +35,9 @@
|
|||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"jiti": "^1.21.0",
|
"jiti": "^1.21.0",
|
||||||
"knitwork": "^1.1.0",
|
"knitwork": "^1.1.0",
|
||||||
"mlly": "^1.6.1",
|
"mlly": "^1.7.0",
|
||||||
"pathe": "^1.1.2",
|
"pathe": "^1.1.2",
|
||||||
"pkg-types": "^1.0.3",
|
"pkg-types": "^1.1.0",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
"ufo": "^1.5.3",
|
"ufo": "^1.5.3",
|
||||||
@ -52,8 +52,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.8",
|
"vite": "5.2.11",
|
||||||
"vitest": "1.4.0",
|
"vitest": "1.5.3",
|
||||||
"webpack": "5.91.0"
|
"webpack": "5.91.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -1,5 +1,17 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
import { resolveGroupSyntax } from './ignore.js'
|
import type { Nuxt, NuxtOptions } from '@nuxt/schema'
|
||||||
|
import { isIgnored, resolveGroupSyntax, resolveIgnorePatterns } from './ignore.js'
|
||||||
|
import * as context from './context.js'
|
||||||
|
|
||||||
|
describe('isIgnored', () => {
|
||||||
|
it('should populate _ignore', () => {
|
||||||
|
const mockNuxt = { options: { ignore: ['my-dir'] } as NuxtOptions } as Nuxt
|
||||||
|
vi.spyOn(context, 'tryUseNuxt').mockReturnValue(mockNuxt)
|
||||||
|
|
||||||
|
expect(isIgnored('my-dir/my-file.ts')).toBe(true)
|
||||||
|
expect(resolveIgnorePatterns()?.includes('my-dir')).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('resolveGroupSyntax', () => {
|
describe('resolveGroupSyntax', () => {
|
||||||
it('should resolve single group syntax', () => {
|
it('should resolve single group syntax', () => {
|
||||||
|
@ -28,6 +28,8 @@ export function isIgnored (pathname: string): boolean {
|
|||||||
return !!(relativePath && nuxt._ignore.ignores(relativePath))
|
return !!(relativePath && nuxt._ignore.ignores(relativePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NEGATION_RE = /^(!?)(.*)$/
|
||||||
|
|
||||||
export function resolveIgnorePatterns (relativePath?: string): string[] {
|
export function resolveIgnorePatterns (relativePath?: string): string[] {
|
||||||
const nuxt = tryUseNuxt()
|
const nuxt = tryUseNuxt()
|
||||||
|
|
||||||
@ -36,22 +38,26 @@ export function resolveIgnorePatterns (relativePath?: string): string[] {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nuxt._ignorePatterns) {
|
const ignorePatterns = nuxt.options.ignore.flatMap(s => resolveGroupSyntax(s))
|
||||||
nuxt._ignorePatterns = nuxt.options.ignore.flatMap(s => resolveGroupSyntax(s))
|
|
||||||
|
|
||||||
const nuxtignoreFile = join(nuxt.options.rootDir, '.nuxtignore')
|
const nuxtignoreFile = join(nuxt.options.rootDir, '.nuxtignore')
|
||||||
if (existsSync(nuxtignoreFile)) {
|
if (existsSync(nuxtignoreFile)) {
|
||||||
const contents = readFileSync(nuxtignoreFile, 'utf-8')
|
const contents = readFileSync(nuxtignoreFile, 'utf-8')
|
||||||
nuxt._ignorePatterns.push(...contents.trim().split(/\r?\n/))
|
ignorePatterns.push(...contents.trim().split(/\r?\n/))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relativePath) {
|
if (relativePath) {
|
||||||
// Map ignore patterns based on if they start with * or !*
|
// Map ignore patterns based on if they start with * or !*
|
||||||
return nuxt._ignorePatterns.map(p => p[0] === '*' || (p[0] === '!' && p[1] === '*') ? p : relative(relativePath, resolve(nuxt.options.rootDir, p)))
|
return ignorePatterns.map((p) => {
|
||||||
|
const [_, negation = '', pattern] = p.match(NEGATION_RE) || []
|
||||||
|
if (pattern[0] === '*') {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return negation + relative(relativePath, resolve(nuxt.options.rootDir, pattern || p))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return nuxt._ignorePatterns
|
return ignorePatterns
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { resolve } from 'pathe'
|
|
||||||
import type { JSValue } from 'untyped'
|
import type { JSValue } from 'untyped'
|
||||||
import { applyDefaults } from 'untyped'
|
import { applyDefaults } from 'untyped'
|
||||||
import type { LoadConfigOptions } from 'c12'
|
import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12'
|
||||||
import { loadConfig } from 'c12'
|
import { loadConfig } from 'c12'
|
||||||
import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
|
import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
|
||||||
import { NuxtConfigSchema } from '@nuxt/schema'
|
import { NuxtConfigSchema } from '@nuxt/schema'
|
||||||
|
|
||||||
export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}
|
export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}
|
||||||
|
|
||||||
|
const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'dir']
|
||||||
|
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> {
|
||||||
(globalThis as any).defineNuxtConfig = (c: any) => c
|
(globalThis as any).defineNuxtConfig = (c: any) => c
|
||||||
const result = await loadConfig<NuxtConfig>({
|
const result = await loadConfig<NuxtConfig>({
|
||||||
@ -28,15 +30,20 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
|
|||||||
nuxtConfig._nuxtConfigFile = configFile
|
nuxtConfig._nuxtConfigFile = configFile
|
||||||
nuxtConfig._nuxtConfigFiles = [configFile]
|
nuxtConfig._nuxtConfigFiles = [configFile]
|
||||||
|
|
||||||
// Resolve `rootDir` & `srcDir` of layers
|
const _layers: ConfigLayer<NuxtConfig, ConfigLayerMeta>[] = []
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
|
// Resolve `rootDir` & `srcDir` of layers
|
||||||
layer.config = layer.config || {}
|
layer.config = layer.config || {}
|
||||||
layer.config.rootDir = layer.config.rootDir ?? layer.cwd
|
layer.config.rootDir = layer.config.rootDir ?? layer.cwd
|
||||||
layer.config.srcDir = resolve(layer.config.rootDir!, layer.config.srcDir!)
|
|
||||||
|
// Normalise layer directories
|
||||||
|
layer.config = await applyDefaults(layerSchema, layer.config as NuxtConfig & Record<string, JSValue>) as unknown as NuxtConfig
|
||||||
|
|
||||||
|
// Filter layers
|
||||||
|
if (!layer.configFile || layer.configFile.endsWith('.nuxtrc')) { continue }
|
||||||
|
_layers.push(layer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter layers
|
|
||||||
const _layers = layers.filter(layer => layer.configFile && !layer.configFile.endsWith('.nuxtrc'))
|
|
||||||
;(nuxtConfig as any)._layers = _layers
|
;(nuxtConfig as any)._layers = _layers
|
||||||
|
|
||||||
// Ensure at least one layer remains (without nuxt.config)
|
// Ensure at least one layer remains (without nuxt.config)
|
||||||
|
31
packages/kit/src/resolve.test.ts
Normal file
31
packages/kit/src/resolve.test.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { resolve } from 'pathe'
|
||||||
|
import { loadNuxt } from './loader/nuxt'
|
||||||
|
import { findPath, resolvePath } from './resolve'
|
||||||
|
import { defineNuxtModule } from './module/define'
|
||||||
|
import { addTemplate } from './template'
|
||||||
|
|
||||||
|
const nuxt = await loadNuxt({
|
||||||
|
overrides: {
|
||||||
|
modules: [
|
||||||
|
defineNuxtModule(() => {
|
||||||
|
addTemplate({
|
||||||
|
filename: 'my-template.mjs',
|
||||||
|
getContents: () => 'export const myUtil = () => \'hello\'',
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resolvePath', () => {
|
||||||
|
it('should resolve paths correctly', async () => {
|
||||||
|
expect(await resolvePath('.nuxt/app.config')).toBe(resolve(nuxt.options.buildDir, 'app.config'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('findPath', () => {
|
||||||
|
it('should find paths correctly', async () => {
|
||||||
|
expect(await findPath(resolve(nuxt.options.buildDir, 'my-template'), { virtual: true })).toBe(resolve(nuxt.options.buildDir, 'my-template.mjs'))
|
||||||
|
})
|
||||||
|
})
|
@ -17,6 +17,12 @@ export interface ResolvePathOptions {
|
|||||||
|
|
||||||
/** The file extensions to try. Default is Nuxt configured extensions. */
|
/** The file extensions to try. Default is Nuxt configured extensions. */
|
||||||
extensions?: string[]
|
extensions?: string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to resolve files that exist in the Nuxt VFS (for example, as a Nuxt template).
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
virtual?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,8 +36,13 @@ export async function resolvePath (path: string, opts: ResolvePathOptions = {}):
|
|||||||
path = normalize(path)
|
path = normalize(path)
|
||||||
|
|
||||||
// Fast return if the path exists
|
// Fast return if the path exists
|
||||||
if (isAbsolute(path) && existsSync(path) && !(await isDirectory(path))) {
|
if (isAbsolute(path)) {
|
||||||
return path
|
if (opts?.virtual && existsInVFS(path)) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (existsSync(path) && !(await isDirectory(path))) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use current nuxt options
|
// Use current nuxt options
|
||||||
@ -49,6 +60,10 @@ export async function resolvePath (path: string, opts: ResolvePathOptions = {}):
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if resolvedPath is a file
|
// Check if resolvedPath is a file
|
||||||
|
if (opts?.virtual && existsInVFS(path, nuxt)) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
let _isDir = false
|
let _isDir = false
|
||||||
if (existsSync(path)) {
|
if (existsSync(path)) {
|
||||||
_isDir = await isDirectory(path)
|
_isDir = await isDirectory(path)
|
||||||
@ -61,11 +76,17 @@ export async function resolvePath (path: string, opts: ResolvePathOptions = {}):
|
|||||||
for (const ext of extensions) {
|
for (const ext of extensions) {
|
||||||
// path.[ext]
|
// path.[ext]
|
||||||
const pathWithExt = path + ext
|
const pathWithExt = path + ext
|
||||||
|
if (opts?.virtual && existsInVFS(pathWithExt, nuxt)) {
|
||||||
|
return pathWithExt
|
||||||
|
}
|
||||||
if (existsSync(pathWithExt)) {
|
if (existsSync(pathWithExt)) {
|
||||||
return pathWithExt
|
return pathWithExt
|
||||||
}
|
}
|
||||||
// path/index.[ext]
|
// path/index.[ext]
|
||||||
const pathWithIndex = join(path, 'index' + ext)
|
const pathWithIndex = join(path, 'index' + ext)
|
||||||
|
if (opts?.virtual && existsInVFS(pathWithIndex, nuxt)) {
|
||||||
|
return pathWithIndex
|
||||||
|
}
|
||||||
if (_isDir && existsSync(pathWithIndex)) {
|
if (_isDir && existsSync(pathWithIndex)) {
|
||||||
return pathWithIndex
|
return pathWithIndex
|
||||||
}
|
}
|
||||||
@ -85,8 +106,17 @@ export async function resolvePath (path: string, opts: ResolvePathOptions = {}):
|
|||||||
* Try to resolve first existing file in paths
|
* Try to resolve first existing file in paths
|
||||||
*/
|
*/
|
||||||
export async function findPath (paths: string | string[], opts?: ResolvePathOptions, pathType: 'file' | 'dir' = 'file'): Promise<string | null> {
|
export async function findPath (paths: string | string[], opts?: ResolvePathOptions, pathType: 'file' | 'dir' = 'file'): Promise<string | null> {
|
||||||
|
const nuxt = opts?.virtual ? tryUseNuxt() : undefined
|
||||||
|
|
||||||
for (const path of toArray(paths)) {
|
for (const path of toArray(paths)) {
|
||||||
const rPath = await resolvePath(path, opts)
|
const rPath = await resolvePath(path, opts)
|
||||||
|
|
||||||
|
// Check VFS
|
||||||
|
if (opts?.virtual && existsInVFS(rPath, nuxt)) {
|
||||||
|
return rPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file system
|
||||||
if (await existsSensitive(rPath)) {
|
if (await existsSensitive(rPath)) {
|
||||||
const _isDir = await isDirectory(rPath)
|
const _isDir = await isDirectory(rPath)
|
||||||
if (!pathType || (pathType === 'file' && !_isDir) || (pathType === 'dir' && _isDir)) {
|
if (!pathType || (pathType === 'file' && !_isDir) || (pathType === 'dir' && _isDir)) {
|
||||||
@ -160,6 +190,17 @@ async function isDirectory (path: string) {
|
|||||||
return (await fsp.lstat(path)).isDirectory()
|
return (await fsp.lstat(path)).isDirectory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function existsInVFS (path: string, nuxt = tryUseNuxt()) {
|
||||||
|
if (!nuxt) { return false }
|
||||||
|
|
||||||
|
if (path in nuxt.vfs) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const templates = nuxt.apps.default?.templates ?? nuxt.options.build.templates
|
||||||
|
return templates.some(template => template.dst === path)
|
||||||
|
}
|
||||||
|
|
||||||
export async function resolveFiles (path: string, pattern: string | string[], opts: { followSymbolicLinks?: boolean } = {}) {
|
export async function resolveFiles (path: string, pattern: string | string[], opts: { followSymbolicLinks?: boolean } = {}) {
|
||||||
const files = await globby(pattern, { cwd: path, followSymbolicLinks: opts.followSymbolicLinks ?? true })
|
const files = await globby(pattern, { cwd: path, followSymbolicLinks: opts.followSymbolicLinks ?? true })
|
||||||
return files.map(p => resolve(path, p)).filter(p => !isIgnored(p)).sort()
|
return files.map(p => resolve(path, p)).filter(p => !isIgnored(p)).sort()
|
||||||
|
@ -129,6 +129,7 @@ export async function _generateTypes (nuxt: Nuxt) {
|
|||||||
jsxImportSource: 'vue',
|
jsxImportSource: 'vue',
|
||||||
target: 'ESNext',
|
target: 'ESNext',
|
||||||
module: 'ESNext',
|
module: 'ESNext',
|
||||||
|
moduleDetection: 'force',
|
||||||
moduleResolution: nuxt.options.future?.typescriptBundlerResolution || (nuxt.options.experimental as any)?.typescriptBundlerResolution ? 'Bundler' : 'Node',
|
moduleResolution: nuxt.options.future?.typescriptBundlerResolution || (nuxt.options.experimental as any)?.typescriptBundlerResolution ? 'Bundler' : 'Node',
|
||||||
skipLibCheck: true,
|
skipLibCheck: true,
|
||||||
isolatedModules: true,
|
isolatedModules: true,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/** @since 3.9.0 */
|
||||||
export function toArray<T> (value: T | T[]): T[] {
|
export function toArray<T> (value: T | T[]): T[] {
|
||||||
return Array.isArray(value) ? value : [value]
|
return Array.isArray(value) ? value : [value]
|
||||||
}
|
}
|
||||||
|
@ -60,23 +60,22 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/devalue": "^2.0.2",
|
"@nuxt/devalue": "^2.0.2",
|
||||||
"@nuxt/devtools": "^1.1.5",
|
"@nuxt/devtools": "^1.2.0",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"@nuxt/telemetry": "^2.5.4",
|
"@nuxt/telemetry": "^2.5.4",
|
||||||
"@nuxt/ui-templates": "^1.3.3",
|
|
||||||
"@nuxt/vite-builder": "workspace:*",
|
"@nuxt/vite-builder": "workspace:*",
|
||||||
"@unhead/dom": "^1.9.5",
|
"@unhead/dom": "^1.9.8",
|
||||||
"@unhead/ssr": "^1.9.5",
|
"@unhead/ssr": "^1.9.8",
|
||||||
"@unhead/vue": "^1.9.5",
|
"@unhead/vue": "^1.9.8",
|
||||||
"@vue/shared": "^3.4.21",
|
"@vue/shared": "^3.4.26",
|
||||||
"acorn": "8.11.3",
|
"acorn": "8.11.3",
|
||||||
"c12": "^1.10.0",
|
"c12": "^1.10.0",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"cookie-es": "^1.1.0",
|
"cookie-es": "^1.1.0",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"destr": "^2.0.3",
|
"destr": "^2.0.3",
|
||||||
"devalue": "^4.3.2",
|
"devalue": "^5.0.0",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.20.2",
|
||||||
"escape-string-regexp": "^5.0.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
"estree-walker": "^3.0.3",
|
"estree-walker": "^3.0.3",
|
||||||
@ -84,11 +83,12 @@
|
|||||||
"globby": "^14.0.1",
|
"globby": "^14.0.1",
|
||||||
"h3": "^1.11.1",
|
"h3": "^1.11.1",
|
||||||
"hookable": "^5.5.3",
|
"hookable": "^5.5.3",
|
||||||
|
"ignore": "^5.3.1",
|
||||||
"jiti": "^1.21.0",
|
"jiti": "^1.21.0",
|
||||||
"klona": "^2.0.6",
|
"klona": "^2.0.6",
|
||||||
"knitwork": "^1.1.0",
|
"knitwork": "^1.1.0",
|
||||||
"magic-string": "^0.30.9",
|
"magic-string": "^0.30.10",
|
||||||
"mlly": "^1.6.1",
|
"mlly": "^1.7.0",
|
||||||
"nitropack": "^2.9.6",
|
"nitropack": "^2.9.6",
|
||||||
"nuxi": "^3.11.1",
|
"nuxi": "^3.11.1",
|
||||||
"nypm": "^0.3.8",
|
"nypm": "^0.3.8",
|
||||||
@ -96,7 +96,7 @@
|
|||||||
"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.0.3",
|
"pkg-types": "^1.1.0",
|
||||||
"radix3": "^1.1.2",
|
"radix3": "^1.1.2",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"std-env": "^3.7.0",
|
"std-env": "^3.7.0",
|
||||||
@ -111,19 +111,20 @@
|
|||||||
"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.21",
|
"vue": "^3.4.26",
|
||||||
"vue-bundle-renderer": "^2.0.0",
|
"vue-bundle-renderer": "^2.0.0",
|
||||||
"vue-devtools-stub": "^0.1.0",
|
"vue-devtools-stub": "^0.1.0",
|
||||||
"vue-router": "^4.3.0"
|
"vue-router": "^4.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@nuxt/ui-templates": "1.3.3",
|
||||||
"@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.8",
|
"vite": "5.2.11",
|
||||||
"vitest": "1.4.0"
|
"vitest": "1.5.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@parcel/watcher": "^2.1.0",
|
"@parcel/watcher": "^2.1.0",
|
||||||
|
@ -41,9 +41,10 @@ const NuxtClientFallbackServer = defineComponent({
|
|||||||
const vm = getCurrentInstance()
|
const vm = getCurrentInstance()
|
||||||
const ssrFailed = ref(false)
|
const ssrFailed = ref(false)
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
const error = useState<boolean | undefined>(`${props.uid}`)
|
||||||
|
|
||||||
onErrorCaptured((err) => {
|
onErrorCaptured((err) => {
|
||||||
useState(`${props.uid}`, () => true)
|
error.value = true
|
||||||
ssrFailed.value = true
|
ssrFailed.value = true
|
||||||
ctx.emit('ssr-error', err)
|
ctx.emit('ssr-error', err)
|
||||||
return false
|
return false
|
||||||
|
1
packages/nuxt/src/app/components/error-404.vue
Symbolic link
1
packages/nuxt/src/app/components/error-404.vue
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../ui-templates/dist/templates/error-404.vue
|
1
packages/nuxt/src/app/components/error-500.vue
Symbolic link
1
packages/nuxt/src/app/components/error-500.vue
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../ui-templates/dist/templates/error-500.vue
|
1
packages/nuxt/src/app/components/error-dev.vue
Symbolic link
1
packages/nuxt/src/app/components/error-dev.vue
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../ui-templates/dist/templates/error-dev.vue
|
@ -40,10 +40,10 @@ const description = _error.message || _error.toString()
|
|||||||
const stack = import.meta.dev && !is404 ? _error.description || `<pre>${stacktrace}</pre>` : undefined
|
const stack = import.meta.dev && !is404 ? _error.description || `<pre>${stacktrace}</pre>` : undefined
|
||||||
|
|
||||||
// TODO: Investigate side-effect issue with imports
|
// TODO: Investigate side-effect issue with imports
|
||||||
const _Error404 = defineAsyncComponent(() => import('@nuxt/ui-templates/templates/error-404.vue').then(r => r.default || r))
|
const _Error404 = defineAsyncComponent(() => import('./error-404.vue').then(r => r.default || r))
|
||||||
const _Error = import.meta.dev
|
const _Error = import.meta.dev
|
||||||
? defineAsyncComponent(() => import('@nuxt/ui-templates/templates/error-dev.vue').then(r => r.default || r))
|
? defineAsyncComponent(() => import('./error-dev.vue').then(r => r.default || r))
|
||||||
: defineAsyncComponent(() => import('@nuxt/ui-templates/templates/error-500.vue').then(r => r.default || r))
|
: defineAsyncComponent(() => import('./error-500.vue').then(r => r.default || r))
|
||||||
|
|
||||||
const ErrorTemplate = is404 ? _Error404 : _Error
|
const ErrorTemplate = is404 ? _Error404 : _Error
|
||||||
</script>
|
</script>
|
||||||
|
@ -7,7 +7,7 @@ import type {
|
|||||||
VNodeProps,
|
VNodeProps,
|
||||||
} 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, RouterLinkProps } 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, parseURL, withTrailingSlash, withoutTrailingSlash } from 'ufo'
|
||||||
import { preloadRouteComponents } from '../composables/preload'
|
import { preloadRouteComponents } from '../composables/preload'
|
||||||
import { onNuxtReady } from '../composables/ready'
|
import { onNuxtReady } from '../composables/ready'
|
||||||
@ -121,6 +121,79 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
return resolvedPath
|
return resolvedPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useNuxtLink (props: NuxtLinkProps) {
|
||||||
|
const router = useRouter()
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
// Resolving `to` value from `to` and `href` props
|
||||||
|
const to: ComputedRef<string | RouteLocationRaw> = computed(() => {
|
||||||
|
checkPropConflicts(props, 'to', 'href')
|
||||||
|
const path = props.to || props.href || '' // Defaults to empty string (won't render any `href` attribute)
|
||||||
|
return resolveTrailingSlashBehavior(path, router.resolve)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Lazily check whether to.value has a protocol
|
||||||
|
const isAbsoluteUrl = computed(() => typeof to.value === 'string' && hasProtocol(to.value, { acceptRelative: true }))
|
||||||
|
|
||||||
|
// Resolves `to` value if it's a route location object
|
||||||
|
const href = computed(() => (typeof to.value === 'object'
|
||||||
|
? router.resolve(to.value)?.href ?? null
|
||||||
|
: (to.value && !props.external && !isAbsoluteUrl.value)
|
||||||
|
? resolveTrailingSlashBehavior(joinURL(config.app.baseURL, to.value), router.resolve) as string
|
||||||
|
: to.value
|
||||||
|
))
|
||||||
|
|
||||||
|
const builtinRouterLink = resolveComponent('RouterLink') as string | typeof RouterLink
|
||||||
|
const useBuiltinLink = builtinRouterLink && typeof builtinRouterLink !== 'string' ? builtinRouterLink.useLink : undefined
|
||||||
|
|
||||||
|
const link = useBuiltinLink?.({
|
||||||
|
...props,
|
||||||
|
to: to.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasTarget = computed(() => props.target && props.target !== '_self')
|
||||||
|
|
||||||
|
// Resolving link type
|
||||||
|
const isExternal = computed<boolean>(() => {
|
||||||
|
// External prop is explicitly set
|
||||||
|
if (props.external) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// When `target` prop is set, link is external
|
||||||
|
if (hasTarget.value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// When `to` is a route object then it's an internal link
|
||||||
|
if (typeof to.value === 'object') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.value === '' || isAbsoluteUrl.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
hasTarget,
|
||||||
|
isAbsoluteUrl,
|
||||||
|
isExternal,
|
||||||
|
//
|
||||||
|
href,
|
||||||
|
isActive: link?.isActive ?? computed(() => to.value === router.currentRoute.value.path),
|
||||||
|
isExactActive: link?.isExactActive ?? computed(() => to.value === router.currentRoute.value.path),
|
||||||
|
route: link?.route ?? computed(() => router.resolve(to.value)),
|
||||||
|
async navigate () {
|
||||||
|
await navigateTo(href.value, { replace: props.replace, external: props.external })
|
||||||
|
},
|
||||||
|
} satisfies ReturnType<typeof useLink> & {
|
||||||
|
to: ComputedRef<string | RouteLocationRaw>
|
||||||
|
hasTarget: ComputedRef<boolean | null | undefined>
|
||||||
|
isAbsoluteUrl: ComputedRef<boolean>
|
||||||
|
isExternal: ComputedRef<boolean>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
name: componentName,
|
name: componentName,
|
||||||
props: {
|
props: {
|
||||||
@ -208,43 +281,11 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
useLink: useNuxtLink,
|
||||||
setup (props, { slots }) {
|
setup (props, { slots }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const config = useRuntimeConfig()
|
|
||||||
|
|
||||||
// Resolving `to` value from `to` and `href` props
|
const { to, href, navigate, isExternal, hasTarget, isAbsoluteUrl } = useNuxtLink(props)
|
||||||
const to: ComputedRef<string | RouteLocationRaw> = computed(() => {
|
|
||||||
checkPropConflicts(props, 'to', 'href')
|
|
||||||
|
|
||||||
const path = props.to || props.href || '' // Defaults to empty string (won't render any `href` attribute)
|
|
||||||
|
|
||||||
return resolveTrailingSlashBehavior(path, router.resolve)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Lazily check whether to.value has a protocol
|
|
||||||
const isAbsoluteUrl = computed(() => typeof to.value === 'string' && hasProtocol(to.value, { acceptRelative: true }))
|
|
||||||
|
|
||||||
const hasTarget = computed(() => props.target && props.target !== '_self')
|
|
||||||
|
|
||||||
// Resolving link type
|
|
||||||
const isExternal = computed<boolean>(() => {
|
|
||||||
// External prop is explicitly set
|
|
||||||
if (props.external) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// When `target` prop is set, link is external
|
|
||||||
if (hasTarget.value) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// When `to` is a route object then it's an internal link
|
|
||||||
if (typeof to.value === 'object') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return to.value === '' || isAbsoluteUrl.value
|
|
||||||
})
|
|
||||||
|
|
||||||
// Prefetching
|
// Prefetching
|
||||||
const prefetched = ref(false)
|
const prefetched = ref(false)
|
||||||
@ -324,14 +365,6 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolves `to` value if it's a route location object
|
|
||||||
// converts `""` to `null` to prevent the attribute from being added as empty (`href=""`)
|
|
||||||
const href = typeof to.value === 'object'
|
|
||||||
? router.resolve(to.value)?.href ?? null
|
|
||||||
: (to.value && !props.external && !isAbsoluteUrl.value)
|
|
||||||
? resolveTrailingSlashBehavior(joinURL(config.app.baseURL, to.value), router.resolve) as string
|
|
||||||
: to.value || null
|
|
||||||
|
|
||||||
// Resolves `target` value
|
// Resolves `target` value
|
||||||
const target = props.target || null
|
const target = props.target || null
|
||||||
|
|
||||||
@ -354,15 +387,13 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigate = () => navigateTo(href, { replace: props.replace, external: props.external })
|
|
||||||
|
|
||||||
return slots.default({
|
return slots.default({
|
||||||
href,
|
href: href.value,
|
||||||
navigate,
|
navigate,
|
||||||
get route () {
|
get route () {
|
||||||
if (!href) { return undefined }
|
if (!href.value) { return undefined }
|
||||||
|
|
||||||
const url = parseURL(href)
|
const url = parseURL(href.value)
|
||||||
return {
|
return {
|
||||||
path: url.pathname,
|
path: url.pathname,
|
||||||
fullPath: url.pathname,
|
fullPath: url.pathname,
|
||||||
@ -373,7 +404,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
matched: [],
|
matched: [],
|
||||||
redirectedFrom: undefined,
|
redirectedFrom: undefined,
|
||||||
meta: {},
|
meta: {},
|
||||||
href,
|
href: href.value,
|
||||||
} satisfies RouteLocation & { href: string }
|
} satisfies RouteLocation & { href: string }
|
||||||
},
|
},
|
||||||
rel,
|
rel,
|
||||||
@ -384,7 +415,8 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return h('a', { ref: el, href, rel, target }, slots.default?.())
|
// converts `""` to `null` to prevent the attribute from being added as empty (`href=""`)
|
||||||
|
return h('a', { ref: el, href: href.value || null, rel, target }, slots.default?.())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}) as unknown as DefineComponent<NuxtLinkProps>
|
}) as unknown as DefineComponent<NuxtLinkProps>
|
||||||
|
48
packages/nuxt/src/app/components/nuxt-route-announcer.ts
Normal file
48
packages/nuxt/src/app/components/nuxt-route-announcer.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { defineComponent, h } from 'vue'
|
||||||
|
import type { Politeness } from '#app/composables/route-announcer'
|
||||||
|
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'NuxtRouteAnnouncer',
|
||||||
|
props: {
|
||||||
|
atomic: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
politeness: {
|
||||||
|
type: String as () => Politeness,
|
||||||
|
default: 'polite',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup (props, { slots, expose }) {
|
||||||
|
const { set, polite, assertive, message, politeness } = useRouteAnnouncer({ politeness: props.politeness })
|
||||||
|
|
||||||
|
expose({
|
||||||
|
set, polite, assertive, message, politeness,
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => h('span', {
|
||||||
|
class: 'nuxt-route-announcer',
|
||||||
|
style: {
|
||||||
|
position: 'absolute',
|
||||||
|
},
|
||||||
|
}, h('span', {
|
||||||
|
'role': 'alert',
|
||||||
|
'aria-live': politeness.value,
|
||||||
|
'aria-atomic': props.atomic,
|
||||||
|
'style': {
|
||||||
|
'border': '0',
|
||||||
|
'clip': 'rect(0 0 0 0)',
|
||||||
|
'clip-path': 'inset(50%)',
|
||||||
|
'height': '1px',
|
||||||
|
'width': '1px',
|
||||||
|
'overflow': 'hidden',
|
||||||
|
'position': 'absolute',
|
||||||
|
'white-space': 'nowrap',
|
||||||
|
'word-wrap': 'normal',
|
||||||
|
'margin': '-1px',
|
||||||
|
'padding': '0',
|
||||||
|
},
|
||||||
|
}, slots.default ? slots.default({ message: message.value }) : message.value))
|
||||||
|
},
|
||||||
|
})
|
1
packages/nuxt/src/app/components/welcome.vue
Symbolic link
1
packages/nuxt/src/app/components/welcome.vue
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../ui-templates/dist/templates/welcome.vue
|
@ -110,6 +110,9 @@ export interface AsyncDataExecuteOptions {
|
|||||||
|
|
||||||
export interface _AsyncData<DataT, ErrorT> {
|
export interface _AsyncData<DataT, ErrorT> {
|
||||||
data: Ref<DataT>
|
data: Ref<DataT>
|
||||||
|
/**
|
||||||
|
* @deprecated Use `status` instead. This may be removed in a future major version.
|
||||||
|
*/
|
||||||
pending: Ref<boolean>
|
pending: Ref<boolean>
|
||||||
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
@ -375,7 +378,9 @@ 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 (hasScope) {
|
if (instance) {
|
||||||
|
onUnmounted(unsub)
|
||||||
|
} else if (hasScope) {
|
||||||
onScopeDispose(unsub)
|
onScopeDispose(unsub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -384,7 +389,9 @@ export function useAsyncData<
|
|||||||
await asyncData.refresh()
|
await asyncData.refresh()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (hasScope) {
|
if (instance) {
|
||||||
|
onUnmounted(off)
|
||||||
|
} else if (hasScope) {
|
||||||
onScopeDispose(off)
|
onScopeDispose(off)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
89
packages/nuxt/src/app/composables/route-announcer.ts
Normal file
89
packages/nuxt/src/app/composables/route-announcer.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { getCurrentScope, onScopeDispose, ref } from 'vue'
|
||||||
|
import { injectHead } from '@unhead/vue'
|
||||||
|
import { useNuxtApp } from '#app'
|
||||||
|
|
||||||
|
export type Politeness = 'assertive' | 'polite' | 'off'
|
||||||
|
|
||||||
|
export type NuxtRouteAnnouncerOpts = {
|
||||||
|
/** @default 'polite' */
|
||||||
|
politeness?: Politeness
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RouteAnnouncer = {
|
||||||
|
message: Ref<string>
|
||||||
|
politeness: Ref<Politeness>
|
||||||
|
set: (message: string, politeness: Politeness) => void
|
||||||
|
polite: (message: string) => void
|
||||||
|
assertive: (message: string) => void
|
||||||
|
_cleanup: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRouteAnnouncer (opts: NuxtRouteAnnouncerOpts = {}) {
|
||||||
|
const message = ref('')
|
||||||
|
const politeness = ref<Politeness>(opts.politeness || 'polite')
|
||||||
|
const activeHead = injectHead()
|
||||||
|
|
||||||
|
function set (messageValue: string = '', politenessSetting: Politeness = 'polite') {
|
||||||
|
message.value = messageValue
|
||||||
|
politeness.value = politenessSetting
|
||||||
|
}
|
||||||
|
|
||||||
|
function polite (message: string) {
|
||||||
|
return set(message, 'polite')
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertive (message: string) {
|
||||||
|
return set(message, 'assertive')
|
||||||
|
}
|
||||||
|
|
||||||
|
function _updateMessageWithPageHeading () {
|
||||||
|
set(document?.title?.trim(), politeness.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cleanup () {
|
||||||
|
activeHead?.hooks?.removeHook('dom:rendered', _updateMessageWithPageHeading)
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateMessageWithPageHeading()
|
||||||
|
|
||||||
|
activeHead?.hooks?.hook('dom:rendered', () => {
|
||||||
|
_updateMessageWithPageHeading()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
_cleanup,
|
||||||
|
message,
|
||||||
|
politeness,
|
||||||
|
set,
|
||||||
|
polite,
|
||||||
|
assertive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* composable to handle the route announcer
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
export function useRouteAnnouncer (opts: Partial<NuxtRouteAnnouncerOpts> = {}): Omit<RouteAnnouncer, '_cleanup'> {
|
||||||
|
const nuxtApp = useNuxtApp()
|
||||||
|
|
||||||
|
// Initialise global route announcer if it doesn't exist already
|
||||||
|
const announcer = nuxtApp._routeAnnouncer = nuxtApp._routeAnnouncer || createRouteAnnouncer(opts)
|
||||||
|
if (opts.politeness !== announcer.politeness.value) {
|
||||||
|
announcer.politeness.value = opts.politeness || 'polite'
|
||||||
|
}
|
||||||
|
if (import.meta.client && getCurrentScope()) {
|
||||||
|
nuxtApp._routeAnnouncerDeps = nuxtApp._routeAnnouncerDeps || 0
|
||||||
|
nuxtApp._routeAnnouncerDeps++
|
||||||
|
onScopeDispose(() => {
|
||||||
|
nuxtApp._routeAnnouncerDeps!--
|
||||||
|
if (nuxtApp._routeAnnouncerDeps === 0) {
|
||||||
|
announcer._cleanup()
|
||||||
|
delete nuxtApp._routeAnnouncer
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return announcer
|
||||||
|
}
|
@ -169,7 +169,8 @@ 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>`,
|
||||||
headers: { location: encodeURI(location) },
|
// do not encode as this would break some modules and some environments
|
||||||
|
headers: { location },
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ function deepAssign (obj: any, newObj: any) {
|
|||||||
for (const key in newObj) {
|
for (const key in newObj) {
|
||||||
const val = newObj[key]
|
const val = newObj[key]
|
||||||
if (val !== null && typeof val === 'object') {
|
if (val !== null && typeof val === 'object') {
|
||||||
obj[key] = obj[key] || {}
|
const defaultVal = Array.isArray(val) ? [] : {}
|
||||||
|
obj[key] = obj[key] || defaultVal
|
||||||
deepAssign(obj[key], val)
|
deepAssign(obj[key], val)
|
||||||
} else {
|
} else {
|
||||||
obj[key] = val
|
obj[key] = val
|
||||||
|
@ -17,6 +17,7 @@ import type { NuxtError } from '../app/composables/error'
|
|||||||
import type { AsyncDataRequestStatus } from '../app/composables/asyncData'
|
import type { AsyncDataRequestStatus } from '../app/composables/asyncData'
|
||||||
import type { NuxtAppManifestMeta } from '../app/composables/manifest'
|
import type { NuxtAppManifestMeta } from '../app/composables/manifest'
|
||||||
import type { LoadingIndicator } from '../app/composables/loading-indicator'
|
import type { LoadingIndicator } from '../app/composables/loading-indicator'
|
||||||
|
import type { RouteAnnouncer } from '../app/composables/route-announcer'
|
||||||
import type { ViewTransition } from './plugins/view-transitions.client'
|
import type { ViewTransition } from './plugins/view-transitions.client'
|
||||||
|
|
||||||
import type { NuxtAppLiterals } from '#app'
|
import type { NuxtAppLiterals } from '#app'
|
||||||
@ -150,6 +151,11 @@ interface _NuxtApp {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_payloadRevivers: Record<string, (data: any) => any>
|
_payloadRevivers: Record<string, (data: any) => any>
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_routeAnnouncer?: RouteAnnouncer
|
||||||
|
/** @internal */
|
||||||
|
_routeAnnouncerDeps?: number
|
||||||
|
|
||||||
// Nuxt injections
|
// Nuxt injections
|
||||||
$config: RuntimeConfig
|
$config: RuntimeConfig
|
||||||
|
|
||||||
@ -227,6 +233,7 @@ export interface CreateOptions {
|
|||||||
globalName?: NuxtApp['globalName']
|
globalName?: NuxtApp['globalName']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 3.0.0 */
|
||||||
export function createNuxtApp (options: CreateOptions) {
|
export function createNuxtApp (options: CreateOptions) {
|
||||||
let hydratingCount = 0
|
let hydratingCount = 0
|
||||||
const nuxtApp: NuxtApp = {
|
const nuxtApp: NuxtApp = {
|
||||||
@ -247,7 +254,12 @@ export function createNuxtApp (options: CreateOptions) {
|
|||||||
static: {
|
static: {
|
||||||
data: {},
|
data: {},
|
||||||
},
|
},
|
||||||
runWithContext: (fn: any) => nuxtApp._scope.run(() => callWithNuxt(nuxtApp, fn)),
|
runWithContext (fn: any) {
|
||||||
|
if (nuxtApp._scope.active) {
|
||||||
|
return nuxtApp._scope.run(() => callWithNuxt(nuxtApp, fn))
|
||||||
|
}
|
||||||
|
return callWithNuxt(nuxtApp, fn)
|
||||||
|
},
|
||||||
isHydrating: import.meta.client,
|
isHydrating: import.meta.client,
|
||||||
deferHydration () {
|
deferHydration () {
|
||||||
if (!nuxtApp.isHydrating) { return () => {} }
|
if (!nuxtApp.isHydrating) { return () => {} }
|
||||||
@ -343,6 +355,7 @@ export function createNuxtApp (options: CreateOptions) {
|
|||||||
return nuxtApp
|
return nuxtApp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 3.0.0 */
|
||||||
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlugin<any>) {
|
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlugin<any>) {
|
||||||
if (plugin.hooks) {
|
if (plugin.hooks) {
|
||||||
nuxtApp.hooks.addHooks(plugin.hooks)
|
nuxtApp.hooks.addHooks(plugin.hooks)
|
||||||
@ -357,6 +370,7 @@ export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlug
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 3.0.0 */
|
||||||
export async function applyPlugins (nuxtApp: NuxtApp, plugins: Array<Plugin & ObjectPlugin<any>>) {
|
export async function applyPlugins (nuxtApp: NuxtApp, plugins: Array<Plugin & ObjectPlugin<any>>) {
|
||||||
const resolvedPlugins: string[] = []
|
const resolvedPlugins: string[] = []
|
||||||
const unresolvedPlugins: [Set<string>, Plugin & ObjectPlugin<any>][] = []
|
const unresolvedPlugins: [Set<string>, Plugin & ObjectPlugin<any>][] = []
|
||||||
@ -407,6 +421,7 @@ export async function applyPlugins (nuxtApp: NuxtApp, plugins: Array<Plugin & Ob
|
|||||||
if (errors.length) { throw errors[0] }
|
if (errors.length) { throw errors[0] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 3.0.0 */
|
||||||
/* @__NO_SIDE_EFFECTS__ */
|
/* @__NO_SIDE_EFFECTS__ */
|
||||||
export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPlugin<T>): Plugin<T> & ObjectPlugin<T> {
|
export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPlugin<T>): Plugin<T> & ObjectPlugin<T> {
|
||||||
if (typeof plugin === 'function') { return plugin }
|
if (typeof plugin === 'function') { return plugin }
|
||||||
@ -419,14 +434,16 @@ export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plu
|
|||||||
/* @__NO_SIDE_EFFECTS__ */
|
/* @__NO_SIDE_EFFECTS__ */
|
||||||
export const definePayloadPlugin = defineNuxtPlugin
|
export const definePayloadPlugin = defineNuxtPlugin
|
||||||
|
|
||||||
|
/** @since 3.0.0 */
|
||||||
export function isNuxtPlugin (plugin: unknown) {
|
export function isNuxtPlugin (plugin: unknown) {
|
||||||
return typeof plugin === 'function' && NuxtPluginIndicator in plugin
|
return typeof plugin === 'function' && NuxtPluginIndicator in plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that the setup function passed in has access to the Nuxt instance via `useNuxt`.
|
* Ensures that the setup function passed in has access to the Nuxt instance via `useNuxtApp`.
|
||||||
* @param nuxt A Nuxt instance
|
* @param nuxt A Nuxt instance
|
||||||
* @param setup The function to call
|
* @param setup The function to call
|
||||||
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
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()
|
||||||
@ -444,6 +461,7 @@ export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp |
|
|||||||
* Returns the current Nuxt instance.
|
* Returns the current Nuxt instance.
|
||||||
*
|
*
|
||||||
* Returns `null` if Nuxt instance is unavailable.
|
* Returns `null` if Nuxt instance is unavailable.
|
||||||
|
* @since 3.10.0
|
||||||
*/
|
*/
|
||||||
export function tryUseNuxtApp (): NuxtApp | null {
|
export function tryUseNuxtApp (): NuxtApp | null {
|
||||||
let nuxtAppInstance
|
let nuxtAppInstance
|
||||||
@ -461,6 +479,7 @@ export function tryUseNuxtApp (): NuxtApp | null {
|
|||||||
* Returns the current Nuxt instance.
|
* Returns the current Nuxt instance.
|
||||||
*
|
*
|
||||||
* Throws an error if Nuxt instance is unavailable.
|
* Throws an error if Nuxt instance is unavailable.
|
||||||
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
export function useNuxtApp (): NuxtApp {
|
export function useNuxtApp (): NuxtApp {
|
||||||
const nuxtAppInstance = tryUseNuxtApp()
|
const nuxtAppInstance = tryUseNuxtApp()
|
||||||
@ -476,6 +495,7 @@ export function useNuxtApp (): NuxtApp {
|
|||||||
return nuxtAppInstance
|
return nuxtAppInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 3.0.0 */
|
||||||
/* @__NO_SIDE_EFFECTS__ */
|
/* @__NO_SIDE_EFFECTS__ */
|
||||||
export function useRuntimeConfig (_event?: H3Event<EventHandlerRequest>): RuntimeConfig {
|
export function useRuntimeConfig (_event?: H3Event<EventHandlerRequest>): RuntimeConfig {
|
||||||
return useNuxtApp().$config
|
return useNuxtApp().$config
|
||||||
@ -485,6 +505,7 @@ function defineGetter<K extends string | number | symbol, V> (obj: Record<K, V>,
|
|||||||
Object.defineProperty(obj, key, { get: () => val })
|
Object.defineProperty(obj, key, { get: () => val })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 3.0.0 */
|
||||||
export function defineAppConfig<C extends AppConfigInput> (config: C): C {
|
export function defineAppConfig<C extends AppConfigInput> (config: C): C {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@ -82,18 +82,13 @@ export const islandsTransform = createUnplugin((options: ServerOnlyComponentTran
|
|||||||
const { attributes, children, loc } = node
|
const { attributes, children, loc } = node
|
||||||
|
|
||||||
const slotName = attributes.name ?? 'default'
|
const slotName = attributes.name ?? 'default'
|
||||||
let vfor: string | undefined
|
|
||||||
if (attributes['v-for']) {
|
|
||||||
vfor = attributes['v-for']
|
|
||||||
}
|
|
||||||
delete attributes['v-for']
|
|
||||||
|
|
||||||
if (attributes.name) { delete attributes.name }
|
if (attributes.name) { delete attributes.name }
|
||||||
if (attributes['v-bind']) {
|
if (attributes['v-bind']) {
|
||||||
attributes._bind = extractAttributes(attributes, ['v-bind'])['v-bind']
|
attributes._bind = extractAttributes(attributes, ['v-bind'])['v-bind']
|
||||||
}
|
}
|
||||||
const teleportAttributes = extractAttributes(attributes, ['v-if', 'v-else-if', 'v-else'])
|
const teleportAttributes = extractAttributes(attributes, ['v-if', 'v-else-if', 'v-else'])
|
||||||
const bindings = getPropsToString(attributes, vfor?.split(' in ').map((v: string) => v.trim()) as [string, string])
|
const bindings = getPropsToString(attributes)
|
||||||
// add the wrapper
|
// add the wrapper
|
||||||
s.appendLeft(startingIndex + loc[0].start, `<NuxtTeleportSsrSlot${attributeToString(teleportAttributes)} name="${slotName}" :props="${bindings}">`)
|
s.appendLeft(startingIndex + loc[0].start, `<NuxtTeleportSsrSlot${attributeToString(teleportAttributes)} name="${slotName}" :props="${bindings}">`)
|
||||||
|
|
||||||
@ -101,7 +96,7 @@ export const islandsTransform = createUnplugin((options: ServerOnlyComponentTran
|
|||||||
// pass slot fallback to NuxtTeleportSsrSlot fallback
|
// pass slot fallback to NuxtTeleportSsrSlot fallback
|
||||||
const attrString = attributeToString(attributes)
|
const attrString = attributeToString(attributes)
|
||||||
const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(/:?key="[^"]"/g, '')
|
const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(/:?key="[^"]"/g, '')
|
||||||
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[1].end, `<slot${attrString.replaceAll(EXTRACTED_ATTRS_RE, '')}/><template #fallback>${vfor ? wrapWithVForDiv(slice, vfor) : slice}</template>`)
|
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[1].end, `<slot${attrString.replaceAll(EXTRACTED_ATTRS_RE, '')}/><template #fallback>${attributes['v-for'] ? wrapWithVForDiv(slice, attributes['v-for']) : slice}</template>`)
|
||||||
} else {
|
} else {
|
||||||
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[0].end, code.slice(startingIndex + loc[0].start, startingIndex + loc[0].end).replaceAll(EXTRACTED_ATTRS_RE, ''))
|
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[0].end, code.slice(startingIndex + loc[0].start, startingIndex + loc[0].end).replaceAll(EXTRACTED_ATTRS_RE, ''))
|
||||||
}
|
}
|
||||||
@ -164,9 +159,10 @@ function isBinding (attr: string): boolean {
|
|||||||
return attr.startsWith(':')
|
return attr.startsWith(':')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPropsToString (bindings: Record<string, string>, vfor?: [string, string]): string {
|
function getPropsToString (bindings: Record<string, string>): string {
|
||||||
|
const vfor = bindings['v-for']?.split(' in ').map((v: string) => v.trim()) as [string, string] | undefined
|
||||||
if (Object.keys(bindings).length === 0) { return 'undefined' }
|
if (Object.keys(bindings).length === 0) { return 'undefined' }
|
||||||
const content = Object.entries(bindings).filter(b => b[0] && b[0] !== '_bind').map(([name, value]) => isBinding(name) ? `[\`${name.slice(1)}\`]: ${value}` : `[\`${name}\`]: \`${value}\``).join(',')
|
const content = Object.entries(bindings).filter(b => b[0] && (b[0] !== '_bind' && b[0] !== 'v-for')).map(([name, value]) => isBinding(name) ? `[\`${name.slice(1)}\`]: ${value}` : `[\`${name}\`]: \`${value}\``).join(',')
|
||||||
const data = bindings._bind ? `mergeProps(${bindings._bind}, { ${content} })` : `{ ${content} }`
|
const data = bindings._bind ? `mergeProps(${bindings._bind}, { ${content} })` : `{ ${content} }`
|
||||||
if (!vfor) {
|
if (!vfor) {
|
||||||
return `[${data}]`
|
return `[${data}]`
|
||||||
|
@ -26,6 +26,9 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
|||||||
const scannedPaths: string[] = []
|
const scannedPaths: string[] = []
|
||||||
|
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
|
if (dir.enabled === false) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// A map from resolved path to component name (used for making duplicate warning message)
|
// A map from resolved path to component name (used for making duplicate warning message)
|
||||||
const resolvedNames = new Map<string, string>()
|
const resolvedNames = new Map<string, string>()
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { promises as fsp, mkdirSync, writeFileSync } from 'node:fs'
|
import { promises as fsp, mkdirSync, writeFileSync } from 'node:fs'
|
||||||
import { dirname, join, relative, resolve } from 'pathe'
|
import { dirname, join, relative, resolve } from 'pathe'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import { compileTemplate, findPath, logger, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath, templateUtils, tryResolveModule } from '@nuxt/kit'
|
import { compileTemplate as _compileTemplate, findPath, logger, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath, templateUtils } from '@nuxt/kit'
|
||||||
import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from 'nuxt/schema'
|
import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from 'nuxt/schema'
|
||||||
|
|
||||||
import * as defaultTemplates from './templates'
|
import * as defaultTemplates from './templates'
|
||||||
@ -20,6 +20,12 @@ export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp
|
|||||||
} as unknown as NuxtApp) as NuxtApp
|
} as unknown as NuxtApp) as NuxtApp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const postTemplates = [
|
||||||
|
defaultTemplates.clientPluginTemplate.filename,
|
||||||
|
defaultTemplates.serverPluginTemplate.filename,
|
||||||
|
defaultTemplates.pluginsDeclaration.filename,
|
||||||
|
]
|
||||||
|
|
||||||
export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?: (template: ResolvedNuxtTemplate<any>) => boolean } = {}) {
|
export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?: (template: ResolvedNuxtTemplate<any>) => boolean } = {}) {
|
||||||
// Resolve app
|
// Resolve app
|
||||||
await resolveApp(nuxt, app)
|
await resolveApp(nuxt, app)
|
||||||
@ -33,64 +39,98 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
|||||||
// Normalize templates
|
// Normalize templates
|
||||||
app.templates = app.templates.map(tmpl => normalizeTemplate(tmpl))
|
app.templates = app.templates.map(tmpl => normalizeTemplate(tmpl))
|
||||||
|
|
||||||
|
// compile plugins first as they are needed within the nuxt.vfs
|
||||||
|
// in order to annotate templated plugins
|
||||||
|
const filteredTemplates: Record<'pre' | 'post', Array<ResolvedNuxtTemplate<any>>> = {
|
||||||
|
pre: [],
|
||||||
|
post: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const template of app.templates as Array<ResolvedNuxtTemplate<any>>) {
|
||||||
|
if (options.filter && !options.filter(template)) { continue }
|
||||||
|
const key = template.filename && postTemplates.includes(template.filename) ? 'post' : 'pre'
|
||||||
|
filteredTemplates[key].push(template)
|
||||||
|
}
|
||||||
|
|
||||||
// Compile templates into vfs
|
// Compile templates into vfs
|
||||||
// TODO: remove utils in v4
|
// TODO: remove utils in v4
|
||||||
const templateContext = { utils: templateUtils, nuxt, app }
|
const templateContext = { utils: templateUtils, nuxt, app }
|
||||||
const filteredTemplates = (app.templates as Array<ResolvedNuxtTemplate<any>>)
|
const compileTemplate = nuxt.options.experimental.compileTemplate ? _compileTemplate : futureCompileTemplate
|
||||||
.filter(template => !options.filter || options.filter(template))
|
|
||||||
|
|
||||||
const writes: Array<() => void> = []
|
const writes: Array<() => void> = []
|
||||||
await Promise.allSettled(filteredTemplates
|
const changedTemplates: Array<ResolvedNuxtTemplate<any>> = []
|
||||||
.map(async (template) => {
|
|
||||||
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!)
|
async function processTemplate (template: ResolvedNuxtTemplate) {
|
||||||
const mark = performance.mark(fullPath)
|
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!)
|
||||||
const oldContents = nuxt.vfs[fullPath]
|
const mark = performance.mark(fullPath)
|
||||||
const contents = await compileTemplate(template, templateContext).catch((e) => {
|
const oldContents = nuxt.vfs[fullPath]
|
||||||
logger.error(`Could not compile template \`${template.filename}\`.`)
|
const contents = await compileTemplate(template, templateContext).catch((e) => {
|
||||||
logger.error(e)
|
logger.error(`Could not compile template \`${template.filename}\`.`)
|
||||||
throw e
|
logger.error(e)
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
|
||||||
|
template.modified = oldContents !== contents
|
||||||
|
if (template.modified) {
|
||||||
|
nuxt.vfs[fullPath] = contents
|
||||||
|
|
||||||
|
const aliasPath = '#build/' + template.filename!.replace(/\.\w+$/, '')
|
||||||
|
nuxt.vfs[aliasPath] = contents
|
||||||
|
|
||||||
|
// In case a non-normalized absolute path is called for on Windows
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
nuxt.vfs[fullPath.replace(/\//g, '\\')] = contents
|
||||||
|
}
|
||||||
|
|
||||||
|
changedTemplates.push(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
const perf = performance.measure(fullPath, mark?.name) // TODO: remove when Node 14 reaches EOL
|
||||||
|
const setupTime = perf ? Math.round((perf.duration * 100)) / 100 : 0 // TODO: remove when Node 14 reaches EOL
|
||||||
|
|
||||||
|
if (nuxt.options.debug || setupTime > 500) {
|
||||||
|
logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.modified && template.write) {
|
||||||
|
writes.push(() => {
|
||||||
|
mkdirSync(dirname(fullPath), { recursive: true })
|
||||||
|
writeFileSync(fullPath, contents, 'utf8')
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template.modified = oldContents !== contents
|
await Promise.allSettled(filteredTemplates.pre.map(processTemplate))
|
||||||
if (template.modified) {
|
await Promise.allSettled(filteredTemplates.post.map(processTemplate))
|
||||||
nuxt.vfs[fullPath] = contents
|
|
||||||
|
|
||||||
const aliasPath = '#build/' + template.filename!.replace(/\.\w+$/, '')
|
|
||||||
nuxt.vfs[aliasPath] = contents
|
|
||||||
|
|
||||||
// In case a non-normalized absolute path is called for on Windows
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
nuxt.vfs[fullPath.replace(/\//g, '\\')] = contents
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const perf = performance.measure(fullPath, mark?.name) // TODO: remove when Node 14 reaches EOL
|
|
||||||
const setupTime = perf ? Math.round((perf.duration * 100)) / 100 : 0 // TODO: remove when Node 14 reaches EOL
|
|
||||||
|
|
||||||
if (nuxt.options.debug || setupTime > 500) {
|
|
||||||
logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (template.modified && template.write) {
|
|
||||||
writes.push(() => {
|
|
||||||
mkdirSync(dirname(fullPath), { recursive: true })
|
|
||||||
writeFileSync(fullPath, contents, 'utf8')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Write template files in single synchronous step to avoid (possible) additional
|
// Write template files in single synchronous step to avoid (possible) additional
|
||||||
// runtime overhead of cascading HMRs from vite/webpack
|
// runtime overhead of cascading HMRs from vite/webpack
|
||||||
for (const write of writes) { write() }
|
for (const write of writes) { write() }
|
||||||
|
|
||||||
const changedTemplates = filteredTemplates.filter(t => t.modified)
|
|
||||||
|
|
||||||
if (changedTemplates.length) {
|
if (changedTemplates.length) {
|
||||||
await nuxt.callHook('app:templatesGenerated', app, changedTemplates, options)
|
await nuxt.callHook('app:templatesGenerated', app, changedTemplates, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
async function futureCompileTemplate<T> (template: NuxtTemplate<T>, ctx: { nuxt: Nuxt, app: NuxtApp, utils?: unknown }) {
|
||||||
|
delete ctx.utils
|
||||||
|
|
||||||
|
if (template.src) {
|
||||||
|
try {
|
||||||
|
return await fsp.readFile(template.src, 'utf-8')
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`[nuxt] Error reading template from \`${template.src}\``)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (template.getContents) {
|
||||||
|
return template.getContents({ ...ctx, options: template.options! })
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('[nuxt] Invalid template. Templates must have either `src` or `getContents`: ' + JSON.stringify(template))
|
||||||
|
}
|
||||||
|
|
||||||
export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
||||||
// Resolve main (app.vue)
|
// Resolve main (app.vue)
|
||||||
if (!app.mainComponent) {
|
if (!app.mainComponent) {
|
||||||
@ -102,7 +142,7 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!app.mainComponent) {
|
if (!app.mainComponent) {
|
||||||
app.mainComponent = (await tryResolveModule('@nuxt/ui-templates/templates/welcome.vue', nuxt.options.modulesDir)) ?? '@nuxt/ui-templates/templates/welcome.vue'
|
app.mainComponent = resolve(nuxt.options.appDir, 'components/welcome.vue')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve root component
|
// Resolve root component
|
||||||
|
@ -85,7 +85,7 @@ function createWatcher () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// TODO: consider moving to emit absolute path in 3.8 or 4.0
|
// TODO: consider moving to emit absolute path in 3.8 or 4.0
|
||||||
watcher.on('all', (event, path) => nuxt.callHook('builder:watch', event, normalize(relative(nuxt.options.srcDir, path))))
|
watcher.on('all', (event, path) => nuxt.callHook('builder:watch', event, nuxt.options.experimental.relativeWatchPaths ? normalize(relative(nuxt.options.srcDir, path)) : normalize(path)))
|
||||||
nuxt.hook('close', () => watcher?.close())
|
nuxt.hook('close', () => watcher?.close())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ function createGranularWatcher () {
|
|||||||
path = normalize(path)
|
path = normalize(path)
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
// TODO: consider moving to emit absolute path in 3.8 or 4.0
|
// TODO: consider moving to emit absolute path in 3.8 or 4.0
|
||||||
nuxt.callHook('builder:watch', event, relative(nuxt.options.srcDir, path))
|
nuxt.callHook('builder:watch', event, nuxt.options.experimental.relativeWatchPaths ? relative(nuxt.options.srcDir, path) : path)
|
||||||
}
|
}
|
||||||
if (event === 'unlinkDir' && path in watchers) {
|
if (event === 'unlinkDir' && path in watchers) {
|
||||||
watchers[path]?.close()
|
watchers[path]?.close()
|
||||||
@ -125,7 +125,7 @@ function createGranularWatcher () {
|
|||||||
if (event === 'addDir' && path !== dir && !ignoredDirs.has(path) && !pathsToWatch.includes(path) && !(path in watchers) && !isIgnored(path)) {
|
if (event === 'addDir' && path !== dir && !ignoredDirs.has(path) && !pathsToWatch.includes(path) && !(path in watchers) && !isIgnored(path)) {
|
||||||
watchers[path] = chokidar.watch(path, { ...nuxt.options.watchers.chokidar, ignored: [isIgnored] })
|
watchers[path] = chokidar.watch(path, { ...nuxt.options.watchers.chokidar, ignored: [isIgnored] })
|
||||||
// TODO: consider moving to emit absolute path in 3.8 or 4.0
|
// TODO: consider moving to emit absolute path in 3.8 or 4.0
|
||||||
watchers[path].on('all', (event, p) => nuxt.callHook('builder:watch', event, normalize(relative(nuxt.options.srcDir, p))))
|
watchers[path].on('all', (event, p) => nuxt.callHook('builder:watch', event, nuxt.options.experimental.relativeWatchPaths ? normalize(relative(nuxt.options.srcDir, p)) : normalize(p)))
|
||||||
nuxt.hook('close', () => watchers[path]?.close())
|
nuxt.hook('close', () => watchers[path]?.close())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -159,7 +159,7 @@ async function createParcelWatcher () {
|
|||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
if (isIgnored(event.path)) { continue }
|
if (isIgnored(event.path)) { continue }
|
||||||
// TODO: consider moving to emit absolute path in 3.8 or 4.0
|
// TODO: consider moving to emit absolute path in 3.8 or 4.0
|
||||||
nuxt.callHook('builder:watch', watchEvents[event.type], normalize(relative(nuxt.options.srcDir, event.path)))
|
nuxt.callHook('builder:watch', watchEvents[event.type], nuxt.options.experimental.relativeWatchPaths ? normalize(relative(nuxt.options.srcDir, event.path)) : normalize(event.path))
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
ignore: [
|
ignore: [
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { pathToFileURL } from 'node:url'
|
import { pathToFileURL } from 'node:url'
|
||||||
import { existsSync, promises as fsp, readFileSync } from 'node:fs'
|
import { existsSync, promises as fsp, readFileSync } from 'node:fs'
|
||||||
import { cpus } from 'node:os'
|
import { cpus } from 'node:os'
|
||||||
import { join, normalize, 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 { randomUUID } from 'uncrypto'
|
||||||
import { joinURL, withTrailingSlash } from 'ufo'
|
import { joinURL, withTrailingSlash } from 'ufo'
|
||||||
@ -14,10 +14,10 @@ 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, RuntimeConfig } from 'nuxt/schema'
|
||||||
import { template as defaultSpaLoadingTemplate } from '@nuxt/ui-templates/templates/spa-loading-icon.mjs'
|
|
||||||
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'
|
||||||
|
import { template as defaultSpaLoadingTemplate } from '../../../ui-templates/dist/templates/spa-loading-icon'
|
||||||
import { ImportProtectionPlugin, nuxtImportProtections } from './plugins/import-protection'
|
import { ImportProtectionPlugin, nuxtImportProtections } from './plugins/import-protection'
|
||||||
|
|
||||||
const logLevelMapReverse = {
|
const logLevelMapReverse = {
|
||||||
@ -154,7 +154,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
baseURL: nuxt.options.app.buildAssetsDir,
|
baseURL: nuxt.options.app.buildAssetsDir,
|
||||||
},
|
},
|
||||||
...nuxt.options._layers
|
...nuxt.options._layers
|
||||||
.map(layer => join(layer.config.srcDir, (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.public || 'public'))
|
.map(layer => resolve(layer.config.srcDir, (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.public || 'public'))
|
||||||
.filter(dir => existsSync(dir))
|
.filter(dir => existsSync(dir))
|
||||||
.map(dir => ({ dir })),
|
.map(dir => ({ dir })),
|
||||||
],
|
],
|
||||||
@ -231,7 +231,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
|
|
||||||
// Resolve user-provided paths
|
// Resolve user-provided paths
|
||||||
nitroConfig.srcDir = resolve(nuxt.options.rootDir, nuxt.options.srcDir, nitroConfig.srcDir!)
|
nitroConfig.srcDir = resolve(nuxt.options.rootDir, nuxt.options.srcDir, nitroConfig.srcDir!)
|
||||||
nitroConfig.ignore = [...(nitroConfig.ignore || []), ...resolveIgnorePatterns(nitroConfig.srcDir), `!${join(nuxt.options.buildDir, 'dist/client', nuxt.options.app.buildAssetsDir)}/**/*`]
|
nitroConfig.ignore = [...(nitroConfig.ignore || []), ...resolveIgnorePatterns(nitroConfig.srcDir), `!${join(nuxt.options.buildDir, 'dist/client', nuxt.options.app.buildAssetsDir, '**/*')}`]
|
||||||
|
|
||||||
// Add app manifest handler and prerender configuration
|
// Add app manifest handler and prerender configuration
|
||||||
if (nuxt.options.experimental.appManifest) {
|
if (nuxt.options.experimental.appManifest) {
|
||||||
@ -409,8 +409,9 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
|
|
||||||
// Trigger Nitro reload when SPA loading template changes
|
// Trigger Nitro reload when SPA loading template changes
|
||||||
const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt)
|
const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt)
|
||||||
nuxt.hook('builder:watch', async (_event, path) => {
|
nuxt.hook('builder:watch', async (_event, relativePath) => {
|
||||||
if (normalize(path) === spaLoadingTemplateFilePath) {
|
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||||
|
if (path === spaLoadingTemplateFilePath) {
|
||||||
await nitro.hooks.callHook('rollup:reload')
|
await nitro.hooks.callHook('rollup:reload')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -569,9 +570,9 @@ async function spaLoadingTemplatePath (nuxt: Nuxt) {
|
|||||||
return resolve(nuxt.options.srcDir, nuxt.options.spaLoadingTemplate)
|
return resolve(nuxt.options.srcDir, nuxt.options.spaLoadingTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
const possiblePaths = nuxt.options._layers.map(layer => join(layer.config.srcDir, 'app/spa-loading-template.html'))
|
const possiblePaths = nuxt.options._layers.map(layer => resolve(layer.config.srcDir, layer.config.dir?.app || 'app', 'spa-loading-template.html'))
|
||||||
|
|
||||||
return await findPath(possiblePaths) ?? resolve(nuxt.options.srcDir, 'app/spa-loading-template.html')
|
return await findPath(possiblePaths) ?? resolve(nuxt.options.srcDir, nuxt.options.dir?.app || 'app', 'spa-loading-template.html')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function spaLoadingTemplate (nuxt: Nuxt) {
|
async function spaLoadingTemplate (nuxt: Nuxt) {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { dirname, join, normalize, relative, resolve } from 'pathe'
|
import { dirname, join, normalize, relative, resolve } from 'pathe'
|
||||||
import { createDebugger, createHooks } from 'hookable'
|
import { createDebugger, createHooks } from 'hookable'
|
||||||
|
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, 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 } from 'nuxt/schema'
|
||||||
import type { PackageJson } from 'pkg-types'
|
import type { PackageJson } from 'pkg-types'
|
||||||
@ -275,7 +276,7 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
addComponent({
|
addComponent({
|
||||||
name: 'NuxtWelcome',
|
name: 'NuxtWelcome',
|
||||||
priority: 10, // built-in that we do not expect the user to override
|
priority: 10, // built-in that we do not expect the user to override
|
||||||
filePath: (await tryResolveModule('@nuxt/ui-templates/templates/welcome.vue', nuxt.options.modulesDir))!,
|
filePath: resolve(nuxt.options.appDir, 'components/welcome'),
|
||||||
})
|
})
|
||||||
|
|
||||||
addComponent({
|
addComponent({
|
||||||
@ -326,6 +327,14 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
filePath: resolve(nuxt.options.appDir, 'components/nuxt-loading-indicator'),
|
filePath: resolve(nuxt.options.appDir, 'components/nuxt-loading-indicator'),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Add <NuxtRouteAnnouncer>
|
||||||
|
addComponent({
|
||||||
|
name: 'NuxtRouteAnnouncer',
|
||||||
|
priority: 10, // built-in that we do not expect the user to override
|
||||||
|
filePath: resolve(nuxt.options.appDir, 'components/nuxt-route-announcer'),
|
||||||
|
mode: 'client',
|
||||||
|
})
|
||||||
|
|
||||||
// Add <NuxtClientFallback>
|
// Add <NuxtClientFallback>
|
||||||
if (nuxt.options.experimental.clientFallback) {
|
if (nuxt.options.experimental.clientFallback) {
|
||||||
addComponent({
|
addComponent({
|
||||||
@ -445,6 +454,10 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (Re)initialise ignore handler with resolved ignores from modules
|
||||||
|
nuxt._ignore = ignore(nuxt.options.ignoreOptions)
|
||||||
|
nuxt._ignore.add(resolveIgnorePatterns())
|
||||||
|
|
||||||
await nuxt.callHook('modules:done')
|
await nuxt.callHook('modules:done')
|
||||||
|
|
||||||
if (nuxt.options.experimental.appManifest) {
|
if (nuxt.options.experimental.appManifest) {
|
||||||
@ -478,6 +491,12 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restart Nuxt when new `app/` dir is added
|
||||||
|
if (event === 'addDir' && path === resolve(nuxt.options.srcDir, 'app')) {
|
||||||
|
logger.info(`\`${path}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
|
||||||
|
return nuxt.callHook('restart', { hard: true })
|
||||||
|
}
|
||||||
|
|
||||||
// Core Nuxt files: app.vue, error.vue and app.config.ts
|
// Core Nuxt files: app.vue, error.vue and app.config.ts
|
||||||
const isFileChange = ['add', 'unlink'].includes(event)
|
const isFileChange = ['add', 'unlink'].includes(event)
|
||||||
if (isFileChange && RESTART_RE.test(path)) {
|
if (isFileChange && RESTART_RE.test(path)) {
|
||||||
@ -554,7 +573,6 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
|||||||
options.modulesDir.push(resolve(options.workspaceDir, 'node_modules'))
|
options.modulesDir.push(resolve(options.workspaceDir, 'node_modules'))
|
||||||
options.modulesDir.push(resolve(pkgDir, 'node_modules'))
|
options.modulesDir.push(resolve(pkgDir, 'node_modules'))
|
||||||
options.build.transpile.push(
|
options.build.transpile.push(
|
||||||
'@nuxt/ui-templates', // this exposes vue SFCs
|
|
||||||
'std-env', // we need to statically replace process.env when used in runtime code
|
'std-env', // we need to statically replace process.env when used in runtime code
|
||||||
)
|
)
|
||||||
options.alias['vue-demi'] = resolve(options.appDir, 'compat/vue-demi')
|
options.alias['vue-demi'] = resolve(options.appDir, 'compat/vue-demi')
|
||||||
|
1
packages/nuxt/src/core/runtime/nitro/error-500.d.ts
vendored
Symbolic link
1
packages/nuxt/src/core/runtime/nitro/error-500.d.ts
vendored
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../../ui-templates/dist/templates/error-500.d.ts
|
1
packages/nuxt/src/core/runtime/nitro/error-500.js
Symbolic link
1
packages/nuxt/src/core/runtime/nitro/error-500.js
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../../ui-templates/dist/templates/error-500.js
|
1
packages/nuxt/src/core/runtime/nitro/error-dev.d.ts
vendored
Symbolic link
1
packages/nuxt/src/core/runtime/nitro/error-dev.d.ts
vendored
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../../ui-templates/dist/templates/error-dev.d.ts
|
1
packages/nuxt/src/core/runtime/nitro/error-dev.js
Symbolic link
1
packages/nuxt/src/core/runtime/nitro/error-dev.js
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../../ui-templates/dist/templates/error-dev.js
|
@ -66,9 +66,7 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
|
|||||||
|
|
||||||
// Fallback to static rendered error page
|
// Fallback to static rendered error page
|
||||||
if (!res) {
|
if (!res) {
|
||||||
const { template } = import.meta.dev
|
const { template } = import.meta.dev ? await import('./error-dev') : await import('./error-500')
|
||||||
? await import('@nuxt/ui-templates/templates/error-dev.mjs')
|
|
||||||
: await import('@nuxt/ui-templates/templates/error-500.mjs')
|
|
||||||
if (import.meta.dev) {
|
if (import.meta.dev) {
|
||||||
// TODO: Support `message` in template
|
// TODO: Support `message` in template
|
||||||
(errorObject as any).description = errorObject.message
|
(errorObject as any).description = errorObject.message
|
||||||
|
@ -16,7 +16,7 @@ import destr from 'destr'
|
|||||||
import { getQuery as getURLQuery, joinURL, withoutTrailingSlash } from 'ufo'
|
import { getQuery as getURLQuery, joinURL, withoutTrailingSlash } from 'ufo'
|
||||||
import { renderToString as _renderToString } from 'vue/server-renderer'
|
import { renderToString as _renderToString } from 'vue/server-renderer'
|
||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { renderSSRHead } from '@unhead/ssr'
|
import { propsToString, renderSSRHead } from '@unhead/ssr'
|
||||||
import type { HeadEntryOptions } from '@unhead/schema'
|
import type { HeadEntryOptions } from '@unhead/schema'
|
||||||
import type { Link, Script, Style } from '@unhead/vue'
|
import type { Link, Script, Style } from '@unhead/vue'
|
||||||
import { createServerHead } from '@unhead/vue'
|
import { createServerHead } from '@unhead/vue'
|
||||||
@ -29,7 +29,7 @@ import unheadPlugins from '#internal/unhead-plugins.mjs'
|
|||||||
|
|
||||||
import type { NuxtPayload, NuxtSSRContext } from '#app'
|
import type { NuxtPayload, NuxtSSRContext } from '#app'
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { appHead, appRootId, appRootTag, appTeleportId, appTeleportTag, componentIslands } from '#internal/nuxt.config.mjs'
|
import { appHead, appRootAttrs, appRootTag, appTeleportAttrs, appTeleportTag, componentIslands } from '#internal/nuxt.config.mjs'
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { buildAssetsURL, publicAssetsURL } from '#internal/nuxt/paths'
|
import { buildAssetsURL, publicAssetsURL } from '#internal/nuxt/paths'
|
||||||
|
|
||||||
@ -77,7 +77,6 @@ export interface NuxtIslandContext {
|
|||||||
export interface NuxtIslandResponse {
|
export interface NuxtIslandResponse {
|
||||||
id?: string
|
id?: string
|
||||||
html: string
|
html: string
|
||||||
state: Record<string, any>
|
|
||||||
head: {
|
head: {
|
||||||
link: (Record<string, string>)[]
|
link: (Record<string, string>)[]
|
||||||
style: ({ innerHTML: string, key: string })[]
|
style: ({ innerHTML: string, key: string })[]
|
||||||
@ -233,15 +232,15 @@ async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
const HAS_APP_TELEPORTS = !!(appTeleportTag && appTeleportId)
|
const HAS_APP_TELEPORTS = !!(appTeleportTag && appTeleportAttrs.id)
|
||||||
const APP_TELEPORT_OPEN_TAG = HAS_APP_TELEPORTS ? `<${appTeleportTag} id="${appTeleportId}">` : ''
|
const APP_TELEPORT_OPEN_TAG = HAS_APP_TELEPORTS ? `<${appTeleportTag}${propsToString(appTeleportAttrs)}>` : ''
|
||||||
const APP_TELEPORT_CLOSE_TAG = HAS_APP_TELEPORTS ? `</${appTeleportTag}>` : ''
|
const APP_TELEPORT_CLOSE_TAG = HAS_APP_TELEPORTS ? `</${appTeleportTag}>` : ''
|
||||||
|
|
||||||
const APP_ROOT_OPEN_TAG = `<${appRootTag}${appRootId ? ` id="${appRootId}"` : ''}>`
|
const APP_ROOT_OPEN_TAG = `<${appRootTag}${propsToString(appRootAttrs)}>`
|
||||||
const APP_ROOT_CLOSE_TAG = `</${appRootTag}>`
|
const APP_ROOT_CLOSE_TAG = `</${appRootTag}>`
|
||||||
|
|
||||||
const PAYLOAD_URL_RE = process.env.NUXT_JSON_PAYLOADS ? /\/_payload.json(\?.*)?$/ : /\/_payload.js(\?.*)?$/
|
const PAYLOAD_URL_RE = process.env.NUXT_JSON_PAYLOADS ? /\/_payload.json(\?.*)?$/ : /\/_payload.js(\?.*)?$/
|
||||||
const ROOT_NODE_REGEX = new RegExp(`^${APP_ROOT_OPEN_TAG}([\\s\\S]*)${APP_ROOT_CLOSE_TAG}$`)
|
const ROOT_NODE_REGEX = new RegExp(`^<${appRootTag}[^>]*>([\\s\\S]*)<\\/${appRootTag}>$`)
|
||||||
|
|
||||||
const PRERENDER_NO_SSR_ROUTES = new Set(['/index.html', '/200.html', '/404.html'])
|
const PRERENDER_NO_SSR_ROUTES = new Set(['/index.html', '/200.html', '/404.html'])
|
||||||
|
|
||||||
@ -471,7 +470,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
bodyPrepend: normalizeChunks([bodyTagsOpen, ssrContext.teleports?.body]),
|
bodyPrepend: normalizeChunks([bodyTagsOpen, ssrContext.teleports?.body]),
|
||||||
body: [
|
body: [
|
||||||
componentIslands ? replaceIslandTeleports(ssrContext, _rendered.html) : _rendered.html,
|
componentIslands ? replaceIslandTeleports(ssrContext, _rendered.html) : _rendered.html,
|
||||||
APP_TELEPORT_OPEN_TAG + (HAS_APP_TELEPORTS ? joinTags([ssrContext.teleports?.[`#${appTeleportId}`]]) : '') + APP_TELEPORT_CLOSE_TAG,
|
APP_TELEPORT_OPEN_TAG + (HAS_APP_TELEPORTS ? joinTags([ssrContext.teleports?.[`#${appTeleportAttrs.id}`]]) : '') + APP_TELEPORT_CLOSE_TAG,
|
||||||
],
|
],
|
||||||
bodyAppend: [bodyTags],
|
bodyAppend: [bodyTags],
|
||||||
}
|
}
|
||||||
@ -496,7 +495,6 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
id: islandContext.id,
|
id: islandContext.id,
|
||||||
head: islandHead,
|
head: islandHead,
|
||||||
html: getServerComponentHTML(htmlContext.body),
|
html: getServerComponentHTML(htmlContext.body),
|
||||||
state: ssrContext.payload.state,
|
|
||||||
components: getClientIslandResponse(ssrContext),
|
components: getClientIslandResponse(ssrContext),
|
||||||
slots: getSlotIslandResponse(ssrContext),
|
slots: getSlotIslandResponse(ssrContext),
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export function hasSuffix (path: string, suffix: string) {
|
|||||||
|
|
||||||
export function resolveComponentNameSegments (fileName: string, prefixParts: string[]) {
|
export function resolveComponentNameSegments (fileName: string, prefixParts: string[]) {
|
||||||
/**
|
/**
|
||||||
* Array of fileName parts splitted by case, / or -
|
* Array of fileName parts split by case, / or -
|
||||||
* @example third-component -> ['third', 'component']
|
* @example third-component -> ['third', 'component']
|
||||||
* @example AwesomeComponent -> ['Awesome', 'Component']
|
* @example AwesomeComponent -> ['Awesome', 'Component']
|
||||||
*/
|
*/
|
||||||
|
@ -34,7 +34,7 @@ export default defineNuxtModule({
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const layer of nuxt.options._layers) {
|
for (const layer of nuxt.options._layers) {
|
||||||
const path = await findPath(resolve(layer.config.srcDir, 'app/router.options'))
|
const path = await findPath(resolve(layer.config.srcDir, layer.config.dir?.app || 'app', 'router.options'))
|
||||||
if (path) { context.files.unshift({ path }) }
|
if (path) { context.files.unshift({ path }) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +86,8 @@ export default defineNuxtModule({
|
|||||||
const restartPaths = nuxt.options._layers.flatMap((layer) => {
|
const restartPaths = nuxt.options._layers.flatMap((layer) => {
|
||||||
const pagesDir = (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.pages || 'pages'
|
const pagesDir = (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.pages || 'pages'
|
||||||
return [
|
return [
|
||||||
join(layer.config.srcDir || layer.cwd, 'app/router.options.ts'),
|
resolve(layer.config.srcDir || layer.cwd, layer.config.dir?.app || 'app', 'router.options.ts'),
|
||||||
join(layer.config.srcDir || layer.cwd, pagesDir),
|
resolve(layer.config.srcDir || layer.cwd, pagesDir),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -228,9 +228,9 @@ export default defineNuxtModule({
|
|||||||
const updateTemplatePaths = nuxt.options._layers.flatMap((l) => {
|
const updateTemplatePaths = nuxt.options._layers.flatMap((l) => {
|
||||||
const dir = (l.config.rootDir === nuxt.options.rootDir ? nuxt.options : l.config).dir
|
const dir = (l.config.rootDir === nuxt.options.rootDir ? nuxt.options : l.config).dir
|
||||||
return [
|
return [
|
||||||
join(l.config.srcDir || l.cwd, dir?.pages || 'pages') + '/',
|
resolve(l.config.srcDir || l.cwd, dir?.pages || 'pages') + '/',
|
||||||
join(l.config.srcDir || l.cwd, dir?.layouts || 'layouts') + '/',
|
resolve(l.config.srcDir || l.cwd, dir?.layouts || 'layouts') + '/',
|
||||||
join(l.config.srcDir || l.cwd, dir?.middleware || 'middleware') + '/',
|
resolve(l.config.srcDir || l.cwd, dir?.middleware || 'middleware') + '/',
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ export default defineNuxtModule({
|
|||||||
|
|
||||||
nuxt.hook('app:resolve', (app) => {
|
nuxt.hook('app:resolve', (app) => {
|
||||||
// Add default layout for pages
|
// Add default layout for pages
|
||||||
if (app.mainComponent!.includes('@nuxt/ui-templates')) {
|
if (app.mainComponent === resolve(nuxt.options.appDir, 'components/welcome.vue')) {
|
||||||
app.mainComponent = resolve(runtimeDir, 'app.vue')
|
app.mainComponent = resolve(runtimeDir, 'app.vue')
|
||||||
}
|
}
|
||||||
app.middleware.unshift({
|
app.middleware.unshift({
|
||||||
@ -324,7 +324,7 @@ export default defineNuxtModule({
|
|||||||
}
|
}
|
||||||
|
|
||||||
nuxt.hook('builder:watch', async (event, relativePath) => {
|
nuxt.hook('builder:watch', async (event, relativePath) => {
|
||||||
const path = join(nuxt.options.srcDir, relativePath)
|
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||||
if (!(path in pageToGlobMap)) { return }
|
if (!(path in pageToGlobMap)) { return }
|
||||||
if (event === 'unlink') {
|
if (event === 'unlink') {
|
||||||
delete inlineRules[path]
|
delete inlineRules[path]
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
createWebHistory,
|
createWebHistory,
|
||||||
} from '#vue-router'
|
} from '#vue-router'
|
||||||
import { createError } from 'h3'
|
import { createError } from 'h3'
|
||||||
import { isEqual, isSamePath, withoutBase } from 'ufo'
|
import { isEqual, withoutBase } from 'ufo'
|
||||||
|
|
||||||
import type { PageMeta } from '../composables'
|
import type { PageMeta } from '../composables'
|
||||||
|
|
||||||
@ -139,6 +139,36 @@ const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
|
|||||||
named: {},
|
named: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const error = useError()
|
||||||
|
if (import.meta.client || !nuxtApp.ssrContext?.islandContext) {
|
||||||
|
router.afterEach(async (to, _from, failure) => {
|
||||||
|
delete nuxtApp._processingMiddleware
|
||||||
|
|
||||||
|
if (import.meta.client && !nuxtApp.isHydrating && error.value) {
|
||||||
|
// Clear any existing errors
|
||||||
|
await nuxtApp.runWithContext(clearError)
|
||||||
|
}
|
||||||
|
if (failure) {
|
||||||
|
await nuxtApp.callHook('page:loading:end')
|
||||||
|
}
|
||||||
|
if (import.meta.server && failure?.type === 4 /* ErrorTypes.NAVIGATION_ABORTED */) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (to.matched.length === 0) {
|
||||||
|
await nuxtApp.runWithContext(() => showError(createError({
|
||||||
|
statusCode: 404,
|
||||||
|
fatal: false,
|
||||||
|
statusMessage: `Page not found: ${to.fullPath}`,
|
||||||
|
data: {
|
||||||
|
path: to.fullPath,
|
||||||
|
},
|
||||||
|
})))
|
||||||
|
} else if (import.meta.server && to.redirectedFrom && to.fullPath !== initialURL) {
|
||||||
|
await nuxtApp.runWithContext(() => navigateTo(to.fullPath || '/'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (import.meta.server) {
|
if (import.meta.server) {
|
||||||
await router.push(initialURL)
|
await router.push(initialURL)
|
||||||
@ -228,34 +258,6 @@ const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
|
|||||||
await nuxtApp.callHook('page:loading:end')
|
await nuxtApp.callHook('page:loading:end')
|
||||||
})
|
})
|
||||||
|
|
||||||
const error = useError()
|
|
||||||
router.afterEach(async (to, _from, failure) => {
|
|
||||||
delete nuxtApp._processingMiddleware
|
|
||||||
|
|
||||||
if (import.meta.client && !nuxtApp.isHydrating && error.value) {
|
|
||||||
// Clear any existing errors
|
|
||||||
await nuxtApp.runWithContext(clearError)
|
|
||||||
}
|
|
||||||
if (failure) {
|
|
||||||
await nuxtApp.callHook('page:loading:end')
|
|
||||||
}
|
|
||||||
if (import.meta.server && failure?.type === 4 /* ErrorTypes.NAVIGATION_ABORTED */) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (to.matched.length === 0) {
|
|
||||||
await nuxtApp.runWithContext(() => showError(createError({
|
|
||||||
statusCode: 404,
|
|
||||||
fatal: false,
|
|
||||||
statusMessage: `Page not found: ${to.fullPath}`,
|
|
||||||
data: {
|
|
||||||
path: to.fullPath,
|
|
||||||
},
|
|
||||||
})))
|
|
||||||
} else if (import.meta.server && to.fullPath !== initialURL && (to.redirectedFrom || !isSamePath(to.fullPath, initialURL))) {
|
|
||||||
await nuxtApp.runWithContext(() => navigateTo(to.fullPath || '/'))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
nuxtApp.hooks.hookOnce('app:created', async () => {
|
nuxtApp.hooks.hookOnce('app:created', async () => {
|
||||||
try {
|
try {
|
||||||
// #4920, #4982
|
// #4920, #4982
|
||||||
|
@ -22,6 +22,7 @@ export const wrapInKeepAlive = (props: any, children: any) => {
|
|||||||
return { default: () => import.meta.client && props ? h(KeepAlive, props === true ? {} : props, children) : children }
|
return { default: () => import.meta.client && props ? h(KeepAlive, props === true ? {} : props, children) : children }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 3.9.0 */
|
||||||
export function toArray<T> (value: T | T[]): T[] {
|
export function toArray<T> (value: T | T[]): T[] {
|
||||||
return Array.isArray(value) ? value : [value]
|
return Array.isArray(value) ? value : [value]
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { promises as fsp } from 'node:fs'
|
import { promises as fsp } from 'node:fs'
|
||||||
|
|
||||||
|
/** @since 3.9.0 */
|
||||||
export function toArray<T> (value: T | T[]): T[] {
|
export function toArray<T> (value: T | T[]): T[] {
|
||||||
return Array.isArray(value) ? value : [value]
|
return Array.isArray(value) ? value : [value]
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ describe('resolveApp', () => {
|
|||||||
".vue",
|
".vue",
|
||||||
],
|
],
|
||||||
"layouts": {},
|
"layouts": {},
|
||||||
"mainComponent": "@nuxt/ui-templates/dist/templates/welcome.vue",
|
"mainComponent": "<repoRoot>/packages/nuxt/src/app/components/welcome.vue",
|
||||||
"middleware": [
|
"middleware": [
|
||||||
{
|
{
|
||||||
"global": true,
|
"global": true,
|
||||||
|
@ -86,7 +86,7 @@ const dirs: ComponentsDir[] = [
|
|||||||
transpile: false,
|
transpile: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
const dirUnable = dirs.map((d) => { return { ...d, enabled: false } })
|
||||||
const expectedComponents = [
|
const expectedComponents = [
|
||||||
{
|
{
|
||||||
chunkName: 'components/isle-server',
|
chunkName: 'components/isle-server',
|
||||||
@ -243,3 +243,8 @@ it('components:scanComponents', async () => {
|
|||||||
}
|
}
|
||||||
expect(scannedComponents).deep.eq(expectedComponents)
|
expect(scannedComponents).deep.eq(expectedComponents)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('components:scanComponents:unable', async () => {
|
||||||
|
const scannedComponents = await scanComponents(dirUnable, srcDir)
|
||||||
|
expect(scannedComponents).deep.eq([])
|
||||||
|
})
|
||||||
|
@ -146,7 +146,7 @@ describe('treeshake client only in ssr', () => {
|
|||||||
expect(treeshaken).toContain('const { ButShouldNotBeTreeShaken } = defineAsyncComponent(async () => {')
|
expect(treeshaken).toContain('const { ButShouldNotBeTreeShaken } = defineAsyncComponent(async () => {')
|
||||||
expect(treeshaken).toContain('const [ { Dont, }, That] = defineAsyncComponent(async () => {')
|
expect(treeshaken).toContain('const [ { Dont, }, That] = defineAsyncComponent(async () => {')
|
||||||
|
|
||||||
// treeshake object that has an assignement pattern
|
// treeshake object that has an assignment pattern
|
||||||
expect(treeshaken).toContain('const { woooooo, } = defineAsyncComponent(async () => {')
|
expect(treeshaken).toContain('const { woooooo, } = defineAsyncComponent(async () => {')
|
||||||
expect(treeshaken).not.toContain('const { Deep, assignment: { Pattern = ofComponent } } = defineAsyncComponent(async () => {')
|
expect(treeshaken).not.toContain('const { Deep, assignment: { Pattern = ofComponent } } = defineAsyncComponent(async () => {')
|
||||||
|
|
||||||
|
@ -35,15 +35,16 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/telemetry": "2.5.4",
|
"@nuxt/telemetry": "2.5.4",
|
||||||
|
"@nuxt/ui-templates": "1.3.3",
|
||||||
"@types/file-loader": "5.0.4",
|
"@types/file-loader": "5.0.4",
|
||||||
"@types/pug": "2.0.10",
|
"@types/pug": "2.0.10",
|
||||||
"@types/sass-loader": "8.0.8",
|
"@types/sass-loader": "8.0.8",
|
||||||
"@unhead/schema": "1.9.5",
|
"@unhead/schema": "1.9.8",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
"@vitejs/plugin-vue-jsx": "3.1.0",
|
"@vitejs/plugin-vue-jsx": "3.1.0",
|
||||||
"@vue/compiler-core": "3.4.21",
|
"@vue/compiler-core": "3.4.26",
|
||||||
"@vue/compiler-sfc": "3.4.21",
|
"@vue/compiler-sfc": "3.4.26",
|
||||||
"@vue/language-core": "2.0.13",
|
"@vue/language-core": "2.0.16",
|
||||||
"c12": "1.10.0",
|
"c12": "1.10.0",
|
||||||
"esbuild-loader": "4.1.0",
|
"esbuild-loader": "4.1.0",
|
||||||
"h3": "1.11.1",
|
"h3": "1.11.1",
|
||||||
@ -53,21 +54,20 @@
|
|||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"unctx": "2.3.1",
|
"unctx": "2.3.1",
|
||||||
"unenv": "1.9.0",
|
"unenv": "1.9.0",
|
||||||
"vite": "5.2.8",
|
"vite": "5.2.11",
|
||||||
"vue": "3.4.21",
|
"vue": "3.4.26",
|
||||||
"vue-bundle-renderer": "2.0.0",
|
"vue-bundle-renderer": "2.0.0",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
"vue-router": "4.3.0",
|
"vue-router": "4.3.2",
|
||||||
"webpack": "5.91.0",
|
"webpack": "5.91.0",
|
||||||
"webpack-dev-middleware": "7.2.1"
|
"webpack-dev-middleware": "7.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui-templates": "^1.3.3",
|
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"hookable": "^5.5.3",
|
"hookable": "^5.5.3",
|
||||||
"pathe": "^1.1.2",
|
"pathe": "^1.1.2",
|
||||||
"pkg-types": "^1.0.3",
|
"pkg-types": "^1.1.0",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"std-env": "^3.7.0",
|
"std-env": "^3.7.0",
|
||||||
"ufo": "^1.5.3",
|
"ufo": "^1.5.3",
|
||||||
|
@ -182,9 +182,10 @@ export default defineUntypedSchema({
|
|||||||
/**
|
/**
|
||||||
* Customize Nuxt root element id.
|
* Customize Nuxt root element id.
|
||||||
* @type {string | false}
|
* @type {string | false}
|
||||||
|
* @deprecated Prefer `rootAttrs.id` instead
|
||||||
*/
|
*/
|
||||||
rootId: {
|
rootId: {
|
||||||
$resolve: val => val === false ? false : val || '__nuxt',
|
$resolve: val => val === false ? false : (val || '__nuxt'),
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,6 +195,19 @@ export default defineUntypedSchema({
|
|||||||
$resolve: val => val || 'div',
|
$resolve: val => val || 'div',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customize Nuxt root element id.
|
||||||
|
* @type {typeof import('@unhead/schema').HtmlAttributes}
|
||||||
|
*/
|
||||||
|
rootAttrs: {
|
||||||
|
$resolve: async (val: undefined | null | Record<string, unknown>, get) => {
|
||||||
|
const rootId = await get('app.rootId')
|
||||||
|
return defu(val, {
|
||||||
|
id: rootId === false ? undefined : (rootId || '__nuxt'),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customize Nuxt root element tag.
|
* Customize Nuxt root element tag.
|
||||||
*/
|
*/
|
||||||
@ -204,10 +218,24 @@ export default defineUntypedSchema({
|
|||||||
/**
|
/**
|
||||||
* Customize Nuxt Teleport element id.
|
* Customize Nuxt Teleport element id.
|
||||||
* @type {string | false}
|
* @type {string | false}
|
||||||
|
* @deprecated Prefer `teleportAttrs.id` instead
|
||||||
*/
|
*/
|
||||||
teleportId: {
|
teleportId: {
|
||||||
$resolve: val => val === false ? false : (val || 'teleports'),
|
$resolve: val => val === false ? false : (val || 'teleports'),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customize Nuxt Teleport element attributes.
|
||||||
|
* @type {typeof import('@unhead/schema').HtmlAttributes}
|
||||||
|
*/
|
||||||
|
teleportAttrs: {
|
||||||
|
$resolve: async (val: undefined | null | Record<string, unknown>, get) => {
|
||||||
|
const teleportId = await get('app.teleportId')
|
||||||
|
return defu(val, {
|
||||||
|
id: teleportId === false ? undefined : (teleportId || 'teleports'),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { existsSync } from 'node:fs'
|
||||||
import { defineUntypedSchema } from 'untyped'
|
import { defineUntypedSchema } from 'untyped'
|
||||||
import { join, relative, resolve } from 'pathe'
|
import { join, relative, resolve } from 'pathe'
|
||||||
import { isDebug, isDevelopment, isTest } from 'std-env'
|
import { isDebug, isDevelopment, isTest } from 'std-env'
|
||||||
@ -79,7 +80,7 @@ export default defineUntypedSchema({
|
|||||||
* ------| middleware/
|
* ------| middleware/
|
||||||
* ------| pages/
|
* ------| pages/
|
||||||
* ------| plugins/
|
* ------| plugins/
|
||||||
* ------| static/
|
* ------| public/
|
||||||
* ------| store/
|
* ------| store/
|
||||||
* ------| server/
|
* ------| server/
|
||||||
* ------| app.config.ts
|
* ------| app.config.ts
|
||||||
@ -88,7 +89,32 @@ export default defineUntypedSchema({
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
srcDir: {
|
srcDir: {
|
||||||
$resolve: async (val: string | undefined, get): Promise<string> => resolve(await get('rootDir') as string, val || '.'),
|
$resolve: async (val: string | undefined, get): Promise<string> => {
|
||||||
|
if (val) {
|
||||||
|
return resolve(await get('rootDir') as string, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [rootDir, isV4] = await Promise.all([
|
||||||
|
get('rootDir') as Promise<string>,
|
||||||
|
(get('future') as Promise<Record<string, unknown>>).then(r => r.compatibilityVersion === 4),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!isV4) {
|
||||||
|
return rootDir
|
||||||
|
}
|
||||||
|
|
||||||
|
const srcDir = resolve(rootDir, 'app')
|
||||||
|
if (!existsSync(srcDir)) {
|
||||||
|
const keys = ['assets', 'layouts', 'middleware', 'pages', 'plugins'] as const
|
||||||
|
const dirs = await Promise.all(keys.map(key => get(`dir.${key}`) as Promise<string>))
|
||||||
|
for (const dir of dirs) {
|
||||||
|
if (existsSync(resolve(rootDir, dir))) {
|
||||||
|
return rootDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return srcDir
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,7 +125,11 @@ export default defineUntypedSchema({
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
serverDir: {
|
serverDir: {
|
||||||
$resolve: async (val: string | undefined, get): Promise<string> => resolve(await get('rootDir') as string, val || resolve(await get('srcDir') as string, 'server')),
|
$resolve: async (val: string | undefined, get): Promise<string> => {
|
||||||
|
const isV4 = ((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
|
||||||
|
|
||||||
|
return resolve(await get('rootDir') as string, (val || isV4) ? 'server' : resolve(await get('srcDir') as string, 'server'))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,6 +249,15 @@ export default defineUntypedSchema({
|
|||||||
* It is better to stick with defaults unless needed.
|
* It is better to stick with defaults unless needed.
|
||||||
*/
|
*/
|
||||||
dir: {
|
dir: {
|
||||||
|
app: {
|
||||||
|
$resolve: async (val: string | undefined, get) => {
|
||||||
|
const isV4 = (await get('future') as Record<string, unknown>).compatibilityVersion === 4
|
||||||
|
if (isV4) {
|
||||||
|
return resolve(await get('srcDir') as string, val || '.')
|
||||||
|
}
|
||||||
|
return val || 'app'
|
||||||
|
},
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* The assets directory (aliased as `~assets` in your build).
|
* The assets directory (aliased as `~assets` in your build).
|
||||||
*/
|
*/
|
||||||
@ -237,7 +276,15 @@ export default defineUntypedSchema({
|
|||||||
/**
|
/**
|
||||||
* The modules directory, each file in which will be auto-registered as a Nuxt module.
|
* The modules directory, each file in which will be auto-registered as a Nuxt module.
|
||||||
*/
|
*/
|
||||||
modules: 'modules',
|
modules: {
|
||||||
|
$resolve: async (val: string | undefined, get) => {
|
||||||
|
const isV4 = (await get('future') as Record<string, unknown>).compatibilityVersion === 4
|
||||||
|
if (isV4) {
|
||||||
|
return resolve(await get('rootDir') as string, val || 'modules')
|
||||||
|
}
|
||||||
|
return val || 'modules'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The directory which will be processed to auto-generate your application page routes.
|
* The directory which will be processed to auto-generate your application page routes.
|
||||||
@ -254,7 +301,13 @@ export default defineUntypedSchema({
|
|||||||
* and copied across into your `dist` folder when your app is generated.
|
* and copied across into your `dist` folder when your app is generated.
|
||||||
*/
|
*/
|
||||||
public: {
|
public: {
|
||||||
$resolve: async (val, get) => val || await get('dir.static') || 'public',
|
$resolve: async (val: string | undefined, get) => {
|
||||||
|
const isV4 = (await get('future') as Record<string, unknown>).compatibilityVersion === 4
|
||||||
|
if (isV4) {
|
||||||
|
return resolve(await get('rootDir') as string, val || await get('dir.static') as string || 'public')
|
||||||
|
}
|
||||||
|
return val || await get('dir.static') as string || 'public'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
static: {
|
static: {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { defineUntypedSchema } from 'untyped'
|
import { defineUntypedSchema } from 'untyped'
|
||||||
import { loading as loadingTemplate } from '@nuxt/ui-templates'
|
import { template as loadingTemplate } from '../../../ui-templates/dist/templates/loading'
|
||||||
|
|
||||||
export default defineUntypedSchema({
|
export default defineUntypedSchema({
|
||||||
devServer: {
|
devServer: {
|
||||||
|
@ -6,6 +6,40 @@ export default defineUntypedSchema({
|
|||||||
* (possibly major) version of the framework.
|
* (possibly major) version of the framework.
|
||||||
*/
|
*/
|
||||||
future: {
|
future: {
|
||||||
|
/**
|
||||||
|
* Enable early access to Nuxt v4 features or flags.
|
||||||
|
*
|
||||||
|
* Setting `compatibilityVersion` to `4` changes defaults throughout your
|
||||||
|
* Nuxt configuration, but you can granularly re-enable Nuxt v3 behaviour
|
||||||
|
* when testing (see example). Please file issues if so, so that we can
|
||||||
|
* address in Nuxt or in the ecosystem.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* export default defineNuxtConfig({
|
||||||
|
* future: {
|
||||||
|
* compatibilityVersion: 4,
|
||||||
|
* },
|
||||||
|
* // To re-enable _all_ Nuxt v3 behaviour, set the following options:
|
||||||
|
* srcDir: '.',
|
||||||
|
* dir: {
|
||||||
|
* app: 'app'
|
||||||
|
* },
|
||||||
|
* experimental: {
|
||||||
|
* compileTemplate: true,
|
||||||
|
* templateUtils: true,
|
||||||
|
* relativeWatchPaths: true,
|
||||||
|
* defaults: {
|
||||||
|
* useAsyncData: {
|
||||||
|
* deep: true
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
* @type {3 | 4}
|
||||||
|
*/
|
||||||
|
compatibilityVersion: 3,
|
||||||
/**
|
/**
|
||||||
* This enables 'Bundler' module resolution mode for TypeScript, which is the recommended setting
|
* This enables 'Bundler' module resolution mode for TypeScript, which is the recommended setting
|
||||||
* for frameworks like Nuxt and Vite.
|
* for frameworks like Nuxt and Vite.
|
||||||
@ -267,7 +301,7 @@ export default defineUntypedSchema({
|
|||||||
* - Uses the hash hydration plugin to reduce initial hydration
|
* - Uses the hash hydration plugin to reduce initial hydration
|
||||||
* @see [Nuxt Discussion #22632](https://github.com/nuxt/nuxt/discussions/22632]
|
* @see [Nuxt Discussion #22632](https://github.com/nuxt/nuxt/discussions/22632]
|
||||||
*/
|
*/
|
||||||
headNext: false,
|
headNext: true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow defining `routeRules` directly within your `~/pages` directory using `defineRouteRules`.
|
* Allow defining `routeRules` directly within your `~/pages` directory using `defineRouteRules`.
|
||||||
@ -319,7 +353,7 @@ export default defineUntypedSchema({
|
|||||||
* 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.
|
||||||
* @see [CookieStore](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore)
|
* @see [CookieStore](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore)
|
||||||
*/
|
*/
|
||||||
cookieStore: false,
|
cookieStore: true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This allows specifying the default options for core Nuxt components and composables.
|
* This allows specifying the default options for core Nuxt components and composables.
|
||||||
@ -336,7 +370,11 @@ export default defineUntypedSchema({
|
|||||||
* Options that apply to `useAsyncData` (and also therefore `useFetch`)
|
* Options that apply to `useAsyncData` (and also therefore `useFetch`)
|
||||||
*/
|
*/
|
||||||
useAsyncData: {
|
useAsyncData: {
|
||||||
deep: true,
|
deep: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? !((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
/** @type {Pick<typeof import('ofetch')['FetchOptions'], 'timeout' | 'retry' | 'retryDelay' | 'retryStatusCodes'>} */
|
/** @type {Pick<typeof import('ofetch')['FetchOptions'], 'timeout' | 'retry' | 'retryDelay' | 'retryStatusCodes'>} */
|
||||||
useFetch: {},
|
useFetch: {},
|
||||||
@ -356,5 +394,42 @@ export default defineUntypedSchema({
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
clientNodeCompat: false,
|
clientNodeCompat: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use `lodash.template` to compile Nuxt templates.
|
||||||
|
*
|
||||||
|
* This flag will be removed with the release of v4 and exists only for
|
||||||
|
* advance testing within Nuxt v3.12+.
|
||||||
|
*/
|
||||||
|
compileTemplate: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion !== 4)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to provide a legacy `templateUtils` object (with `serialize`,
|
||||||
|
* `importName` and `importSources`) when compiling Nuxt templates.
|
||||||
|
*
|
||||||
|
* This flag will be removed with the release of v4 and exists only for
|
||||||
|
* advance testing within Nuxt v3.12+.
|
||||||
|
*/
|
||||||
|
templateUtils: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion !== 4)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to provide relative paths in the `builder:watch` hook.
|
||||||
|
*
|
||||||
|
* This flag will be removed with the release of v4 and exists only for
|
||||||
|
* advance testing within Nuxt v3.12+.
|
||||||
|
*/
|
||||||
|
relativeWatchPaths: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion !== 4)
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -53,7 +53,7 @@ export interface ScanDir {
|
|||||||
*/
|
*/
|
||||||
pathPrefix?: boolean
|
pathPrefix?: boolean
|
||||||
/**
|
/**
|
||||||
* Ignore scanning this directory if set to `true`
|
* Ignore scanning this directory if set to `false`
|
||||||
*/
|
*/
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
/**
|
/**
|
||||||
|
@ -75,7 +75,6 @@ export interface Nuxt {
|
|||||||
// Private fields.
|
// Private fields.
|
||||||
_version: string
|
_version: string
|
||||||
_ignore?: Ignore
|
_ignore?: Ignore
|
||||||
_ignorePatterns?: string[]
|
|
||||||
|
|
||||||
/** The resolved Nuxt configuration. */
|
/** The resolved Nuxt configuration. */
|
||||||
options: NuxtOptions
|
options: NuxtOptions
|
||||||
|
90
packages/schema/test/folder-structure.spec.ts
Normal file
90
packages/schema/test/folder-structure.spec.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { applyDefaults } from 'untyped'
|
||||||
|
|
||||||
|
import { NuxtConfigSchema } from '../src'
|
||||||
|
import type { NuxtOptions } from '../src'
|
||||||
|
|
||||||
|
describe('nuxt folder structure', () => {
|
||||||
|
it('should resolve directories for v3 setup correctly', async () => {
|
||||||
|
const result = await applyDefaults(NuxtConfigSchema, {})
|
||||||
|
expect(getDirs(result as unknown as NuxtOptions)).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"dir": {
|
||||||
|
"app": "app",
|
||||||
|
"modules": "modules",
|
||||||
|
"public": "public",
|
||||||
|
},
|
||||||
|
"rootDir": "<cwd>",
|
||||||
|
"serverDir": "<cwd>/server",
|
||||||
|
"srcDir": "<cwd>",
|
||||||
|
"workspaceDir": "<cwd>",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should resolve directories with a custom `srcDir` and `rootDir`', async () => {
|
||||||
|
const result = await applyDefaults(NuxtConfigSchema, { srcDir: 'src/', rootDir: '/test' })
|
||||||
|
expect(getDirs(result as unknown as NuxtOptions)).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"dir": {
|
||||||
|
"app": "app",
|
||||||
|
"modules": "modules",
|
||||||
|
"public": "public",
|
||||||
|
},
|
||||||
|
"rootDir": "/test",
|
||||||
|
"serverDir": "/test/src/server",
|
||||||
|
"srcDir": "/test/src",
|
||||||
|
"workspaceDir": "/test",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should resolve directories when opting-in to v4 schema', async () => {
|
||||||
|
const result = await applyDefaults(NuxtConfigSchema, { future: { compatibilityVersion: 4 } })
|
||||||
|
expect(getDirs(result as unknown as NuxtOptions)).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"dir": {
|
||||||
|
"app": "<cwd>/app",
|
||||||
|
"modules": "<cwd>/modules",
|
||||||
|
"public": "<cwd>/public",
|
||||||
|
},
|
||||||
|
"rootDir": "<cwd>",
|
||||||
|
"serverDir": "<cwd>/server",
|
||||||
|
"srcDir": "<cwd>/app",
|
||||||
|
"workspaceDir": "<cwd>",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should resolve directories when opting-in to v4 schema with a custom `srcDir` and `rootDir`', async () => {
|
||||||
|
const result = await applyDefaults(NuxtConfigSchema, { future: { compatibilityVersion: 4 }, srcDir: 'customApp/', rootDir: '/test' })
|
||||||
|
expect(getDirs(result as unknown as NuxtOptions)).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"dir": {
|
||||||
|
"app": "/test/customApp",
|
||||||
|
"modules": "/test/modules",
|
||||||
|
"public": "/test/public",
|
||||||
|
},
|
||||||
|
"rootDir": "/test",
|
||||||
|
"serverDir": "/test/server",
|
||||||
|
"srcDir": "/test/customApp",
|
||||||
|
"workspaceDir": "/test",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function getDirs (options: NuxtOptions) {
|
||||||
|
const stripRoot = (dir: string) => dir.replace(process.cwd(), '<cwd>')
|
||||||
|
return {
|
||||||
|
rootDir: stripRoot(options.rootDir),
|
||||||
|
serverDir: stripRoot(options.serverDir),
|
||||||
|
srcDir: stripRoot(options.srcDir),
|
||||||
|
dir: {
|
||||||
|
app: stripRoot(options.dir.app),
|
||||||
|
modules: stripRoot(options.dir.modules),
|
||||||
|
public: stripRoot(options.dir.public),
|
||||||
|
},
|
||||||
|
workspaceDir: stripRoot(options.workspaceDir!),
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user