mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-13 04:08:11 +00:00
Merge branch 'main' into feat/nuxt_async_context
This commit is contained in:
commit
ed716bf654
4
.github/workflows/autofix-docs.yml
vendored
4
.github/workflows/autofix-docs.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -33,4 +33,4 @@ jobs:
|
|||||||
- name: Lint (docs)
|
- name: Lint (docs)
|
||||||
run: pnpm lint:docs:fix
|
run: pnpm lint:docs:fix
|
||||||
|
|
||||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||||
|
6
.github/workflows/autofix.yml
vendored
6
.github/workflows/autofix.yml
vendored
@ -17,14 +17,14 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Check engine ranges, peer dependency ranges and installed versions
|
- name: Check engine ranges, peer dependency ranges and installed versions
|
||||||
run: pnpm installed-check -d --fix
|
run: pnpm installed-check --no-include-workspace-root --ignore-dev --workspace-ignore='test/**,playground' --fix
|
||||||
|
|
||||||
- name: Build (stub)
|
- name: Build (stub)
|
||||||
run: pnpm dev:prepare
|
run: pnpm dev:prepare
|
||||||
@ -55,4 +55,4 @@ jobs:
|
|||||||
- name: Lint (code)
|
- name: Lint (code)
|
||||||
run: pnpm lint:fix
|
run: pnpm lint:fix
|
||||||
|
|
||||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||||
|
52
.github/workflows/benchmark.yml
vendored
52
.github/workflows/benchmark.yml
vendored
@ -1,52 +0,0 @@
|
|||||||
name: benchmark
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
# pull_request:
|
|
||||||
# paths-ignore:
|
|
||||||
# - "docs/**"
|
|
||||||
# - "*.md"
|
|
||||||
# branches:
|
|
||||||
# - main
|
|
||||||
# - "!v[0-9]*"
|
|
||||||
|
|
||||||
# https://github.com/vitejs/vite/blob/main/.github/workflows/ci.yml
|
|
||||||
env:
|
|
||||||
# 7 GiB by default on GitHub, setting to 6 GiB
|
|
||||||
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
|
|
||||||
NODE_OPTIONS: --max-old-space-size=6144
|
|
||||||
|
|
||||||
# Remove default permissions of GITHUB_TOKEN for security
|
|
||||||
# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
|
|
||||||
cancel-in-progress: ${{ github.event_name != 'push' }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
run:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
- run: corepack enable
|
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
cache: "pnpm"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Build (stub)
|
|
||||||
run: pnpm dev:prepare
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm build
|
|
||||||
|
|
||||||
- name: Run benchmarks
|
|
||||||
uses: CodSpeedHQ/action@513a19673a831f139e8717bf45ead67e47f00044 # v3.2.0
|
|
||||||
with:
|
|
||||||
run: pnpm vitest bench
|
|
||||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
|
2
.github/workflows/changelog.yml
vendored
2
.github/workflows/changelog.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
104
.github/workflows/ci.yml
vendored
104
.github/workflows/ci.yml
vendored
@ -41,7 +41,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -56,9 +56,6 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Check types
|
|
||||||
run: pnpm test:attw
|
|
||||||
|
|
||||||
- name: Cache dist
|
- name: Cache dist
|
||||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
@ -81,7 +78,7 @@ jobs:
|
|||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
uses: github/codeql-action/init@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3.28.4
|
||||||
with:
|
with:
|
||||||
config: |
|
config: |
|
||||||
paths:
|
paths:
|
||||||
@ -98,7 +95,7 @@ jobs:
|
|||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
uses: github/codeql-action/analyze@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3.28.4
|
||||||
with:
|
with:
|
||||||
category: "/language:${{ matrix.language }}"
|
category: "/language:${{ matrix.language }}"
|
||||||
|
|
||||||
@ -118,7 +115,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -149,7 +146,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -161,6 +158,9 @@ jobs:
|
|||||||
- name: Lint
|
- name: Lint
|
||||||
run: pnpm lint
|
run: pnpm lint
|
||||||
|
|
||||||
|
- name: Check built types
|
||||||
|
run: pnpm test:attw
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
# autofix workflow will be triggered instead for PRs
|
# autofix workflow will be triggered instead for PRs
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
@ -173,7 +173,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -188,6 +188,59 @@ jobs:
|
|||||||
- name: Test (runtime unit)
|
- name: Test (runtime unit)
|
||||||
run: pnpm test:runtime
|
run: pnpm test:runtime
|
||||||
|
|
||||||
|
test-size:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
- run: corepack enable
|
||||||
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Restore dist cache
|
||||||
|
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: packages
|
||||||
|
|
||||||
|
- name: Check bundle size
|
||||||
|
run: pnpm vitest run bundle
|
||||||
|
|
||||||
|
test-benchmark:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
- run: corepack enable
|
||||||
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Restore dist cache
|
||||||
|
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: packages
|
||||||
|
|
||||||
|
- name: Run benchmarks
|
||||||
|
uses: CodSpeedHQ/action@513a19673a831f139e8717bf45ead67e47f00044 # v3.2.0
|
||||||
|
with:
|
||||||
|
run: pnpm vitest bench
|
||||||
|
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||||
|
|
||||||
test-fixtures:
|
test-fixtures:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
needs:
|
needs:
|
||||||
@ -253,27 +306,25 @@ jobs:
|
|||||||
TEST_MANIFEST: ${{ matrix.manifest }}
|
TEST_MANIFEST: ${{ matrix.manifest }}
|
||||||
TEST_CONTEXT: ${{ matrix.context }}
|
TEST_CONTEXT: ${{ matrix.context }}
|
||||||
TEST_PAYLOAD: ${{ matrix.payload }}
|
TEST_PAYLOAD: ${{ matrix.payload }}
|
||||||
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }}
|
SKIP_BUNDLE_SIZE: true
|
||||||
|
|
||||||
- uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
|
- uses: codecov/codecov-action@0da7aa657d958d32c117fc47e1f977e7524753c7 # v5.3.0
|
||||||
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 }}
|
||||||
|
|
||||||
build-release:
|
release-nightly:
|
||||||
concurrency:
|
concurrency:
|
||||||
group: release
|
group: release
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
if: |
|
if: |
|
||||||
github.event_name == 'push' &&
|
github.event_name == 'push' &&
|
||||||
github.repository == 'nuxt/nuxt' &&
|
github.repository_owner == 'nuxt' &&
|
||||||
!contains(github.event.head_commit.message, '[skip-release]') &&
|
!contains(github.event.head_commit.message, '[skip-release]') &&
|
||||||
!startsWith(github.event.head_commit.message, 'docs')
|
!startsWith(github.event.head_commit.message, 'docs')
|
||||||
needs:
|
needs:
|
||||||
- lint
|
|
||||||
- build
|
- build
|
||||||
- test-fixtures
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
@ -284,7 +335,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -299,22 +350,13 @@ jobs:
|
|||||||
- name: Release Edge
|
- name: Release Edge
|
||||||
run: ./scripts/release-edge.sh ${{ github.ref == 'refs/heads/main' && 'latest' || '3x' }}
|
run: ./scripts/release-edge.sh ${{ github.ref == 'refs/heads/main' && 'latest' || '3x' }}
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
|
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
||||||
NPM_CONFIG_PROVENANCE: true
|
NPM_CONFIG_PROVENANCE: true
|
||||||
|
|
||||||
release-pr:
|
release-pr:
|
||||||
concurrency:
|
if: github.repository_owner == 'nuxt' && github.event_name != 'push'
|
||||||
group: release
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
pull-requests: write
|
|
||||||
if: |
|
|
||||||
github.event_name == 'pull_request' &&
|
|
||||||
contains(github.event.pull_request.labels.*.name, '🧷 edge release')
|
|
||||||
needs:
|
needs:
|
||||||
- lint
|
|
||||||
- build
|
- build
|
||||||
- test-fixtures
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
@ -325,7 +367,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -337,8 +379,4 @@ jobs:
|
|||||||
name: dist
|
name: dist
|
||||||
path: packages
|
path: packages
|
||||||
|
|
||||||
- name: Release Edge
|
- run: pnpm pkg-pr-new publish --compact './packages/kit' './packages/nuxt' './packages/rspack' './packages/schema' './packages/vite' './packages/webpack'
|
||||||
run: ./scripts/release-edge.sh pr-${{ github.event.issue.number }}
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
|
|
||||||
NPM_CONFIG_PROVENANCE: true
|
|
||||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
6
.github/workflows/lint-monorepo.yml
vendored
6
.github/workflows/lint-monorepo.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: CI
|
name: ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -29,7 +29,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: lts/*
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -39,4 +39,4 @@ jobs:
|
|||||||
run: pnpm sherif -r multiple-dependency-versions
|
run: pnpm sherif -r multiple-dependency-versions
|
||||||
|
|
||||||
- name: Check engine ranges, peer dependency ranges and installed versions
|
- name: Check engine ranges, peer dependency ranges and installed versions
|
||||||
run: pnpm installed-check -d
|
run: pnpm installed-check --no-include-workspace-root --ignore-dev --workspace-ignore='test/**,playground'
|
||||||
|
2
.github/workflows/lint-workflows.yml
vendored
2
.github/workflows/lint-workflows.yml
vendored
@ -26,6 +26,6 @@ jobs:
|
|||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
# 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
|
||||||
uses: docker://rhysd/actionlint:1.7.6@sha256:e3856d413f923accc4120884ff79f6bdba3dd53fd42884d325f21af61cc15ce0
|
uses: docker://rhysd/actionlint:1.7.7@sha256:887a259a5a534f3c4f36cb02dca341673c6089431057242cdc931e9f133147e9
|
||||||
with:
|
with:
|
||||||
args: -color
|
args: -color
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: lts/*
|
||||||
registry-url: "https://registry.npmjs.org/"
|
registry-url: "https://registry.npmjs.org/"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
|
2
.github/workflows/scorecards.yml
vendored
2
.github/workflows/scorecards.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
uses: github/codeql-action/upload-sarif@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3.28.4
|
||||||
if: github.repository == 'nuxt/nuxt' && success()
|
if: github.repository == 'nuxt/nuxt' && success()
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
2
.github/workflows/semantic-pull-requests.yml
vendored
2
.github/workflows/semantic-pull-requests.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
|
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
|
||||||
if: github.repository == 'nuxt/nuxt' && !startsWith(github.head_ref, 'v')
|
if: github.repository == 'nuxt/nuxt' && !startsWith(github.head_ref, 'v')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Semantic pull request
|
name: semantic-pr
|
||||||
steps:
|
steps:
|
||||||
- name: Validate PR title
|
- name: Validate PR title
|
||||||
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
|
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'workflow_dispatch' || github.repository == 'nuxt/nuxt'
|
if: github.event_name == 'workflow_dispatch' || github.repository == 'nuxt/nuxt'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
|
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
|
||||||
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale.
|
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale.
|
||||||
|
23
debug/build-config.ts
Normal file
23
debug/build-config.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import process from 'node:process'
|
||||||
|
|
||||||
|
import type { InputPluginOption } from 'rollup'
|
||||||
|
import type { BuildOptions } from 'unbuild'
|
||||||
|
|
||||||
|
import { AnnotateFunctionTimingsPlugin } from './plugins/timings-unbuild'
|
||||||
|
|
||||||
|
export const stubOptions = {
|
||||||
|
jiti: {
|
||||||
|
transformOptions: {
|
||||||
|
babel: {
|
||||||
|
plugins: (process.env.TIMINGS_DEBUG ? [fileURLToPath(new URL('./plugins/timings-babel.mjs', import.meta.url))] : []) as any,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies BuildOptions['stubOptions']
|
||||||
|
|
||||||
|
export function addRollupTimingsPlugin (options: { plugins: InputPluginOption[] }) {
|
||||||
|
if (process.env.TIMINGS_DEBUG) {
|
||||||
|
options.plugins.push(AnnotateFunctionTimingsPlugin())
|
||||||
|
}
|
||||||
|
}
|
167
debug/plugins/timings-babel.mjs
Normal file
167
debug/plugins/timings-babel.mjs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
import { declare } from '@babel/helper-plugin-utils'
|
||||||
|
import { types as t } from '@babel/core'
|
||||||
|
|
||||||
|
const metricsPath = fileURLToPath(new URL('../../debug-timings.json', import.meta.url))
|
||||||
|
|
||||||
|
// inlined from https://github.com/danielroe/errx
|
||||||
|
function captureStackTrace () {
|
||||||
|
const IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[a-z]:[/\\]/i
|
||||||
|
const LINE_RE = /^\s+at (?:(?<function>[^)]+) \()?(?<source>[^)]+)\)?$/u
|
||||||
|
const SOURCE_RE = /^(?<source>.+):(?<line>\d+):(?<column>\d+)$/u
|
||||||
|
|
||||||
|
if (!Error.captureStackTrace) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line unicorn/error-message
|
||||||
|
const stack = new Error()
|
||||||
|
Error.captureStackTrace(stack)
|
||||||
|
const trace = []
|
||||||
|
for (const line of stack.stack?.split('\n') || []) {
|
||||||
|
const parsed = LINE_RE.exec(line)?.groups
|
||||||
|
if (!parsed) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!parsed.source) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const parsedSource = SOURCE_RE.exec(parsed.source)?.groups
|
||||||
|
if (parsedSource) {
|
||||||
|
Object.assign(parsed, parsedSource)
|
||||||
|
}
|
||||||
|
if (IS_ABSOLUTE_RE.test(parsed.source)) {
|
||||||
|
parsed.source = `file://${parsed.source}`
|
||||||
|
}
|
||||||
|
if (parsed.source === import.meta.url) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (const key of ['line', 'column']) {
|
||||||
|
if (parsed[key]) {
|
||||||
|
// @ts-expect-error
|
||||||
|
parsed[key] = Number(parsed[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace.push(parsed)
|
||||||
|
}
|
||||||
|
return trace
|
||||||
|
}
|
||||||
|
|
||||||
|
export const leading = `
|
||||||
|
import { writeFileSync as ____writeFileSync } from 'node:fs'
|
||||||
|
const ___captureStackTrace = ${captureStackTrace.toString()};
|
||||||
|
globalThis.___calls ||= {};
|
||||||
|
globalThis.___timings ||= {};
|
||||||
|
globalThis.___callers ||= {};`
|
||||||
|
|
||||||
|
function onExit () {
|
||||||
|
if (globalThis.___logged) { return }
|
||||||
|
globalThis.___logged = true
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
____writeFileSync(metricsPath, JSON.stringify(Object.fromEntries(Object.entries(globalThis.___timings).map(([name, time]) => [
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
time: Number(Number(time).toFixed(2)),
|
||||||
|
calls: globalThis.___calls[name],
|
||||||
|
callers: globalThis.___callers[name] ? Object.fromEntries(Object.entries(globalThis.___callers[name]).map(([name, count]) => [name.trim(), count]).sort((a, b) => typeof b[0] === 'string' && typeof a[0] === 'string' ? a[0].localeCompare(b[0]) : 0)) : undefined,
|
||||||
|
},
|
||||||
|
]).sort((a, b) => typeof b[0] === 'string' && typeof a[0] === 'string' ? a[0].localeCompare(b[0]) : 0)), null, 2))
|
||||||
|
|
||||||
|
// worst by total time
|
||||||
|
const timings = Object.entries(globalThis.___timings)
|
||||||
|
|
||||||
|
const topFunctionsTotalTime = timings
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, 20)
|
||||||
|
.map(([name, time]) => ({
|
||||||
|
name,
|
||||||
|
time: Number(Number(time).toFixed(2)),
|
||||||
|
calls: globalThis.___calls[name],
|
||||||
|
callers: globalThis.___callers[name] && Object.entries(globalThis.___callers[name]).map(([name, count]) => `${name.trim()} (${count})`).join(', '),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Top 20 functions by total time:')
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.table(topFunctionsTotalTime)
|
||||||
|
|
||||||
|
// worst by average time (excluding single calls)
|
||||||
|
const topFunctionsAverageTime = timings
|
||||||
|
.filter(([name]) => (globalThis.___calls[name] || 0) > 1)
|
||||||
|
.map(([name, time]) => [name, time / (globalThis.___calls[name] || 1)])
|
||||||
|
// @ts-expect-error
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, 20)
|
||||||
|
.map(([name, time]) => ({
|
||||||
|
name,
|
||||||
|
time: Number(Number(time).toFixed(2)),
|
||||||
|
calls: name && globalThis.___calls[name],
|
||||||
|
callers: name && globalThis.___callers[name] && Object.entries(globalThis.___callers[name]).sort((a, b) => b[1] - a[1]).map(([name, count]) => `${name.trim()} (${count})`).join(', '),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Top 20 functions by average time:')
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.table(topFunctionsAverageTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const trailing = `process.on("exit", ${onExit.toString().replace('metricsPath', JSON.stringify(metricsPath))})`
|
||||||
|
|
||||||
|
/** @param {string} functionName */
|
||||||
|
export function generateInitCode (functionName) {
|
||||||
|
return `
|
||||||
|
___calls[${JSON.stringify(functionName)}] = (___calls[${JSON.stringify(functionName)}] || 0) + 1;
|
||||||
|
___timings[${JSON.stringify(functionName)}] ||= 0;
|
||||||
|
const ___now = Date.now();`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} functionName */
|
||||||
|
export function generateFinallyCode (functionName) {
|
||||||
|
return `
|
||||||
|
___timings[${JSON.stringify(functionName)}] += Date.now() - ___now;
|
||||||
|
try {
|
||||||
|
const ___callee = ___captureStackTrace()[1]?.function;
|
||||||
|
if (___callee) {
|
||||||
|
___callers[${JSON.stringify(functionName)}] ||= {};
|
||||||
|
___callers[${JSON.stringify(functionName)}][' ' + ___callee] = (___callers[${JSON.stringify(functionName)}][' ' + ___callee] || 0) + 1;
|
||||||
|
}
|
||||||
|
} catch {}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export default declare((api) => {
|
||||||
|
api.assertVersion(7)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'annotate-function-timings',
|
||||||
|
visitor: {
|
||||||
|
Program (path) {
|
||||||
|
path.unshiftContainer('body', t.expressionStatement(t.identifier(leading)))
|
||||||
|
path.pushContainer('body', t.expressionStatement(t.identifier(trailing)))
|
||||||
|
},
|
||||||
|
FunctionDeclaration (path) {
|
||||||
|
const functionName = path.node.id?.name
|
||||||
|
|
||||||
|
const start = path.get('body').get('body')[0]
|
||||||
|
const end = path.get('body').get('body').pop()
|
||||||
|
|
||||||
|
if (!functionName || ['createJiti', '___captureStackTrace', '_interopRequireDefault'].includes(functionName) || !start || !end) { return }
|
||||||
|
|
||||||
|
const initCode = generateInitCode(functionName)
|
||||||
|
const finallyCode = generateFinallyCode(functionName)
|
||||||
|
|
||||||
|
const originalCode = path.get('body').get('body').map(statement => statement.node)
|
||||||
|
path.get('body').get('body').forEach(statement => statement.remove())
|
||||||
|
|
||||||
|
path.get('body').unshiftContainer('body', t.expressionStatement(t.identifier(initCode)))
|
||||||
|
path.get('body').pushContainer('body', t.tryStatement(
|
||||||
|
t.blockStatement(originalCode),
|
||||||
|
t.catchClause(t.identifier('e'), t.blockStatement([])),
|
||||||
|
t.blockStatement([t.expressionStatement(t.identifier(finallyCode))]),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
57
debug/plugins/timings-unbuild.ts
Normal file
57
debug/plugins/timings-unbuild.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import type { Plugin } from 'rollup'
|
||||||
|
import { parse } from 'acorn'
|
||||||
|
import { type Node, walk } from 'estree-walker'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import tsBlankSpace from 'ts-blank-space'
|
||||||
|
|
||||||
|
import { generateFinallyCode, generateInitCode, leading, trailing } from './timings-babel.mjs'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var ___logged: boolean
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var ___timings: Record<string, number>
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var ___calls: Record<string, number>
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var ___callers: Record<string, number>
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var ____writeFileSync: typeof import('fs').writeFileSync
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AnnotateFunctionTimingsPlugin () {
|
||||||
|
return {
|
||||||
|
name: 'timings',
|
||||||
|
transform: {
|
||||||
|
order: 'post',
|
||||||
|
handler (code, id) {
|
||||||
|
const s = new MagicString(code)
|
||||||
|
try {
|
||||||
|
const ast = parse(tsBlankSpace(code), { sourceType: 'module', ecmaVersion: 'latest', locations: true })
|
||||||
|
walk(ast as Node, {
|
||||||
|
enter (node) {
|
||||||
|
if (node.type === 'FunctionDeclaration' && node.id && node.id.name) {
|
||||||
|
const functionName = node.id.name ? `${node.id.name} (${id.match(/[\\/]packages[\\/]([^/]+)[\\/]/)?.[1]})` : ''
|
||||||
|
const start = (node.body as Node & { start: number, end: number }).start
|
||||||
|
const end = (node.body as Node & { start: number, end: number }).end
|
||||||
|
|
||||||
|
if (!functionName || ['createJiti', 'captureStackTrace', '___captureStackTrace', '_interopRequireDefault'].includes(functionName) || !start || !end) { return }
|
||||||
|
|
||||||
|
s.prependLeft(start + 1, generateInitCode(functionName) + 'try {')
|
||||||
|
s.appendRight(end - 1, `} finally { ${generateFinallyCode(functionName)} }`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
code = s.toString()
|
||||||
|
if (!code.includes(leading)) {
|
||||||
|
code = [leading, code, trailing].join('\n')
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(e, code, id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Plugin
|
||||||
|
}
|
@ -174,6 +174,7 @@ Under the hood, `mountSuspended` wraps `mount` from `@vue/test-utils`, so you ca
|
|||||||
For example:
|
For example:
|
||||||
|
|
||||||
```ts twoslash
|
```ts twoslash
|
||||||
|
// @noErrors
|
||||||
import { it, expect } from 'vitest'
|
import { it, expect } from 'vitest'
|
||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
declare module '#components' {
|
declare module '#components' {
|
||||||
@ -194,6 +195,7 @@ it('can mount some component', async () => {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts twoslash
|
```ts twoslash
|
||||||
|
// @noErrors
|
||||||
import { it, expect } from 'vitest'
|
import { it, expect } from 'vitest'
|
||||||
// ---cut---
|
// ---cut---
|
||||||
// tests/components/SomeComponents.nuxt.spec.ts
|
// tests/components/SomeComponents.nuxt.spec.ts
|
||||||
@ -225,6 +227,7 @@ The passed in component will be rendered inside a `<div id="test-wrapper"></div>
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```ts twoslash
|
```ts twoslash
|
||||||
|
// @noErrors
|
||||||
import { it, expect } from 'vitest'
|
import { it, expect } from 'vitest'
|
||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
declare module '#components' {
|
declare module '#components' {
|
||||||
@ -243,6 +246,7 @@ it('can render some component', async () => {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts twoslash
|
```ts twoslash
|
||||||
|
// @noErrors
|
||||||
import { it, expect } from 'vitest'
|
import { it, expect } from 'vitest'
|
||||||
// ---cut---
|
// ---cut---
|
||||||
// tests/App.nuxt.spec.ts
|
// tests/App.nuxt.spec.ts
|
||||||
|
@ -202,6 +202,19 @@ const { data: discounts, status } = await useAsyncData('cart-discount', async ()
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::note
|
||||||
|
`useAsyncData` is for fetching and caching data, not triggering side effects like calling Pinia actions, as this can cause unintended behavior such as repeated executions with nullish values. If you need to trigger side effects, use the [`callOnce`](/docs/api/utils/call-once) utility to do so.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const offersStore = useOffersStore()
|
||||||
|
|
||||||
|
// you can't do this
|
||||||
|
await useAsyncData(() => offersStore.getOffer(route.params.slug))
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
::read-more{to="/docs/api/composables/use-async-data"}
|
::read-more{to="/docs/api/composables/use-async-data"}
|
||||||
Read more about `useAsyncData`.
|
Read more about `useAsyncData`.
|
||||||
::
|
::
|
||||||
|
@ -36,14 +36,24 @@ The module automatically loads and parses them.
|
|||||||
|
|
||||||
## Render Content
|
## Render Content
|
||||||
|
|
||||||
To render content pages, add a [catch-all route](/docs/guide/directory-structure/pages/#catch-all-route) using the [`<ContentDoc>`](https://content.nuxt.com/components/content-doc) component:
|
To render content pages, add a [catch-all route](/docs/guide/directory-structure/pages/#catch-all-route) using the [`<ContentRenderer>`](https://content.nuxt.com/docs/components/content-renderer) component:
|
||||||
|
|
||||||
```vue [pages/[...slug\\].vue]
|
```vue [pages/[...slug\\].vue]
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const route = useRoute()
|
||||||
|
const { data: page } = await useAsyncData(route.path, () => {
|
||||||
|
return queryCollection('content').path(route.path).first()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<div>
|
||||||
<!-- ContentDoc returns content for `$route.path` by default or you can pass a `path` prop -->
|
<header><!-- ... --></header>
|
||||||
<ContentDoc />
|
|
||||||
</main>
|
<ContentRenderer v-if="page" :value="page" />
|
||||||
|
|
||||||
|
<footer><!-- ... --></footer>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ Note that removing a variable from `.env` or removing the `.env` file entirely w
|
|||||||
If you want to use a different file - for example, to use `.env.local` or `.env.production` - you can do so by passing the `--dotenv` flag when using `nuxi`.
|
If you want to use a different file - for example, to use `.env.local` or `.env.production` - you can do so by passing the `--dotenv` flag when using `nuxi`.
|
||||||
|
|
||||||
```bash [Terminal]
|
```bash [Terminal]
|
||||||
npx nuxi dev --dotenv .env.local
|
npx nuxi dev -- --dotenv .env.local
|
||||||
```
|
```
|
||||||
|
|
||||||
When updating `.env` in development mode, the Nuxt instance is automatically restarted to apply new values to the `process.env`.
|
When updating `.env` in development mode, the Nuxt instance is automatically restarted to apply new values to the `process.env`.
|
||||||
|
@ -116,6 +116,8 @@ export function useAPI<T>(
|
|||||||
This example demonstrates how to use a custom `useFetch`, but the same structure is identical for a custom `useAsyncData`.
|
This example demonstrates how to use a custom `useFetch`, but the same structure is identical for a custom `useAsyncData`.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
:link-example{to="/docs/examples/advanced/use-custom-fetch-composable"}
|
||||||
|
|
||||||
::callout{icon="i-simple-icons-youtube" color="red" to="https://www.youtube.com/watch?v=jXH8Tr-exhI"}
|
::callout{icon="i-simple-icons-youtube" color="red" to="https://www.youtube.com/watch?v=jXH8Tr-exhI"}
|
||||||
Watch a video about custom `$fetch` and Repository Pattern in Nuxt.
|
Watch a video about custom `$fetch` and Repository Pattern in Nuxt.
|
||||||
::
|
::
|
||||||
|
@ -51,3 +51,26 @@ The content of the default slot will be tree-shaken out of the server build. (Th
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Accessing HTML Elements
|
||||||
|
|
||||||
|
Components inside `<ClientOnly>` are rendered only after being mounted. To access the rendered elements in the DOM, you can watch a template ref:
|
||||||
|
|
||||||
|
```vue [pages/example.vue]
|
||||||
|
<script setup lang="ts">
|
||||||
|
const nuxtWelcomeRef = ref()
|
||||||
|
|
||||||
|
// The watch will be triggered when the component is available
|
||||||
|
watch(nuxtWelcomeRef, () => {
|
||||||
|
console.log('<NuxtWelcome /> mounted')
|
||||||
|
}, { once: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ClientOnly>
|
||||||
|
<NuxtWelcome ref="nuxtWelcomeRef" />
|
||||||
|
</ClientOnly>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
@ -16,19 +16,24 @@ links:
|
|||||||
|
|
||||||
In this example, we use `<NuxtLink>` component to link to another page of the application.
|
In this example, we use `<NuxtLink>` component to link to another page of the application.
|
||||||
|
|
||||||
|
::code-group
|
||||||
```vue [pages/index.vue]
|
```vue [pages/index.vue]
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink to="/about">
|
<NuxtLink to="/about">About page</NuxtLink>
|
||||||
About page
|
|
||||||
</NuxtLink>
|
|
||||||
<!-- <a href="/about">...</a> (+Vue Router & prefetching) -->
|
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```html [(Renders as) index.html]
|
||||||
|
<!-- (Vue Router & Smart Prefetching) -->
|
||||||
|
<a href="/about">About page</a>
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
### Passing Params to Dynamic Routes
|
### Passing Params to Dynamic Routes
|
||||||
|
|
||||||
In this example, we pass the `id` param to link to the route `~/pages/posts/[id].vue`.
|
In this example, we pass the `id` param to link to the route `~/pages/posts/[id].vue`.
|
||||||
|
|
||||||
|
::code-group
|
||||||
```vue [pages/index.vue]
|
```vue [pages/index.vue]
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink :to="{ name: 'posts-id', params: { id: 123 } }">
|
<NuxtLink :to="{ name: 'posts-id', params: { id: 123 } }">
|
||||||
@ -37,26 +42,46 @@ In this example, we pass the `id` param to link to the route `~/pages/posts/[id]
|
|||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```html [(Renders as) index.html]
|
||||||
|
<a href="/posts/123">Post 123</a>
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
::tip
|
::tip
|
||||||
Check out the Pages panel in Nuxt DevTools to see the route name and the params it might take.
|
Check out the Pages panel in Nuxt DevTools to see the route name and the params it might take.
|
||||||
::
|
::
|
||||||
|
|
||||||
### Handling 404s
|
### Handling Static File and Cross-App Links
|
||||||
|
|
||||||
When using `<NuxtLink>` for `/public` directory files or when pointing to a different app on the same domain, you should use the `external` prop.
|
By default, `<NuxtLink>` uses Vue Router's client side navigation for relative route. When linking to static files in the `/public` directory or to another application hosted on the same domain, it might result in unexpected 404 errors because they are not part of the client routes. In such cases, you can use the `external` prop with `<NuxtLink>` to bypass Vue Router's internal routing mechanism.
|
||||||
|
|
||||||
Using `external` forces the link to be rendered as an `a` tag instead of a Vue Router `RouterLink`.
|
The `external` prop explicitly indicates that the link is external. `<NuxtLink>` will render the link as a standard HTML `<a>` tag. This ensures the link behaves correctly, bypassing Vue Router’s logic and directly pointing to the resource.
|
||||||
|
|
||||||
|
#### Linking to Static Files
|
||||||
|
|
||||||
|
For static files in the `/public` directory, such as PDFs or images, use the `external` prop to ensure the link resolves correctly.
|
||||||
|
|
||||||
```vue [pages/index.vue]
|
```vue [pages/index.vue]
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink to="/the-important-report.pdf" external>
|
<NuxtLink to="/example-report.pdf" external>
|
||||||
Download Report
|
Download Report
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<!-- <a href="/the-important-report.pdf"></a> -->
|
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
The external logic is applied by default when using absolute URLs and when providing a `target` prop.
|
#### Linking to a Cross-App URL
|
||||||
|
|
||||||
|
When pointing to a different application on the same domain, using the `external` prop ensures the correct behavior.
|
||||||
|
|
||||||
|
```vue [pages/index.vue]
|
||||||
|
<template>
|
||||||
|
<NuxtLink to="/another-app" external>
|
||||||
|
Go to Another App
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the `external` prop or relying on automatic handling ensures proper navigation, avoids unexpected routing issues, and improves compatibility with static resources or cross-application scenarios.
|
||||||
|
|
||||||
## External Routing
|
## External Routing
|
||||||
|
|
||||||
@ -71,40 +96,126 @@ In this example, we use `<NuxtLink>` component to link to a website.
|
|||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
## `target` and `rel` Attributes
|
## `rel` and `noRel` Attributes
|
||||||
|
|
||||||
A `rel` attribute of `noopener noreferrer` is applied by default to absolute links and links that open in new tabs.
|
A `rel` attribute of `noopener noreferrer` is applied by default to links with a `target` attribute or to absolute links (e.g., links starting with `http://`, `https://`, or `//`).
|
||||||
- `noopener` solves a [security bug](https://mathiasbynens.github.io/rel-noopener/) in older browsers.
|
- `noopener` solves a [security bug](https://mathiasbynens.github.io/rel-noopener/) in older browsers.
|
||||||
- `noreferrer` improves privacy for your users by not sending the `Referer` header to the linked site.
|
- `noreferrer` improves privacy for your users by not sending the `Referer` header to the linked site.
|
||||||
|
|
||||||
These defaults have no negative impact on SEO and are considered [best practice](https://developer.chrome.com/docs/lighthouse/best-practices/external-anchors-use-rel-noopener).
|
These defaults have no negative impact on SEO and are considered [best practice](https://developer.chrome.com/docs/lighthouse/best-practices/external-anchors-use-rel-noopener).
|
||||||
|
|
||||||
When you need to overwrite this behavior you can use the `rel` and `noRel` props.
|
When you need to overwrite this behavior you can use the `rel` or `noRel` props.
|
||||||
|
|
||||||
```vue [app.vue]
|
```vue [app.vue]
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink to="https://twitter.com/nuxt_js" target="_blank">
|
<NuxtLink to="https://twitter.com/nuxt_js">
|
||||||
Nuxt Twitter
|
Nuxt Twitter
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<!-- <a href="https://twitter.com/nuxt_js" target="_blank" rel="noopener noreferrer">...</a> -->
|
<!-- <a href="https://twitter.com/nuxt_js" rel="noopener noreferrer">...</a> -->
|
||||||
|
|
||||||
<NuxtLink to="https://discord.nuxtjs.org" target="_blank" rel="noopener">
|
<NuxtLink to="https://discord.nuxtjs.org" rel="noopener">
|
||||||
Nuxt Discord
|
Nuxt Discord
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<!-- <a href="https://discord.nuxtjs.org" target="_blank" rel="noopener">...</a> -->
|
<!-- <a href="https://discord.nuxtjs.org" rel="noopener">...</a> -->
|
||||||
|
|
||||||
|
<NuxtLink to="/about" target="_blank">About page</NuxtLink>
|
||||||
|
<!-- <a href="/about" target="_blank" rel="noopener noreferrer">...</a> -->
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
A `noRel` prop can be used to prevent the default `rel` attribute from being added to the absolute links.
|
||||||
|
|
||||||
|
```vue [app.vue]
|
||||||
|
<template>
|
||||||
<NuxtLink to="https://github.com/nuxt" no-rel>
|
<NuxtLink to="https://github.com/nuxt" no-rel>
|
||||||
Nuxt GitHub
|
Nuxt GitHub
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<!-- <a href="https://github.com/nuxt">...</a> -->
|
<!-- <a href="https://github.com/nuxt">...</a> -->
|
||||||
|
|
||||||
<NuxtLink to="/contact" target="_blank">
|
|
||||||
Contact page opens in another tab
|
|
||||||
</NuxtLink>
|
|
||||||
<!-- <a href="/contact" target="_blank" rel="noopener noreferrer">...</a> -->
|
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::note
|
||||||
|
`noRel` and `rel` cannot be used together. `rel` will be ignored.
|
||||||
|
::
|
||||||
|
|
||||||
|
## Prefetch Links
|
||||||
|
|
||||||
|
Nuxt automatically includes smart prefetching. That means it detects when a link is visible (by default), either in the viewport or when scrolling and prefetches the JavaScript for those pages so that they are ready when the user clicks the link. Nuxt only loads the resources when the browser isn't busy and skips prefetching if your connection is offline or if you only have 2g connection.
|
||||||
|
|
||||||
|
```vue [pages/index.vue]
|
||||||
|
<NuxtLink to="/about" no-prefetch>About page not pre-fetched</NuxtLink>
|
||||||
|
<NuxtLink to="/about" :prefetch="false">About page not pre-fetched</NuxtLink>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Prefetch Triggers
|
||||||
|
|
||||||
|
We now support custom prefetch triggers for `<NuxtLink>` after `v3.13.0`. You can use the `prefetchOn` prop to control when to prefetch links.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<NuxtLink prefetch-on="visibility">
|
||||||
|
This will prefetch when it becomes visible (default)
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
|
<NuxtLink prefetch-on="interaction">
|
||||||
|
This will prefetch when hovered or when it gains focus
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `visibility`: Prefetches when the link becomes visible in the viewport. Monitors the element's intersection with the viewport using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). Prefetching is triggered when the element is scrolled into view.
|
||||||
|
- `interaction`: Prefetches when the link is hovered or focused. This approach listens for `pointerenter` and `focus` events, proactively prefetching resources when the user indicates intent to interact.
|
||||||
|
|
||||||
|
You can also use an object to configure `prefetchOn`:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<NuxtLink :prefetch-on="{ interaction: true }">
|
||||||
|
This will prefetch when hovered or when it gains focus
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
That you probably don't want both enabled!
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<NuxtLink :prefetch-on="{ visibility: true, interaction: true }">
|
||||||
|
This will prefetch when hovered/focus - or when it becomes visible
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration will observe when the element enters the viewport and also listen for `pointerenter` and `focus` events. This may result in unnecessary resource usage or redundant prefetching, as both triggers can prefetch the same resource under different conditions.
|
||||||
|
|
||||||
|
### Enable Cross-origin Prefetch
|
||||||
|
|
||||||
|
To enable cross-origin prefetching, you can set the `crossOriginPrefetch` option in your `nuxt.config`. This will enabled cross-origin prefetch using the [Speculation Rules API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API).
|
||||||
|
|
||||||
|
```ts [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
crossOriginPrefetch: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disable prefetch globally
|
||||||
|
|
||||||
|
It's also possible to enable/disable prefetching all links globally for your app.
|
||||||
|
|
||||||
|
```ts [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
defaults: {
|
||||||
|
nuxtLink: {
|
||||||
|
prefetch: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
### RouterLink
|
### RouterLink
|
||||||
@ -113,16 +224,16 @@ When not using `external`, `<NuxtLink>` supports all Vue Router's [`RouterLink`
|
|||||||
|
|
||||||
- `to`: Any URL or a [route location object](https://router.vuejs.org/api/#RouteLocation) from Vue Router
|
- `to`: Any URL or a [route location object](https://router.vuejs.org/api/#RouteLocation) from Vue Router
|
||||||
- `custom`: Whether `<NuxtLink>` should wrap its content in an `<a>` element. It allows taking full control of how a link is rendered and how navigation works when it is clicked. Works the same as [Vue Router's `custom` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-custom)
|
- `custom`: Whether `<NuxtLink>` should wrap its content in an `<a>` element. It allows taking full control of how a link is rendered and how navigation works when it is clicked. Works the same as [Vue Router's `custom` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-custom)
|
||||||
- `exactActiveClass`: A class to apply on exact active links. Works the same as [Vue Router's `exact-active-class` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-exactActiveClass) on internal links. Defaults to Vue Router's default `"router-link-exact-active"`)
|
- `exactActiveClass`: A class to apply on exact active links. Works the same as [Vue Router's `exactActiveClass` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-exactActiveClass) on internal links. Defaults to Vue Router's default (`"router-link-exact-active"`)
|
||||||
|
- `activeClass`: A class to apply on active links. Works the same as [Vue Router's `activeClass` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-activeClass) on internal links. Defaults to Vue Router's default (`"router-link-active"`)
|
||||||
- `replace`: Works the same as [Vue Router's `replace` prop](https://router.vuejs.org/api/interfaces/RouteLocationOptions.html#Properties-replace) on internal links
|
- `replace`: Works the same as [Vue Router's `replace` prop](https://router.vuejs.org/api/interfaces/RouteLocationOptions.html#Properties-replace) on internal links
|
||||||
- `ariaCurrentValue`: An `aria-current` attribute value to apply on exact active links. Works the same as [Vue Router's `aria-current-value` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-ariaCurrentValue) on internal links
|
- `ariaCurrentValue`: An `aria-current` attribute value to apply on exact active links. Works the same as [Vue Router's `ariaCurrentValue` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-ariaCurrentValue) on internal links
|
||||||
- `activeClass`: A class to apply on active links. Works the same as [Vue Router's `active-class` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-activeClass) on internal links. Defaults to Vue Router's default (`"router-link-active"`)
|
|
||||||
|
|
||||||
### NuxtLink
|
### NuxtLink
|
||||||
|
|
||||||
- `href`: An alias for `to`. If used with `to`, `href` will be ignored
|
- `href`: An alias for `to`. If used with `to`, `href` will be ignored
|
||||||
- `noRel`: If set to `true`, no `rel` attribute will be added to the link
|
- `noRel`: If set to `true`, no `rel` attribute will be added to the external link
|
||||||
- `external`: Forces the link to be rendered as an `a` tag instead of a Vue Router `RouterLink`.
|
- `external`: Forces the link to be rendered as an `<a>` tag instead of a Vue Router `RouterLink`.
|
||||||
- `prefetch`: When enabled will prefetch middleware, layouts and payloads (when using [payloadExtraction](/docs/api/nuxt-config#crossoriginprefetch)) of links in the viewport. Used by the experimental [crossOriginPrefetch](/docs/api/nuxt-config#crossoriginprefetch) config.
|
- `prefetch`: When enabled will prefetch middleware, layouts and payloads (when using [payloadExtraction](/docs/api/nuxt-config#crossoriginprefetch)) of links in the viewport. Used by the experimental [crossOriginPrefetch](/docs/api/nuxt-config#crossoriginprefetch) config.
|
||||||
- `prefetchOn`: Allows custom control of when to prefetch links. Possible options are `interaction` and `visibility` (default). You can also pass an object for full control, for example: `{ interaction: true, visibility: true }`. This prop is only used when `prefetch` is enabled (default) and `noPrefetch` is not set.
|
- `prefetchOn`: Allows custom control of when to prefetch links. Possible options are `interaction` and `visibility` (default). You can also pass an object for full control, for example: `{ interaction: true, visibility: true }`. This prop is only used when `prefetch` is enabled (default) and `noPrefetch` is not set.
|
||||||
- `noPrefetch`: Disables prefetching.
|
- `noPrefetch`: Disables prefetching.
|
||||||
@ -159,6 +270,8 @@ export default defineNuxtConfig({
|
|||||||
exactActiveClass: 'router-link-exact-active',
|
exactActiveClass: 'router-link-exact-active',
|
||||||
prefetchedClass: undefined, // can be any valid string class name
|
prefetchedClass: undefined, // can be any valid string class name
|
||||||
trailingSlash: undefined // can be 'append' or 'remove'
|
trailingSlash: undefined // can be 'append' or 'remove'
|
||||||
|
prefetch: true,
|
||||||
|
prefetchOn: { visibility: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,10 @@ const { data: posts } = await useAsyncData(
|
|||||||
## Params
|
## Params
|
||||||
|
|
||||||
- `key`: a unique key to ensure that data fetching can be properly de-duplicated across requests. If you do not provide a key, then a key that is unique to the file name and line number of the instance of `useAsyncData` will be generated for you.
|
- `key`: a unique key to ensure that data fetching can be properly de-duplicated across requests. If you do not provide a key, then a key that is unique to the file name and line number of the instance of `useAsyncData` will be generated for you.
|
||||||
- `handler`: an asynchronous function that must return a truthy value (for example, it should not be `undefined` or `null`) or the request may be duplicated on the client side
|
- `handler`: an asynchronous function that must return a truthy value (for example, it should not be `undefined` or `null`) or the request may be duplicated on the client side.
|
||||||
|
::warning
|
||||||
|
The `handler` function should be **side-effect free** to ensure predictable behavior during SSR and CSR hydration. If you need to trigger side effects, use the [`callOnce`](/docs/api/utils/call-once) utility to do so.
|
||||||
|
::
|
||||||
- `options`:
|
- `options`:
|
||||||
- `server`: whether to fetch the data on the server (defaults to `true`)
|
- `server`: whether to fetch the data on the server (defaults to `true`)
|
||||||
- `lazy`: whether to resolve the async function after loading the route, instead of blocking client-side navigation (defaults to `false`)
|
- `lazy`: whether to resolve the async function after loading the route, instead of blocking client-side navigation (defaults to `false`)
|
||||||
|
@ -4,7 +4,7 @@ description: The recommended way to provide head data with user input.
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/unjs/unhead/blob/main/packages/unhead/src/composables/useHeadSafe.ts
|
to: https://github.com/unjs/unhead/blob/main/packages/vue/src/composables.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: useHead customizes the head properties of individual pages of your
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/unjs/unhead/blob/main/packages/unhead/src/composables/useHead.ts
|
to: https://github.com/unjs/unhead/blob/main/packages/vue/src/composables.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -9,11 +9,27 @@ links:
|
|||||||
---
|
---
|
||||||
|
|
||||||
::note
|
::note
|
||||||
`useNuxtData` gives you access to the current cached value of [`useAsyncData`](/docs/api/composables/use-async-data) , `useLazyAsyncData`, [`useFetch`](/docs/api/composables/use-fetch) and [`useLazyFetch`](/docs/api/composables/use-lazy-fetch) with explicitly provided key.
|
`useNuxtData` gives you access to the current cached value of [`useAsyncData`](/docs/api/composables/use-async-data) , [`useLazyAsyncData`](/docs/api/composables/use-lazy-async-data), [`useFetch`](/docs/api/composables/use-fetch) and [`useLazyFetch`](/docs/api/composables/use-lazy-fetch) with explicitly provided key.
|
||||||
::
|
::
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
The `useNuxtData` composable is used to access the current cached value of data-fetching composables such as `useAsyncData`, `useLazyAsyncData`, `useFetch`, and `useLazyFetch`. By providing the key used during the data fetch, you can retrieve the cached data and use it as needed.
|
||||||
|
|
||||||
|
This is particularly useful for optimizing performance by reusing already-fetched data or implementing features like Optimistic Updates or cascading data updates.
|
||||||
|
|
||||||
|
To use `useNuxtData`, ensure that the data-fetching composable (`useFetch`, `useAsyncData`, etc.) has been called with an explicitly provided key.
|
||||||
|
|
||||||
|
## Params
|
||||||
|
|
||||||
|
- `key`: The unique key that identifies the cached data. This key should match the one used during the original data fetch.
|
||||||
|
|
||||||
|
## Return Values
|
||||||
|
|
||||||
|
- `data`: A reactive reference to the cached data associated with the provided key. If no cached data exists, the value will be `null`. This `Ref` automatically updates if the cached data changes, allowing seamless reactivity in your components.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
The example below shows how you can use cached data as a placeholder while the most recent data is being fetched from the server.
|
The example below shows how you can use cached data as a placeholder while the most recent data is being fetched from the server.
|
||||||
|
|
||||||
```vue [pages/posts.vue]
|
```vue [pages/posts.vue]
|
||||||
@ -26,13 +42,15 @@ const { data } = await useFetch('/api/posts', { key: 'posts' })
|
|||||||
```vue [pages/posts/[id\\].vue]
|
```vue [pages/posts/[id\\].vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// Access to the cached value of useFetch in posts.vue (parent route)
|
// Access to the cached value of useFetch in posts.vue (parent route)
|
||||||
const { id } = useRoute().params
|
|
||||||
const { data: posts } = useNuxtData('posts')
|
const { data: posts } = useNuxtData('posts')
|
||||||
const { data } = useLazyFetch(`/api/posts/${id}`, {
|
|
||||||
key: `post-${id}`,
|
const route = useRoute()
|
||||||
|
|
||||||
|
const { data } = useLazyFetch(`/api/posts/${route.params.id}`, {
|
||||||
|
key: `post-${route.params.id}`,
|
||||||
default() {
|
default() {
|
||||||
// Find the individual post from the cache and set it as the default value.
|
// Find the individual post from the cache and set it as the default value.
|
||||||
return posts.value.find(post => post.id === id)
|
return posts.value.find(post => post.id === route.params.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -40,7 +58,9 @@ const { data } = useLazyFetch(`/api/posts/${id}`, {
|
|||||||
|
|
||||||
## Optimistic Updates
|
## Optimistic Updates
|
||||||
|
|
||||||
We can leverage the cache to update the UI after a mutation, while the data is being invalidated in the background.
|
The example below shows how implementing Optimistic Updates can be achieved using useNuxtData.
|
||||||
|
|
||||||
|
Optimistic Updates is a technique where the user interface is updated immediately, assuming a server operation will succeed. If the operation eventually fails, the UI is rolled back to its previous state.
|
||||||
|
|
||||||
```vue [pages/todos.vue]
|
```vue [pages/todos.vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -52,28 +72,34 @@ const { data } = await useAsyncData('todos', () => $fetch('/api/todos'))
|
|||||||
```vue [components/NewTodo.vue]
|
```vue [components/NewTodo.vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const newTodo = ref('')
|
const newTodo = ref('')
|
||||||
const previousTodos = ref([])
|
let previousTodos = []
|
||||||
|
|
||||||
// Access to the cached value of useAsyncData in todos.vue
|
// Access to the cached value of useAsyncData in todos.vue
|
||||||
const { data: todos } = useNuxtData('todos')
|
const { data: todos } = useNuxtData('todos')
|
||||||
|
|
||||||
const { data } = await useFetch('/api/addTodo', {
|
async function addTodo () {
|
||||||
|
return $fetch('/api/addTodo', {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
body: {
|
body: {
|
||||||
todo: newTodo.value
|
todo: newTodo.value
|
||||||
},
|
},
|
||||||
onRequest () {
|
onRequest () {
|
||||||
previousTodos.value = todos.value // Store the previously cached value to restore if fetch fails.
|
// Store the previously cached value to restore if fetch fails.
|
||||||
|
previousTodos = todos.value
|
||||||
|
|
||||||
todos.value.push(newTodo.value) // Optimistically update the todos.
|
// Optimistically update the todos.
|
||||||
|
todos.value = [...todos.value, newTodo.value]
|
||||||
},
|
},
|
||||||
onRequestError () {
|
onResponseError () {
|
||||||
todos.value = previousTodos.value // Rollback the data if the request failed.
|
// Rollback the data if the request failed.
|
||||||
|
todos.value = previousTodos
|
||||||
},
|
},
|
||||||
async onResponse () {
|
async onResponse () {
|
||||||
await refreshNuxtData('todos') // Invalidate todos in the background if the request succeeded.
|
// Invalidate todos in the background if the request succeeded.
|
||||||
|
await refreshNuxtData('todos')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The useSeoMeta composable lets you define your site's SEO meta tags
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/unjs/unhead/blob/main/packages/unhead/src/composables/useSeoMeta.ts
|
to: https://github.com/unjs/unhead/blob/main/packages/vue/src/composables.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The useServerSeoMeta composable lets you define your site's SEO met
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/unjs/unhead/blob/main/packages/unhead/src/composables/useServerSeoMeta.ts
|
to: https://github.com/unjs/unhead/blob/main/packages/vue/src/composables.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -15,7 +15,12 @@ Route middleware are navigation guards stored in the [`middleware/`](/docs/guide
|
|||||||
## Type
|
## Type
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
addRouteMiddleware (name: string | RouteMiddleware, middleware?: RouteMiddleware, options: AddRouteMiddlewareOptions = {})
|
function addRouteMiddleware (name: string, middleware: RouteMiddleware, options?: AddRouteMiddlewareOptions): void
|
||||||
|
function addRouteMiddleware (middleware: RouteMiddleware): void
|
||||||
|
|
||||||
|
interface AddRouteMiddlewareOptions {
|
||||||
|
global?: boolean
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
@ -42,25 +47,9 @@ An optional `options` argument lets you set the value of `global` to `true` to i
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Anonymous Route Middleware
|
|
||||||
|
|
||||||
Anonymous route middleware does not have a name. It takes a function as the first argument, making the second `middleware` argument redundant:
|
|
||||||
|
|
||||||
```ts [plugins/my-plugin.ts]
|
|
||||||
export default defineNuxtPlugin(() => {
|
|
||||||
addRouteMiddleware((to, from) => {
|
|
||||||
if (to.path === '/forbidden') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Named Route Middleware
|
### Named Route Middleware
|
||||||
|
|
||||||
Named route middleware takes a string as the first argument and a function as the second.
|
Named route middleware is defined by providing a string as the first argument and a function as the second:
|
||||||
|
|
||||||
When defined in a plugin, it overrides any existing middleware of the same name located in the `middleware/` directory:
|
|
||||||
|
|
||||||
```ts [plugins/my-plugin.ts]
|
```ts [plugins/my-plugin.ts]
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
@ -70,16 +59,30 @@ export default defineNuxtPlugin(() => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When defined in a plugin, it overrides any existing middleware of the same name located in the `middleware/` directory.
|
||||||
|
|
||||||
### Global Route Middleware
|
### Global Route Middleware
|
||||||
|
|
||||||
Set an optional, third argument `{ global: true }` to indicate whether the route middleware is global:
|
Global route middleware can be defined in two ways:
|
||||||
|
|
||||||
```ts [plugins/my-plugin.ts]
|
- Pass a function directly as the first argument without a name. It will automatically be treated as global middleware and applied on every route change.
|
||||||
export default defineNuxtPlugin(() => {
|
|
||||||
|
```ts [plugins/my-plugin.ts]
|
||||||
|
export default defineNuxtPlugin(() => {
|
||||||
|
addRouteMiddleware((to, from) => {
|
||||||
|
console.log('anonymous global middleware that runs on every route change')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- Set an optional, third argument `{ global: true }` to indicate whether the route middleware is global.
|
||||||
|
|
||||||
|
```ts [plugins/my-plugin.ts]
|
||||||
|
export default defineNuxtPlugin(() => {
|
||||||
addRouteMiddleware('global-middleware', (to, from) => {
|
addRouteMiddleware('global-middleware', (to, from) => {
|
||||||
console.log('global middleware that runs on every route change')
|
console.log('global middleware that runs on every route change')
|
||||||
},
|
},
|
||||||
{ global: true }
|
{ global: true }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
@ -24,6 +24,8 @@ This is useful for code that should be executed only once, such as logging an ev
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
The default mode of `callOnce` is to run code only once. For example, if the code runs on the server it won't run again on the client. It also won't run again if you `callOnce` more than once on the client, for example by navigating back to this page.
|
||||||
|
|
||||||
```vue [app.vue]
|
```vue [app.vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const websiteConfig = useState('config')
|
const websiteConfig = useState('config')
|
||||||
@ -35,6 +37,23 @@ await callOnce(async () => {
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
It is also possible to run on every navigation while still avoiding the initial server/client double load. For this, it is possible to use the `navigation` mode:
|
||||||
|
|
||||||
|
```vue [app.vue]
|
||||||
|
<script setup lang="ts">
|
||||||
|
const websiteConfig = useState('config')
|
||||||
|
|
||||||
|
await callOnce(async () => {
|
||||||
|
console.log('This will only be logged once and then on every client side navigation')
|
||||||
|
websiteConfig.value = await $fetch('https://my-cms.com/api/website-config')
|
||||||
|
}, { mode: 'navigation' })
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
::important
|
||||||
|
`navigation` mode is available since [Nuxt v3.15](/blog/v3-15).
|
||||||
|
::
|
||||||
|
|
||||||
::tip{to="/docs/getting-started/state-management#usage-with-pinia"}
|
::tip{to="/docs/getting-started/state-management#usage-with-pinia"}
|
||||||
`callOnce` is useful in combination with the [Pinia module](/modules/pinia) to call store actions.
|
`callOnce` is useful in combination with the [Pinia module](/modules/pinia) to call store actions.
|
||||||
::
|
::
|
||||||
@ -52,9 +71,22 @@ Note that `callOnce` doesn't return anything. You should use [`useAsyncData`](/d
|
|||||||
## Type
|
## Type
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
callOnce(fn?: () => any | Promise<any>): Promise<void>
|
callOnce (key?: string, fn?: (() => any | Promise<any>), options?: CallOnceOptions): Promise<void>
|
||||||
callOnce(key: string, fn?: () => any | Promise<any>): Promise<void>
|
callOnce(fn?: (() => any | Promise<any>), options?: CallOnceOptions): Promise<void>
|
||||||
|
|
||||||
|
type CallOnceOptions = {
|
||||||
|
/**
|
||||||
|
* Execution mode for the callOnce function
|
||||||
|
* @default 'render'
|
||||||
|
*/
|
||||||
|
mode?: 'navigation' | 'render'
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
- `key`: A unique key ensuring that the code is run once. If you do not provide a key, then a key that is unique to the file and line number of the instance of `callOnce` will be generated for you.
|
- `key`: A unique key ensuring that the code is run once. If you do not provide a key, then a key that is unique to the file and line number of the instance of `callOnce` will be generated for you.
|
||||||
- `fn`: The function to run once. This function can also return a `Promise` and a value.
|
- `fn`: The function to run once. This function can also return a `Promise` and a value.
|
||||||
|
- `options`: Setup the mode, either to re-execute on navigation (`navigation`) or just once for the lifetime of the app (`render`). Defaults to `render`.
|
||||||
|
- `render`: Executes once during initial render (either SSR or CSR) - Default mode
|
||||||
|
- `navigation`: Executes once during initial render and once per subsequent client-side navigation
|
||||||
|
@ -4,7 +4,7 @@ description: "Scaffold an entity into your Nuxt application."
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/add.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/add.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: "Analyze the production bundle or your Nuxt application."
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/analyze.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/analyze.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: "Build your Nuxt application."
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/build.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/build.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: 'Remove common generated Nuxt files and caches.'
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/cleanup.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/cleanup.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The dev command starts a development server with hot module replace
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/dev.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/dev.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The devtools command allows you to enable or disable Nuxt DevTools
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/devtools.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/devtools.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: Pre-renders every route of the application and stores the result in
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/generate.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/generate.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The info command logs information about the current or specified Nu
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/info.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/info.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The init command initializes a fresh Nuxt project.
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/init.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/init.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: "Search and add modules to your Nuxt application with the command l
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/module/
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/module/
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The prepare command creates a .nuxt directory in your application a
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/prepare.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/prepare.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The preview command starts a server to preview your application aft
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/preview.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/preview.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The typecheck command runs vue-tsc to check types throughout your a
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/typecheck.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/typecheck.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: The upgrade command upgrades Nuxt to the latest version.
|
|||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/cli/blob/main/src/commands/upgrade.ts
|
to: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/upgrade.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
79
package.json
79
package.json
@ -12,6 +12,9 @@
|
|||||||
"build:stub": "pnpm dev:prepare",
|
"build:stub": "pnpm dev:prepare",
|
||||||
"dev": "pnpm play",
|
"dev": "pnpm play",
|
||||||
"dev:prepare": "pnpm --filter './packages/**' prepack --stub && pnpm --filter './packages/ui-templates' build",
|
"dev:prepare": "pnpm --filter './packages/**' prepack --stub && pnpm --filter './packages/ui-templates' build",
|
||||||
|
"debug:prepare": "TIMINGS_DEBUG=true pnpm dev:prepare",
|
||||||
|
"debug:build": "TIMINGS_DEBUG=true pnpm build",
|
||||||
|
"debug:dev": "rm -rf **/node_modules/.cache/jiti && pnpm nuxi dev",
|
||||||
"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",
|
||||||
@ -34,40 +37,46 @@
|
|||||||
"typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs --languages html"
|
"typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs --languages html"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
"@babel/core": "7.26.0",
|
||||||
|
"@babel/helper-plugin-utils": "7.26.5",
|
||||||
|
"@nuxt/cli": "3.20.0",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@nuxt/rspack-builder": "workspace:*",
|
"@nuxt/rspack-builder": "workspace:*",
|
||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"@nuxt/vite-builder": "workspace:*",
|
"@nuxt/vite-builder": "workspace:*",
|
||||||
"@nuxt/webpack-builder": "workspace:*",
|
"@nuxt/webpack-builder": "workspace:*",
|
||||||
"@types/node": "22.10.5",
|
"@types/node": "22.10.10",
|
||||||
"@unhead/dom": "1.11.15",
|
"@unhead/dom": "1.11.18",
|
||||||
"@unhead/schema": "1.11.15",
|
"@unhead/schema": "1.11.18",
|
||||||
"@unhead/shared": "1.11.15",
|
"@unhead/shared": "1.11.18",
|
||||||
"@unhead/ssr": "1.11.15",
|
"@unhead/ssr": "1.11.18",
|
||||||
"@unhead/vue": "1.11.15",
|
"@unhead/vue": "1.11.18",
|
||||||
"@vue/compiler-core": "3.5.13",
|
"@vue/compiler-core": "3.5.13",
|
||||||
"@vue/compiler-dom": "3.5.13",
|
"@vue/compiler-dom": "3.5.13",
|
||||||
"@vue/shared": "3.5.13",
|
"@vue/shared": "3.5.13",
|
||||||
"c12": "2.0.1",
|
"c12": "2.0.1",
|
||||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||||
"jiti": "2.4.2",
|
"jiti": "2.4.2",
|
||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.17",
|
||||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"ohash": "1.1.4",
|
"ohash": "1.1.4",
|
||||||
"postcss": "8.4.49",
|
"postcss": "8.5.1",
|
||||||
"rollup": "4.30.1",
|
"rollup": "4.31.0",
|
||||||
"send": ">=1.1.0",
|
"send": ">=1.1.0",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
"ufo": "1.5.4",
|
"ufo": "1.5.4",
|
||||||
"unbuild": "3.2.0",
|
"unbuild": "3.3.1",
|
||||||
"unhead": "1.11.15",
|
"unhead": "1.11.18",
|
||||||
"unimport": "3.14.5",
|
"unimport": "4.0.0",
|
||||||
"vite": "6.0.7",
|
"vite": "6.0.11",
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@arethetypeswrong/cli": "0.17.2",
|
"@arethetypeswrong/cli": "0.17.3",
|
||||||
|
"@babel/core": "7.26.0",
|
||||||
|
"@babel/helper-plugin-utils": "7.26.5",
|
||||||
|
"@codspeed/vitest-plugin": "4.0.0",
|
||||||
"@nuxt/cli": "3.20.0",
|
"@nuxt/cli": "3.20.0",
|
||||||
"@nuxt/eslint-config": "0.7.5",
|
"@nuxt/eslint-config": "0.7.5",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
@ -75,52 +84,58 @@
|
|||||||
"@nuxt/test-utils": "3.15.4",
|
"@nuxt/test-utils": "3.15.4",
|
||||||
"@nuxt/webpack-builder": "workspace:*",
|
"@nuxt/webpack-builder": "workspace:*",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/node": "22.10.5",
|
"@types/babel__core": "7.20.5",
|
||||||
|
"@types/babel__helper-plugin-utils": "7.10.3",
|
||||||
|
"@types/node": "22.10.10",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@unhead/schema": "1.11.15",
|
"@unhead/schema": "1.11.18",
|
||||||
"@unhead/vue": "1.11.15",
|
"@unhead/vue": "1.11.18",
|
||||||
"@vitest/coverage-v8": "2.1.8",
|
"@vitest/coverage-v8": "3.0.3",
|
||||||
"@vue/test-utils": "2.4.6",
|
"@vue/test-utils": "2.4.6",
|
||||||
|
"acorn": "8.14.0",
|
||||||
"autoprefixer": "10.4.20",
|
"autoprefixer": "10.4.20",
|
||||||
"case-police": "0.7.2",
|
"case-police": "0.7.2",
|
||||||
"changelogen": "0.5.7",
|
"changelogen": "0.5.7",
|
||||||
"consola": "3.3.3",
|
"consola": "3.4.0",
|
||||||
"cssnano": "7.0.6",
|
"cssnano": "7.0.6",
|
||||||
"destr": "2.0.3",
|
"destr": "2.0.3",
|
||||||
"devalue": "5.1.1",
|
"devalue": "5.1.1",
|
||||||
"eslint": "9.18.0",
|
"eslint": "9.18.0",
|
||||||
"eslint-plugin-no-only-tests": "3.3.0",
|
"eslint-plugin-no-only-tests": "3.3.0",
|
||||||
"eslint-plugin-perfectionist": "4.6.0",
|
"eslint-plugin-perfectionist": "4.7.0",
|
||||||
"eslint-typegen": "1.0.0",
|
"eslint-typegen": "1.0.0",
|
||||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
"estree-walker": "3.0.3",
|
||||||
"happy-dom": "16.5.3",
|
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||||
|
"happy-dom": "16.7.2",
|
||||||
"installed-check": "9.3.0",
|
"installed-check": "9.3.0",
|
||||||
"jiti": "2.4.2",
|
"jiti": "2.4.2",
|
||||||
"knip": "5.42.0",
|
"knip": "5.43.1",
|
||||||
|
"magic-string": "0.30.17",
|
||||||
"markdownlint-cli": "0.43.0",
|
"markdownlint-cli": "0.43.0",
|
||||||
"memfs": "4.17.0",
|
"memfs": "4.17.0",
|
||||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"nuxt-content-twoslash": "0.1.2",
|
"nuxt-content-twoslash": "0.1.2",
|
||||||
"ofetch": "1.4.1",
|
"ofetch": "1.4.1",
|
||||||
"pathe": "2.0.1",
|
"pathe": "2.0.2",
|
||||||
"playwright-core": "1.49.1",
|
"pkg-pr-new": "0.0.39",
|
||||||
|
"playwright-core": "1.50.0",
|
||||||
|
"rollup": "4.31.0",
|
||||||
"semver": "7.6.3",
|
"semver": "7.6.3",
|
||||||
"sherif": "1.1.1",
|
"sherif": "1.1.1",
|
||||||
"std-env": "3.8.0",
|
"std-env": "3.8.0",
|
||||||
"tinyexec": "0.3.2",
|
"tinyexec": "0.3.2",
|
||||||
"tinyglobby": "0.2.10",
|
"tinyglobby": "0.2.10",
|
||||||
|
"ts-blank-space": "0.5.0",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
"ufo": "1.5.4",
|
"ufo": "1.5.4",
|
||||||
"vitest": "2.1.8",
|
"unbuild": "3.3.1",
|
||||||
|
"vitest": "3.0.3",
|
||||||
"vitest-environment-nuxt": "1.0.1",
|
"vitest-environment-nuxt": "1.0.1",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.13",
|
||||||
"vue-tsc": "2.2.0",
|
"vue-tsc": "2.2.0",
|
||||||
"webpack": "5.97.1"
|
"webpack": "5.97.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.3",
|
"packageManager": "pnpm@9.15.4",
|
||||||
"engines": {
|
|
||||||
"node": "^18.20.4 || ^20.9.0 || ^22.0.0 || >=23.0.0"
|
|
||||||
},
|
|
||||||
"version": ""
|
"version": ""
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import { defineBuildConfig } from 'unbuild'
|
import { defineBuildConfig } from 'unbuild'
|
||||||
|
import { addRollupTimingsPlugin, stubOptions } from '../../debug/build-config'
|
||||||
|
|
||||||
export default defineBuildConfig({
|
export default defineBuildConfig({
|
||||||
declaration: true,
|
declaration: true,
|
||||||
entries: [
|
entries: [
|
||||||
'src/index',
|
'src/index',
|
||||||
],
|
],
|
||||||
|
stubOptions,
|
||||||
|
hooks: {
|
||||||
|
'rollup:options' (ctx, options) {
|
||||||
|
addRollupTimingsPlugin(options)
|
||||||
|
},
|
||||||
|
},
|
||||||
externals: [
|
externals: [
|
||||||
'@rspack/core',
|
'@rspack/core',
|
||||||
'@nuxt/schema',
|
'@nuxt/schema',
|
||||||
|
@ -29,35 +29,36 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"c12": "^2.0.1",
|
"c12": "^2.0.1",
|
||||||
"consola": "^3.3.3",
|
"consola": "^3.4.0",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"destr": "^2.0.3",
|
"destr": "^2.0.3",
|
||||||
"errx": "^0.1.0",
|
"errx": "^0.1.0",
|
||||||
"globby": "^14.0.2",
|
"globby": "^14.0.2",
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.3",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.4.2",
|
||||||
"klona": "^2.0.6",
|
"klona": "^2.0.6",
|
||||||
"mlly": "^1.7.3",
|
"mlly": "^1.7.4",
|
||||||
"ohash": "^1.1.4",
|
"ohash": "^1.1.4",
|
||||||
"pathe": "^2.0.1",
|
"pathe": "^2.0.2",
|
||||||
"pkg-types": "^1.3.0",
|
"pkg-types": "^1.3.1",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
|
"std-env": "^3.8.0",
|
||||||
"ufo": "^1.5.4",
|
"ufo": "^1.5.4",
|
||||||
"unctx": "^2.4.1",
|
"unctx": "^2.4.1",
|
||||||
"unimport": "^3.14.5",
|
"unimport": "^4.0.0",
|
||||||
"untyped": "^1.5.2"
|
"untyped": "^1.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rspack/core": "1.1.8",
|
"@rspack/core": "1.2.2",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||||
"unbuild": "3.2.0",
|
"unbuild": "3.3.1",
|
||||||
"vite": "6.0.7",
|
"vite": "6.0.11",
|
||||||
"vitest": "2.1.8",
|
"vitest": "3.0.3",
|
||||||
"webpack": "5.97.1"
|
"webpack": "5.97.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.20.5"
|
"node": ">=18.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ export function addWebpackPlugin (pluginOrGetter: WebpackPluginInstance | Webpac
|
|||||||
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
|
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
|
||||||
const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter
|
const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter
|
||||||
|
|
||||||
config.plugins = config.plugins || []
|
config.plugins ||= []
|
||||||
config.plugins[method](...toArray(plugin))
|
config.plugins[method](...toArray(plugin))
|
||||||
}, options)
|
}, options)
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ export function addRspackPlugin (pluginOrGetter: RspackPluginInstance | RspackPl
|
|||||||
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
|
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
|
||||||
const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter
|
const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter
|
||||||
|
|
||||||
config.plugins = config.plugins || []
|
config.plugins ||= []
|
||||||
config.plugins[method](...toArray(plugin))
|
config.plugins[method](...toArray(plugin))
|
||||||
}, options)
|
}, options)
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ export function addVitePlugin (pluginOrGetter: VitePlugin | VitePlugin[] | (() =
|
|||||||
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
|
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
|
||||||
const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter
|
const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter
|
||||||
|
|
||||||
config.plugins = config.plugins || []
|
config.plugins ||= []
|
||||||
config.plugins[method](...toArray(plugin))
|
config.plugins[method](...toArray(plugin))
|
||||||
}, options)
|
}, options)
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,11 @@ const builderMap = {
|
|||||||
'@nuxt/webpack-builder': 'webpack',
|
'@nuxt/webpack-builder': 'webpack',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function checkNuxtVersion (version: string, nuxt: Nuxt = useNuxt()) {
|
||||||
|
const nuxtVersion = getNuxtVersion(nuxt)
|
||||||
|
return satisfies(normalizeSemanticVersion(nuxtVersion), version, { includePrerelease: true })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check version constraints and return incompatibility issues as an array
|
* Check version constraints and return incompatibility issues as an array
|
||||||
*/
|
*/
|
||||||
@ -23,7 +28,7 @@ export async function checkNuxtCompatibility (constraints: NuxtCompatibility, nu
|
|||||||
// Nuxt version check
|
// Nuxt version check
|
||||||
if (constraints.nuxt) {
|
if (constraints.nuxt) {
|
||||||
const nuxtVersion = getNuxtVersion(nuxt)
|
const nuxtVersion = getNuxtVersion(nuxt)
|
||||||
if (!satisfies(normalizeSemanticVersion(nuxtVersion), constraints.nuxt, { includePrerelease: true })) {
|
if (!checkNuxtVersion(constraints.nuxt, nuxt)) {
|
||||||
issues.push({
|
issues.push({
|
||||||
name: 'nuxt',
|
name: 'nuxt',
|
||||||
message: `Nuxt version \`${constraints.nuxt}\` is required but currently using \`${nuxtVersion}\``,
|
message: `Nuxt version \`${constraints.nuxt}\` is required but currently using \`${nuxtVersion}\``,
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { kebabCase, pascalCase } from 'scule'
|
import { kebabCase, pascalCase } from 'scule'
|
||||||
import type { Component, ComponentsDir } from '@nuxt/schema'
|
import type { Component, ComponentsDir } from '@nuxt/schema'
|
||||||
import { useNuxt } from './context'
|
import { useNuxt } from './context'
|
||||||
import { assertNuxtCompatibility } from './compatibility'
|
|
||||||
import { logger } from './logger'
|
import { logger } from './logger'
|
||||||
import { MODE_RE } from './utils'
|
import { MODE_RE } from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a directory to be scanned for components and imported only when used.
|
* Register a directory to be scanned for components and imported only when used.
|
||||||
*/
|
*/
|
||||||
export async function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) {
|
export function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
nuxt.options.components ||= []
|
||||||
nuxt.options.components = nuxt.options.components || []
|
|
||||||
dir.priority ||= 0
|
dir.priority ||= 0
|
||||||
nuxt.hook('components:dirs', (dirs) => { dirs[opts.prepend ? 'unshift' : 'push'](dir) })
|
nuxt.hook('components:dirs', (dirs) => { dirs[opts.prepend ? 'unshift' : 'push'](dir) })
|
||||||
}
|
}
|
||||||
@ -23,10 +21,9 @@ export type AddComponentOptions = { name: string, filePath: string } & Partial<E
|
|||||||
/**
|
/**
|
||||||
* Register a component by its name and filePath.
|
* Register a component by its name and filePath.
|
||||||
*/
|
*/
|
||||||
export async function addComponent (opts: AddComponentOptions) {
|
export function addComponent (opts: AddComponentOptions) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
nuxt.options.components ||= []
|
||||||
nuxt.options.components = nuxt.options.components || []
|
|
||||||
|
|
||||||
if (!opts.mode) {
|
if (!opts.mode) {
|
||||||
const [, mode = 'all'] = opts.filePath.match(MODE_RE) || []
|
const [, mode = 'all'] = opts.filePath.match(MODE_RE) || []
|
||||||
|
@ -3,12 +3,14 @@ import ignore from 'ignore'
|
|||||||
import { join, relative, resolve } from 'pathe'
|
import { join, relative, resolve } from 'pathe'
|
||||||
import { tryUseNuxt } from './context'
|
import { tryUseNuxt } from './context'
|
||||||
|
|
||||||
|
export function createIsIgnored (nuxt = tryUseNuxt()) {
|
||||||
|
return (pathname: string, stats?: unknown) => isIgnored(pathname, stats, nuxt)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a filter function to filter an array of paths
|
* Return a filter function to filter an array of paths
|
||||||
*/
|
*/
|
||||||
export function isIgnored (pathname: string): boolean {
|
export function isIgnored (pathname: string, _stats?: unknown, nuxt = tryUseNuxt()): boolean {
|
||||||
const nuxt = tryUseNuxt()
|
|
||||||
|
|
||||||
// Happens with CLI reloads
|
// Happens with CLI reloads
|
||||||
if (!nuxt) {
|
if (!nuxt) {
|
||||||
return false
|
return false
|
||||||
|
@ -19,7 +19,7 @@ export { assertNuxtCompatibility, checkNuxtCompatibility, getNuxtVersion, hasNux
|
|||||||
export { addComponent, addComponentsDir } from './components'
|
export { addComponent, addComponentsDir } from './components'
|
||||||
export type { AddComponentOptions } from './components'
|
export type { AddComponentOptions } from './components'
|
||||||
export { getNuxtCtx, tryUseNuxt, useNuxt, nuxtCtx } from './context'
|
export { getNuxtCtx, tryUseNuxt, useNuxt, nuxtCtx } from './context'
|
||||||
export { isIgnored, resolveIgnorePatterns } from './ignore'
|
export { createIsIgnored, isIgnored, resolveIgnorePatterns } from './ignore'
|
||||||
export { addLayout } from './layout'
|
export { addLayout } from './layout'
|
||||||
export { addRouteMiddleware, extendPages, extendRouteRules } from './pages'
|
export { addRouteMiddleware, extendPages, extendRouteRules } from './pages'
|
||||||
export type { AddRouteMiddlewareOptions, ExtendRouteRulesOptions } from './pages'
|
export type { AddRouteMiddlewareOptions, ExtendRouteRulesOptions } from './pages'
|
||||||
|
@ -58,7 +58,7 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
|
|||||||
const processedLayers = new Set<string>()
|
const processedLayers = new Set<string>()
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
// Resolve `rootDir` & `srcDir` of layers
|
// Resolve `rootDir` & `srcDir` of layers
|
||||||
layer.config = layer.config || {}
|
layer.config ||= {}
|
||||||
layer.config.rootDir = layer.config.rootDir ?? layer.cwd!
|
layer.config.rootDir = layer.config.rootDir ?? layer.cwd!
|
||||||
|
|
||||||
// Only process/resolve layers once
|
// Only process/resolve layers once
|
||||||
|
@ -17,7 +17,7 @@ export interface LoadNuxtOptions extends LoadNuxtConfigOptions {
|
|||||||
export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
||||||
// Backward compatibility
|
// Backward compatibility
|
||||||
opts.cwd = resolve(opts.cwd || (opts as any).rootDir /* backwards compat */ || '.')
|
opts.cwd = resolve(opts.cwd || (opts as any).rootDir /* backwards compat */ || '.')
|
||||||
opts.overrides = opts.overrides || (opts as any).config as NuxtConfig /* backwards compat */ || {}
|
opts.overrides ||= (opts as any).config as NuxtConfig /* backwards compat */ || {}
|
||||||
|
|
||||||
// Apply dev as config override
|
// Apply dev as config override
|
||||||
opts.overrides.dev = !!opts.dev
|
opts.overrides.dev = !!opts.dev
|
||||||
|
@ -87,7 +87,7 @@ function _defineNuxtModule<
|
|||||||
// Avoid duplicate installs
|
// Avoid duplicate installs
|
||||||
const uniqueKey = module.meta.name || module.meta.configKey
|
const uniqueKey = module.meta.name || module.meta.configKey
|
||||||
if (uniqueKey) {
|
if (uniqueKey) {
|
||||||
nuxt.options._requiredModules = nuxt.options._requiredModules || {}
|
nuxt.options._requiredModules ||= {}
|
||||||
if (nuxt.options._requiredModules[uniqueKey]) {
|
if (nuxt.options._requiredModules[uniqueKey]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,11 @@ export async function installModule<
|
|||||||
> (moduleToInstall: T, inlineOptions?: [Config] extends [never] ? any : Config[1], nuxt: Nuxt = useNuxt()) {
|
> (moduleToInstall: T, inlineOptions?: [Config] extends [never] ? any : Config[1], nuxt: Nuxt = useNuxt()) {
|
||||||
const { nuxtModule, buildTimeModuleMeta, resolvedModulePath } = await loadNuxtModuleInstance(moduleToInstall, nuxt)
|
const { nuxtModule, buildTimeModuleMeta, resolvedModulePath } = await loadNuxtModuleInstance(moduleToInstall, nuxt)
|
||||||
|
|
||||||
const localLayerModuleDirs = new Set<string>()
|
const localLayerModuleDirs: string[] = []
|
||||||
for (const l of nuxt.options._layers) {
|
for (const l of nuxt.options._layers) {
|
||||||
const srcDir = l.config.srcDir || l.cwd
|
const srcDir = l.config.srcDir || l.cwd
|
||||||
if (!NODE_MODULES_RE.test(srcDir)) {
|
if (!NODE_MODULES_RE.test(srcDir)) {
|
||||||
localLayerModuleDirs.add(resolve(srcDir, l.config?.dir?.modules || 'modules'))
|
localLayerModuleDirs.push(resolve(srcDir, l.config?.dir?.modules || 'modules').replace(/\/?$/, '/'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,13 +38,13 @@ export async function installModule<
|
|||||||
const parsed = parseNodeModulePath(modulePath)
|
const parsed = parseNodeModulePath(modulePath)
|
||||||
const moduleRoot = parsed.dir ? parsed.dir + parsed.name : modulePath
|
const moduleRoot = parsed.dir ? parsed.dir + parsed.name : modulePath
|
||||||
nuxt.options.build.transpile.push(normalizeModuleTranspilePath(moduleRoot))
|
nuxt.options.build.transpile.push(normalizeModuleTranspilePath(moduleRoot))
|
||||||
const directory = parsed.dir ? moduleRoot : getDirectory(modulePath)
|
const directory = (parsed.dir ? moduleRoot : getDirectory(modulePath)).replace(/\/?$/, '/')
|
||||||
if (directory !== moduleToInstall && !localLayerModuleDirs.has(directory)) {
|
if (directory !== moduleToInstall && !localLayerModuleDirs.some(dir => directory.startsWith(dir))) {
|
||||||
nuxt.options.modulesDir.push(resolve(directory, 'node_modules'))
|
nuxt.options.modulesDir.push(resolve(directory, 'node_modules'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nuxt.options._installedModules = nuxt.options._installedModules || []
|
nuxt.options._installedModules ||= []
|
||||||
const entryPath = typeof moduleToInstall === 'string' ? resolveAlias(moduleToInstall) : undefined
|
const entryPath = typeof moduleToInstall === 'string' ? resolveAlias(moduleToInstall) : undefined
|
||||||
|
|
||||||
if (typeof moduleToInstall === 'string' && entryPath !== moduleToInstall) {
|
if (typeof moduleToInstall === 'string' && entryPath !== moduleToInstall) {
|
||||||
@ -95,11 +95,10 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
|
|||||||
paths.add(nuxtModule)
|
paths.add(nuxtModule)
|
||||||
|
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
for (const parentURL of nuxt.options.modulesDir) {
|
|
||||||
try {
|
try {
|
||||||
const src = isAbsolute(path)
|
const src = isAbsolute(path)
|
||||||
? pathToFileURL(await resolvePath(path, { cwd: parentURL, fallbackToOriginal: false, extensions: nuxt.options.extensions })).href
|
? pathToFileURL(await resolvePath(path, { fallbackToOriginal: false, extensions: nuxt.options.extensions })).href
|
||||||
: await resolveModule(path, { url: pathToFileURL(parentURL.replace(/\/node_modules\/?$/, '')), extensions: nuxt.options.extensions })
|
: await resolveModule(path, { url: nuxt.options.modulesDir.map(m => pathToFileURL(m.replace(/\/node_modules\/?$/, ''))), extensions: nuxt.options.extensions })
|
||||||
|
|
||||||
nuxtModule = await jiti.import(src, { default: true }) as NuxtModule
|
nuxtModule = await jiti.import(src, { default: true }) as NuxtModule
|
||||||
resolvedModulePath = fileURLToPath(new URL(src))
|
resolvedModulePath = fileURLToPath(new URL(src))
|
||||||
@ -119,8 +118,6 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof nuxtModule !== 'string') { break }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw error if module could not be found
|
// Throw error if module could not be found
|
||||||
|
@ -40,7 +40,7 @@ export function addDevServerHandler (handler: NitroDevEventHandler) {
|
|||||||
*/
|
*/
|
||||||
export function addServerPlugin (plugin: string) {
|
export function addServerPlugin (plugin: string) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
nuxt.options.nitro.plugins = nuxt.options.nitro.plugins || []
|
nuxt.options.nitro.plugins ||= []
|
||||||
nuxt.options.nitro.plugins.push(normalize(plugin))
|
nuxt.options.nitro.plugins.push(normalize(plugin))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ export function useNitro (): Nitro {
|
|||||||
export function addServerImports (imports: Import[]) {
|
export function addServerImports (imports: Import[]) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
nuxt.hook('nitro:config', (config) => {
|
nuxt.hook('nitro:config', (config) => {
|
||||||
config.imports = config.imports || {}
|
config.imports ||= {}
|
||||||
config.imports.imports = config.imports.imports || []
|
config.imports.imports ||= []
|
||||||
config.imports.imports.push(...imports)
|
config.imports.imports.push(...imports)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -102,8 +102,8 @@ export function addServerImportsDir (dirs: string | string[], opts: { prepend?:
|
|||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
const _dirs = toArray(dirs)
|
const _dirs = toArray(dirs)
|
||||||
nuxt.hook('nitro:config', (config) => {
|
nuxt.hook('nitro:config', (config) => {
|
||||||
config.imports = config.imports || {}
|
config.imports ||= {}
|
||||||
config.imports.dirs = config.imports.dirs || []
|
config.imports.dirs ||= []
|
||||||
config.imports.dirs[opts.prepend ? 'unshift' : 'push'](..._dirs)
|
config.imports.dirs[opts.prepend ? 'unshift' : 'push'](..._dirs)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@ export function addServerImportsDir (dirs: string | string[], opts: { prepend?:
|
|||||||
export function addServerScanDir (dirs: string | string[], opts: { prepend?: boolean } = {}) {
|
export function addServerScanDir (dirs: string | string[], opts: { prepend?: boolean } = {}) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
nuxt.hook('nitro:config', (config) => {
|
nuxt.hook('nitro:config', (config) => {
|
||||||
config.scanDirs = config.scanDirs || []
|
config.scanDirs ||= []
|
||||||
|
|
||||||
for (const dir of toArray(dirs)) {
|
for (const dir of toArray(dirs)) {
|
||||||
config.scanDirs[opts.prepend ? 'unshift' : 'push'](dir)
|
config.scanDirs[opts.prepend ? 'unshift' : 'push'](dir)
|
||||||
|
@ -20,9 +20,7 @@ export interface ExtendRouteRulesOptions {
|
|||||||
export function extendRouteRules (route: string, rule: NitroRouteConfig, options: ExtendRouteRulesOptions = {}) {
|
export function extendRouteRules (route: string, rule: NitroRouteConfig, options: ExtendRouteRulesOptions = {}) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
for (const opts of [nuxt.options, nuxt.options.nitro]) {
|
for (const opts of [nuxt.options, nuxt.options.nitro]) {
|
||||||
if (!opts.routeRules) {
|
opts.routeRules ||= {}
|
||||||
opts.routeRules = {}
|
|
||||||
}
|
|
||||||
opts.routeRules[route] = options.override
|
opts.routeRules[route] = options.override
|
||||||
? defu(rule, opts.routeRules[route])
|
? defu(rule, opts.routeRules[route])
|
||||||
: defu(opts.routeRules[route], rule)
|
: defu(opts.routeRules[route], rule)
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
|
import { existsSync } from 'node:fs'
|
||||||
|
import { isAbsolute } from 'node:path'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
import { normalize } from 'pathe'
|
import { normalize } from 'pathe'
|
||||||
import type { NuxtPlugin, NuxtPluginTemplate } from '@nuxt/schema'
|
import type { NuxtPlugin, NuxtPluginTemplate } from '@nuxt/schema'
|
||||||
import { useNuxt } from './context'
|
import { resolvePathSync } from 'mlly'
|
||||||
|
import { isWindows } from 'std-env'
|
||||||
|
import { MODE_RE, filterInPlace } from './utils'
|
||||||
|
import { tryUseNuxt, useNuxt } from './context'
|
||||||
import { addTemplate } from './template'
|
import { addTemplate } from './template'
|
||||||
import { resolveAlias } from './resolve'
|
import { resolveAlias } from './resolve'
|
||||||
import { MODE_RE } from './utils'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize a nuxt plugin object
|
* Normalize a nuxt plugin object
|
||||||
*/
|
*/
|
||||||
|
const pluginSymbol = Symbol.for('nuxt plugin')
|
||||||
export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
|
export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
|
||||||
// Normalize src
|
// Normalize src
|
||||||
if (typeof plugin === 'string') {
|
if (typeof plugin === 'string') {
|
||||||
@ -16,6 +22,10 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
|
|||||||
plugin = { ...plugin }
|
plugin = { ...plugin }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pluginSymbol in plugin) {
|
||||||
|
return plugin
|
||||||
|
}
|
||||||
|
|
||||||
if (!plugin.src) {
|
if (!plugin.src) {
|
||||||
throw new Error('Invalid plugin. src option is required: ' + JSON.stringify(plugin))
|
throw new Error('Invalid plugin. src option is required: ' + JSON.stringify(plugin))
|
||||||
}
|
}
|
||||||
@ -23,6 +33,14 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
|
|||||||
// Normalize full path to plugin
|
// Normalize full path to plugin
|
||||||
plugin.src = normalize(resolveAlias(plugin.src))
|
plugin.src = normalize(resolveAlias(plugin.src))
|
||||||
|
|
||||||
|
if (!existsSync(plugin.src) && isAbsolute(plugin.src)) {
|
||||||
|
try {
|
||||||
|
plugin.src = resolvePathSync(isWindows ? pathToFileURL(plugin.src).href : plugin.src, { extensions: tryUseNuxt()?.options.extensions })
|
||||||
|
} catch {
|
||||||
|
// ignore errors as the file may be in the nuxt vfs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize mode
|
// Normalize mode
|
||||||
if (plugin.ssr) {
|
if (plugin.ssr) {
|
||||||
plugin.mode = 'server'
|
plugin.mode = 'server'
|
||||||
@ -32,6 +50,9 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
|
|||||||
plugin.mode = mode as 'all' | 'client' | 'server'
|
plugin.mode = mode as 'all' | 'client' | 'server'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error not adding symbol to types to avoid conflicts
|
||||||
|
plugin[pluginSymbol] = true
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +82,7 @@ export function addPlugin (_plugin: NuxtPlugin | string, opts: AddPluginOptions
|
|||||||
const plugin = normalizePlugin(_plugin)
|
const plugin = normalizePlugin(_plugin)
|
||||||
|
|
||||||
// Remove any existing plugin with the same src
|
// Remove any existing plugin with the same src
|
||||||
nuxt.options.plugins = nuxt.options.plugins.filter(p => normalizePlugin(p).src !== plugin.src)
|
filterInPlace(nuxt.options.plugins, p => normalizePlugin(p).src !== plugin.src)
|
||||||
|
|
||||||
// Prepend to array by default to be before user provided plugins since is usually used by modules
|
// Prepend to array by default to be before user provided plugins since is usually used by modules
|
||||||
nuxt.options.plugins[opts.append ? 'push' : 'unshift'](plugin)
|
nuxt.options.plugins[opts.append ? 'push' : 'unshift'](plugin)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { existsSync, promises as fsp } from 'node:fs'
|
import { promises as fsp } from 'node:fs'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import { basename, dirname, isAbsolute, join, normalize, resolve } from 'pathe'
|
import { basename, dirname, isAbsolute, join, normalize, resolve } from 'pathe'
|
||||||
import { globby } from 'globby'
|
import { globby } from 'globby'
|
||||||
@ -38,97 +38,30 @@ export interface ResolvePathOptions {
|
|||||||
* If path could not be resolved, normalized input path will be returned
|
* If path could not be resolved, normalized input path will be returned
|
||||||
*/
|
*/
|
||||||
export async function resolvePath (path: string, opts: ResolvePathOptions = {}): Promise<string> {
|
export async function resolvePath (path: string, opts: ResolvePathOptions = {}): Promise<string> {
|
||||||
// Always normalize input
|
const res = await _resolvePathGranularly(path, opts)
|
||||||
const _path = path
|
|
||||||
path = normalize(path)
|
|
||||||
|
|
||||||
// Fast return if the path exists
|
if (res.type === 'file') {
|
||||||
if (isAbsolute(path)) {
|
return res.path
|
||||||
if (opts?.virtual && existsInVFS(path)) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
if (existsSync(path) && !(await isDirectory(path))) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use current nuxt options
|
|
||||||
const nuxt = tryUseNuxt()
|
|
||||||
const cwd = opts.cwd || (nuxt ? nuxt.options.rootDir : process.cwd())
|
|
||||||
const extensions = opts.extensions || (nuxt ? nuxt.options.extensions : ['.ts', '.mjs', '.cjs', '.json'])
|
|
||||||
const modulesDir = nuxt ? nuxt.options.modulesDir : []
|
|
||||||
|
|
||||||
// Resolve aliases
|
|
||||||
path = resolveAlias(path)
|
|
||||||
|
|
||||||
// Resolve relative to cwd
|
|
||||||
if (!isAbsolute(path)) {
|
|
||||||
path = resolve(cwd, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if resolvedPath is a file
|
|
||||||
if (opts?.virtual && existsInVFS(path, nuxt)) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
let _isDir = false
|
|
||||||
if (existsSync(path)) {
|
|
||||||
_isDir = await isDirectory(path)
|
|
||||||
if (!_isDir) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check possible extensions
|
|
||||||
for (const ext of extensions) {
|
|
||||||
// path.[ext]
|
|
||||||
const pathWithExt = path + ext
|
|
||||||
if (opts?.virtual && existsInVFS(pathWithExt, nuxt)) {
|
|
||||||
return pathWithExt
|
|
||||||
}
|
|
||||||
if (existsSync(pathWithExt)) {
|
|
||||||
return pathWithExt
|
|
||||||
}
|
|
||||||
// path/index.[ext]
|
|
||||||
const pathWithIndex = join(path, 'index' + ext)
|
|
||||||
if (opts?.virtual && existsInVFS(pathWithIndex, nuxt)) {
|
|
||||||
return pathWithIndex
|
|
||||||
}
|
|
||||||
if (_isDir && existsSync(pathWithIndex)) {
|
|
||||||
return pathWithIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to resolve as module id
|
|
||||||
const resolveModulePath = await _resolvePath(_path, { url: [cwd, ...modulesDir] }).catch(() => null)
|
|
||||||
if (resolveModulePath) {
|
|
||||||
return resolveModulePath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return normalized input
|
// Return normalized input
|
||||||
return opts.fallbackToOriginal ? _path : path
|
return opts.fallbackToOriginal ? path : res.path
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 res = await _resolvePathGranularly(path, opts)
|
||||||
|
|
||||||
// Check VFS
|
if (!res.type || (pathType && res.type !== pathType)) {
|
||||||
if (opts?.virtual && existsInVFS(rPath, nuxt)) {
|
continue
|
||||||
return rPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file system
|
// Check file system
|
||||||
if (await existsSensitive(rPath)) {
|
if (res.virtual || await existsSensitive(res.path)) {
|
||||||
const _isDir = await isDirectory(rPath)
|
return res.path
|
||||||
if (!pathType || (pathType === 'file' && !_isDir) || (pathType === 'dir' && _isDir)) {
|
|
||||||
return rPath
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -186,15 +119,106 @@ export async function resolveNuxtModule (base: string, paths: string[]): Promise
|
|||||||
|
|
||||||
// --- Internal ---
|
// --- Internal ---
|
||||||
|
|
||||||
async function existsSensitive (path: string) {
|
interface PathResolution {
|
||||||
if (!existsSync(path)) { return false }
|
path: string
|
||||||
const dirFiles = await fsp.readdir(dirname(path))
|
type?: 'file' | 'dir'
|
||||||
return dirFiles.includes(basename(path))
|
virtual?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage note: We assume path existence is already ensured
|
async function _resolvePathType (path: string, opts: ResolvePathOptions = {}, skipFs = false): Promise<PathResolution | undefined> {
|
||||||
async function isDirectory (path: string) {
|
if (opts?.virtual && existsInVFS(path)) {
|
||||||
return (await fsp.lstat(path)).isDirectory()
|
return {
|
||||||
|
path,
|
||||||
|
type: 'file',
|
||||||
|
virtual: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipFs) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fd = await fsp.open(path, 'r').catch(() => null)
|
||||||
|
try {
|
||||||
|
const stats = await fd?.stat()
|
||||||
|
if (stats) {
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
type: stats.isFile() ? 'file' : 'dir',
|
||||||
|
virtual: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fd?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _resolvePathGranularly (path: string, opts: ResolvePathOptions = {}): Promise<PathResolution> {
|
||||||
|
// Always normalize input
|
||||||
|
const _path = path
|
||||||
|
path = normalize(path)
|
||||||
|
|
||||||
|
// Fast return if the path exists
|
||||||
|
if (isAbsolute(path)) {
|
||||||
|
const res = await _resolvePathType(path, opts)
|
||||||
|
if (res && res.type === 'file') {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use current nuxt options
|
||||||
|
const nuxt = tryUseNuxt()
|
||||||
|
const cwd = opts.cwd || (nuxt ? nuxt.options.rootDir : process.cwd())
|
||||||
|
const extensions = opts.extensions || (nuxt ? nuxt.options.extensions : ['.ts', '.mjs', '.cjs', '.json'])
|
||||||
|
const modulesDir = nuxt ? nuxt.options.modulesDir : []
|
||||||
|
|
||||||
|
// Resolve aliases
|
||||||
|
path = resolveAlias(path)
|
||||||
|
|
||||||
|
// Resolve relative to cwd
|
||||||
|
if (!isAbsolute(path)) {
|
||||||
|
path = resolve(cwd, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await _resolvePathType(path, opts)
|
||||||
|
if (res && res.type === 'file') {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check possible extensions
|
||||||
|
for (const ext of extensions) {
|
||||||
|
// path.[ext]
|
||||||
|
const extPath = await _resolvePathType(path + ext, opts)
|
||||||
|
if (extPath && extPath.type === 'file') {
|
||||||
|
return extPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// path/index.[ext]
|
||||||
|
const indexPath = await _resolvePathType(join(path, 'index' + ext), opts, res?.type !== 'dir' /* skip checking if parent is not a directory */)
|
||||||
|
if (indexPath && indexPath.type === 'file') {
|
||||||
|
return indexPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to resolve as module id
|
||||||
|
const resolvedModulePath = await _resolvePath(_path, { url: [cwd, ...modulesDir] }).catch(() => null)
|
||||||
|
if (resolvedModulePath) {
|
||||||
|
return {
|
||||||
|
path: resolvedModulePath,
|
||||||
|
type: 'file',
|
||||||
|
virtual: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return normalized input
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function existsSensitive (path: string) {
|
||||||
|
const dirFiles = await fsp.readdir(dirname(path)).catch(() => null)
|
||||||
|
return dirFiles && dirFiles.includes(basename(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
function existsInVFS (path: string, nuxt = tryUseNuxt()) {
|
function existsInVFS (path: string, nuxt = tryUseNuxt()) {
|
||||||
|
@ -8,6 +8,7 @@ import type { TSConfig } from 'pkg-types'
|
|||||||
import { gte } from 'semver'
|
import { gte } from 'semver'
|
||||||
import { readPackageJSON } from 'pkg-types'
|
import { readPackageJSON } from 'pkg-types'
|
||||||
|
|
||||||
|
import { filterInPlace } from './utils'
|
||||||
import { tryResolveModule } from './internal/esm'
|
import { tryResolveModule } from './internal/esm'
|
||||||
import { getDirectory } from './module/install'
|
import { getDirectory } from './module/install'
|
||||||
import { tryUseNuxt, useNuxt } from './context'
|
import { tryUseNuxt, useNuxt } from './context'
|
||||||
@ -23,7 +24,7 @@ export function addTemplate<T> (_template: NuxtTemplate<T> | string) {
|
|||||||
const template = normalizeTemplate(_template)
|
const template = normalizeTemplate(_template)
|
||||||
|
|
||||||
// Remove any existing template with the same destination path
|
// Remove any existing template with the same destination path
|
||||||
nuxt.options.build.templates = nuxt.options.build.templates.filter(p => normalizeTemplate(p).dst !== template.dst)
|
filterInPlace(nuxt.options.build.templates, p => normalizeTemplate(p).dst !== template.dst)
|
||||||
|
|
||||||
// Add to templates array
|
// Add to templates array
|
||||||
nuxt.options.build.templates.push(template)
|
nuxt.options.build.templates.push(template)
|
||||||
@ -229,9 +230,9 @@ export async function _generateTypes (nuxt: Nuxt) {
|
|||||||
? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl)
|
? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl)
|
||||||
: nuxt.options.buildDir
|
: nuxt.options.buildDir
|
||||||
|
|
||||||
tsConfig.compilerOptions = tsConfig.compilerOptions || {}
|
tsConfig.compilerOptions ||= {}
|
||||||
tsConfig.compilerOptions.paths = tsConfig.compilerOptions.paths || {}
|
tsConfig.compilerOptions.paths ||= {}
|
||||||
tsConfig.include = tsConfig.include || []
|
tsConfig.include ||= []
|
||||||
|
|
||||||
for (const alias in aliases) {
|
for (const alias in aliases) {
|
||||||
if (excludedAlias.some(re => re.test(alias))) {
|
if (excludedAlias.some(re => re.test(alias))) {
|
||||||
|
@ -5,6 +5,21 @@ export function toArray<T> (value: T | T[]): T[] {
|
|||||||
return Array.isArray(value) ? value : [value]
|
return Array.isArray(value) ? value : [value]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter out items from an array in place. This function mutates the array.
|
||||||
|
* `predicate` get through the array from the end to the start for performance.
|
||||||
|
*
|
||||||
|
* This function should be faster than `Array.prototype.filter` on large arrays.
|
||||||
|
*/
|
||||||
|
export function filterInPlace<T> (array: T[], predicate: (item: T, index: number, arr: T[]) => unknown) {
|
||||||
|
for (let i = array.length; i--; i >= 0) {
|
||||||
|
if (!predicate(array[i]!, i, array)) {
|
||||||
|
array.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
export const MODE_RE = /\.(server|client)(\.\w+)*$/
|
export const MODE_RE = /\.(server|client)(\.\w+)*$/
|
||||||
/**
|
/**
|
||||||
* async local storage for the name of the current nuxt instance
|
* async local storage for the name of the current nuxt instance
|
||||||
|
@ -2,7 +2,7 @@ import { fileURLToPath } from 'node:url'
|
|||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { join, normalize } from 'pathe'
|
import { join, normalize } from 'pathe'
|
||||||
import { withoutTrailingSlash } from 'ufo'
|
import { withoutTrailingSlash } from 'ufo'
|
||||||
import { loadNuxtConfig } from '../src'
|
import { loadNuxtConfig } from '@nuxt/kit'
|
||||||
|
|
||||||
const fixtures = {
|
const fixtures = {
|
||||||
'empty directory': 'node_modules/fixture',
|
'empty directory': 'node_modules/fixture',
|
||||||
@ -16,7 +16,7 @@ describe('loadNuxtConfig', () => {
|
|||||||
for (const fixture in fixtures) {
|
for (const fixture in fixtures) {
|
||||||
const relativeDir = join('../../..', fixtures[fixture as keyof typeof fixtures])
|
const relativeDir = join('../../..', fixtures[fixture as keyof typeof fixtures])
|
||||||
const path = withoutTrailingSlash(normalize(fileURLToPath(new URL(relativeDir, import.meta.url))))
|
const path = withoutTrailingSlash(normalize(fileURLToPath(new URL(relativeDir, import.meta.url))))
|
||||||
bench(fixture, async () => {
|
bench(`loadNuxtConfig in the ${fixture}`, async () => {
|
||||||
await loadNuxtConfig({ cwd: path })
|
await loadNuxtConfig({ cwd: path })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { fileURLToPath } from 'node:url'
|
|||||||
import { afterAll, bench, describe } from 'vitest'
|
import { afterAll, bench, describe } from 'vitest'
|
||||||
import { join, normalize } from 'pathe'
|
import { join, normalize } from 'pathe'
|
||||||
import { withoutTrailingSlash } from 'ufo'
|
import { withoutTrailingSlash } from 'ufo'
|
||||||
import { loadNuxt, writeTypes } from '../src'
|
import { loadNuxt, writeTypes } from '@nuxt/kit'
|
||||||
|
|
||||||
describe('writeTypes', async () => {
|
describe('writeTypes', async () => {
|
||||||
const relativeDir = join('../../..', 'test/fixtures/basic-types')
|
const relativeDir = join('../../..', 'test/fixtures/basic-types')
|
||||||
@ -13,7 +13,7 @@ describe('writeTypes', async () => {
|
|||||||
await nuxt.close()
|
await nuxt.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
bench('write types', async () => {
|
bench('writeTypes in the basic-types fixture', async () => {
|
||||||
await writeTypes(nuxt)
|
await writeTypes(nuxt)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { BuildEntry } from 'unbuild'
|
import type { BuildEntry } from 'unbuild'
|
||||||
import { defineBuildConfig } from 'unbuild'
|
import { defineBuildConfig } from 'unbuild'
|
||||||
|
import { addRollupTimingsPlugin, stubOptions } from '../../debug/build-config'
|
||||||
|
|
||||||
export default defineBuildConfig({
|
export default defineBuildConfig({
|
||||||
declaration: true,
|
declaration: true,
|
||||||
@ -16,10 +17,14 @@ export default defineBuildConfig({
|
|||||||
'pages',
|
'pages',
|
||||||
].map(name => ({ input: `src/${name}/runtime/`, outDir: `dist/${name}/runtime`, format: 'esm', ext: 'js' } as BuildEntry)),
|
].map(name => ({ input: `src/${name}/runtime/`, outDir: `dist/${name}/runtime`, format: 'esm', ext: 'js' } as BuildEntry)),
|
||||||
],
|
],
|
||||||
|
stubOptions,
|
||||||
hooks: {
|
hooks: {
|
||||||
'mkdist:entry:options' (_ctx, _entry, mkdistOptions) {
|
'mkdist:entry:options' (_ctx, _entry, mkdistOptions) {
|
||||||
mkdistOptions.addRelativeDeclarationExtensions = true
|
mkdistOptions.addRelativeDeclarationExtensions = true
|
||||||
},
|
},
|
||||||
|
'rollup:options' (ctx, options) {
|
||||||
|
addRollupTimingsPlugin(options)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dependencies: [
|
dependencies: [
|
||||||
'@nuxt/cli',
|
'@nuxt/cli',
|
||||||
|
@ -71,16 +71,16 @@
|
|||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"@nuxt/telemetry": "^2.6.4",
|
"@nuxt/telemetry": "^2.6.4",
|
||||||
"@nuxt/vite-builder": "workspace:*",
|
"@nuxt/vite-builder": "workspace:*",
|
||||||
"@unhead/dom": "^1.11.15",
|
"@unhead/dom": "^1.11.18",
|
||||||
"@unhead/shared": "^1.11.15",
|
"@unhead/shared": "^1.11.18",
|
||||||
"@unhead/ssr": "^1.11.15",
|
"@unhead/ssr": "^1.11.18",
|
||||||
"@unhead/vue": "^1.11.15",
|
"@unhead/vue": "^1.11.18",
|
||||||
"@vue/shared": "^3.5.13",
|
"@vue/shared": "^3.5.13",
|
||||||
"acorn": "8.14.0",
|
"acorn": "8.14.0",
|
||||||
"c12": "^2.0.1",
|
"c12": "^2.0.1",
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^4.0.3",
|
||||||
"compatx": "^0.1.8",
|
"compatx": "^0.1.8",
|
||||||
"consola": "^3.3.3",
|
"consola": "^3.4.0",
|
||||||
"cookie-es": "^1.2.2",
|
"cookie-es": "^1.2.2",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"destr": "^2.0.3",
|
"destr": "^2.0.3",
|
||||||
@ -90,38 +90,38 @@
|
|||||||
"escape-string-regexp": "^5.0.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
"estree-walker": "^3.0.3",
|
"estree-walker": "^3.0.3",
|
||||||
"globby": "^14.0.2",
|
"globby": "^14.0.2",
|
||||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||||
"hookable": "^5.5.3",
|
"hookable": "^5.5.3",
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.3",
|
||||||
"impound": "^0.2.0",
|
"impound": "^0.2.0",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.4.2",
|
||||||
"klona": "^2.0.6",
|
"klona": "^2.0.6",
|
||||||
"knitwork": "^1.2.0",
|
"knitwork": "^1.2.0",
|
||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.17",
|
||||||
"mlly": "^1.7.3",
|
"mlly": "^1.7.4",
|
||||||
"nanotar": "^0.1.1",
|
"nanotar": "^0.2.0",
|
||||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||||
"nypm": "^0.4.1",
|
"nypm": "^0.5.0",
|
||||||
"ofetch": "^1.4.1",
|
"ofetch": "^1.4.1",
|
||||||
"ohash": "^1.1.4",
|
"ohash": "^1.1.4",
|
||||||
"pathe": "^2.0.1",
|
"pathe": "^2.0.2",
|
||||||
"perfect-debounce": "^1.0.0",
|
"perfect-debounce": "^1.0.0",
|
||||||
"pkg-types": "^1.3.0",
|
"pkg-types": "^1.3.1",
|
||||||
"radix3": "^1.1.2",
|
"radix3": "^1.1.2",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"std-env": "^3.8.0",
|
"std-env": "^3.8.0",
|
||||||
"strip-literal": "^2.1.1",
|
"strip-literal": "^3.0.0",
|
||||||
"tinyglobby": "0.2.10",
|
"tinyglobby": "0.2.10",
|
||||||
"ufo": "^1.5.4",
|
"ufo": "^1.5.4",
|
||||||
"ultrahtml": "^1.5.3",
|
"ultrahtml": "^1.5.3",
|
||||||
"uncrypto": "^0.1.3",
|
"uncrypto": "^0.1.3",
|
||||||
"unctx": "^2.4.1",
|
"unctx": "^2.4.1",
|
||||||
"unenv": "^1.10.0",
|
"unenv": "^1.10.0",
|
||||||
"unhead": "^1.11.15",
|
"unhead": "^1.11.18",
|
||||||
"unimport": "^3.14.5",
|
"unimport": "^4.0.0",
|
||||||
"unplugin": "^2.1.2",
|
"unplugin": "^2.1.2",
|
||||||
"unplugin-vue-router": "^0.10.9",
|
"unplugin-vue-router": "^0.11.1",
|
||||||
"unstorage": "^1.14.4",
|
"unstorage": "^1.14.4",
|
||||||
"untyped": "^1.5.2",
|
"untyped": "^1.5.2",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
@ -135,9 +135,9 @@
|
|||||||
"@types/estree": "1.0.6",
|
"@types/estree": "1.0.6",
|
||||||
"@vitejs/plugin-vue": "5.2.1",
|
"@vitejs/plugin-vue": "5.2.1",
|
||||||
"@vue/compiler-sfc": "3.5.13",
|
"@vue/compiler-sfc": "3.5.13",
|
||||||
"unbuild": "3.2.0",
|
"unbuild": "3.3.1",
|
||||||
"vite": "6.0.7",
|
"vite": "6.0.11",
|
||||||
"vitest": "2.1.8"
|
"vitest": "3.0.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@parcel/watcher": "^2.1.0",
|
"@parcel/watcher": "^2.1.0",
|
||||||
|
@ -14,7 +14,7 @@ export default defineComponent({
|
|||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
|
||||||
props: ['fallback', 'placeholder', 'placeholderTag', 'fallbackTag'],
|
props: ['fallback', 'placeholder', 'placeholderTag', 'fallbackTag'],
|
||||||
setup (_, { slots, attrs }) {
|
setup (props, { slots, attrs }) {
|
||||||
const mounted = ref(false)
|
const mounted = ref(false)
|
||||||
onMounted(() => { mounted.value = true })
|
onMounted(() => { mounted.value = true })
|
||||||
// Bail out of checking for pages/layouts as they might be included under `<ClientOnly>` 🤷♂️
|
// Bail out of checking for pages/layouts as they might be included under `<ClientOnly>` 🤷♂️
|
||||||
@ -24,10 +24,10 @@ export default defineComponent({
|
|||||||
nuxtApp._isNuxtLayoutUsed = true
|
nuxtApp._isNuxtLayoutUsed = true
|
||||||
}
|
}
|
||||||
provide(clientOnlySymbol, true)
|
provide(clientOnlySymbol, true)
|
||||||
return (props: any) => {
|
return () => {
|
||||||
if (mounted.value) { return slots.default?.() }
|
if (mounted.value) { return slots.default?.() }
|
||||||
const slot = slots.fallback || slots.placeholder
|
const slot = slots.fallback || slots.placeholder
|
||||||
if (slot) { return slot() }
|
if (slot) { return h(slot) }
|
||||||
const fallbackStr = props.fallback || props.placeholder || ''
|
const fallbackStr = props.fallback || props.placeholder || ''
|
||||||
const fallbackTag = props.fallbackTag || props.placeholderTag || 'span'
|
const fallbackTag = props.fallbackTag || props.placeholderTag || 'span'
|
||||||
return createElementBlock(fallbackTag, attrs, fallbackStr)
|
return createElementBlock(fallbackTag, attrs, fallbackStr)
|
||||||
@ -95,7 +95,7 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
|
|||||||
if (isPromise(setupState)) {
|
if (isPromise(setupState)) {
|
||||||
return Promise.resolve(setupState).then((setupState) => {
|
return Promise.resolve(setupState).then((setupState) => {
|
||||||
if (typeof setupState !== 'function') {
|
if (typeof setupState !== 'function') {
|
||||||
setupState = setupState || {}
|
setupState ||= {}
|
||||||
setupState.mounted$ = mounted$
|
setupState.mounted$ = mounted$
|
||||||
return setupState
|
return setupState
|
||||||
}
|
}
|
||||||
|
@ -325,10 +325,13 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
const elRef = import.meta.server ? undefined : (ref: any) => { el!.value = props.custom ? ref?.$el?.nextElementSibling : ref?.$el }
|
const elRef = import.meta.server ? undefined : (ref: any) => { el!.value = props.custom ? ref?.$el?.nextElementSibling : ref?.$el }
|
||||||
|
|
||||||
function shouldPrefetch (mode: 'visibility' | 'interaction') {
|
function shouldPrefetch (mode: 'visibility' | 'interaction') {
|
||||||
|
if (import.meta.server) { return }
|
||||||
return !prefetched.value && (typeof props.prefetchOn === 'string' ? props.prefetchOn === mode : (props.prefetchOn?.[mode] ?? options.prefetchOn?.[mode])) && (props.prefetch ?? options.prefetch) !== false && props.noPrefetch !== true && props.target !== '_blank' && !isSlowConnection()
|
return !prefetched.value && (typeof props.prefetchOn === 'string' ? props.prefetchOn === mode : (props.prefetchOn?.[mode] ?? options.prefetchOn?.[mode])) && (props.prefetch ?? options.prefetch) !== false && props.noPrefetch !== true && props.target !== '_blank' && !isSlowConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function prefetch (nuxtApp = useNuxtApp()) {
|
async function prefetch (nuxtApp = useNuxtApp()) {
|
||||||
|
if (import.meta.server) { return }
|
||||||
|
|
||||||
if (prefetched.value) { return }
|
if (prefetched.value) { return }
|
||||||
|
|
||||||
prefetched.value = true
|
prefetched.value = true
|
||||||
@ -395,6 +398,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
// `custom` API cannot support fallthrough attributes as the slot
|
// `custom` API cannot support fallthrough attributes as the slot
|
||||||
// may render fragment or text root nodes (#14897, #19375)
|
// may render fragment or text root nodes (#14897, #19375)
|
||||||
if (!props.custom) {
|
if (!props.custom) {
|
||||||
|
if (import.meta.client) {
|
||||||
if (shouldPrefetch('interaction')) {
|
if (shouldPrefetch('interaction')) {
|
||||||
routerLinkProps.onPointerenter = prefetch.bind(null, undefined)
|
routerLinkProps.onPointerenter = prefetch.bind(null, undefined)
|
||||||
routerLinkProps.onFocus = prefetch.bind(null, undefined)
|
routerLinkProps.onFocus = prefetch.bind(null, undefined)
|
||||||
@ -402,6 +406,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
if (prefetched.value) {
|
if (prefetched.value) {
|
||||||
routerLinkProps.class = props.prefetchedClass || options.prefetchedClass
|
routerLinkProps.class = props.prefetchedClass || options.prefetchedClass
|
||||||
}
|
}
|
||||||
|
}
|
||||||
routerLinkProps.rel = props.rel || undefined
|
routerLinkProps.rel = props.rel || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export async function callOnce (...args: any): Promise<void> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nuxtApp._once = nuxtApp._once || {}
|
nuxtApp._once ||= {}
|
||||||
nuxtApp._once[_key] = nuxtApp._once[_key] || fn() || true
|
nuxtApp._once[_key] = nuxtApp._once[_key] || fn() || true
|
||||||
await nuxtApp._once[_key]
|
await nuxtApp._once[_key]
|
||||||
nuxtApp.payload.once.add(_key)
|
nuxtApp.payload.once.add(_key)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { existsSync, statSync, writeFileSync } from 'node:fs'
|
import { existsSync, statSync, writeFileSync } from 'node:fs'
|
||||||
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
|
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
|
||||||
import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, defineNuxtModule, findPath, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit'
|
import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, defineNuxtModule, findPath, resolveAlias, resolvePath } from '@nuxt/kit'
|
||||||
import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema'
|
import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema'
|
||||||
|
|
||||||
import { distDir } from '../dirs'
|
import { distDir } from '../dirs'
|
||||||
@ -198,24 +198,6 @@ export default defineNuxtModule<ComponentsOptions>({
|
|||||||
tsConfig.compilerOptions!.paths['#components'] = [resolve(nuxt.options.buildDir, 'components')]
|
tsConfig.compilerOptions!.paths['#components'] = [resolve(nuxt.options.buildDir, 'components')]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Watch for changes
|
|
||||||
nuxt.hook('builder:watch', async (event, relativePath) => {
|
|
||||||
if (!['add', 'unlink'].includes(event)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const path = resolve(nuxt.options.srcDir, relativePath)
|
|
||||||
if (componentDirs.some(dir => path.startsWith(dir.path + '/'))) {
|
|
||||||
await updateTemplates({
|
|
||||||
filter: template => [
|
|
||||||
'components.plugin.mjs',
|
|
||||||
'components.d.ts',
|
|
||||||
'components.server.mjs',
|
|
||||||
'components.client.mjs',
|
|
||||||
].includes(template.filename),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
addBuildPlugin(TreeShakeTemplatePlugin({ sourcemap: !!nuxt.options.sourcemap.server, getComponents }), { client: false })
|
addBuildPlugin(TreeShakeTemplatePlugin({ sourcemap: !!nuxt.options.sourcemap.server, getComponents }), { client: false })
|
||||||
|
|
||||||
const sharedLoaderOptions = {
|
const sharedLoaderOptions = {
|
||||||
@ -250,7 +232,7 @@ export default defineNuxtModule<ComponentsOptions>({
|
|||||||
|
|
||||||
// TODO: refactor this
|
// TODO: refactor this
|
||||||
nuxt.hook('vite:extendConfig', (config, { isClient }) => {
|
nuxt.hook('vite:extendConfig', (config, { isClient }) => {
|
||||||
config.plugins = config.plugins || []
|
config.plugins ||= []
|
||||||
|
|
||||||
if (isClient && selectiveClient) {
|
if (isClient && selectiveClient) {
|
||||||
writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}')
|
writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}')
|
||||||
@ -275,7 +257,7 @@ export default defineNuxtModule<ComponentsOptions>({
|
|||||||
nuxt.hook(key, (configs) => {
|
nuxt.hook(key, (configs) => {
|
||||||
configs.forEach((config) => {
|
configs.forEach((config) => {
|
||||||
const mode = config.name === 'client' ? 'client' : 'server'
|
const mode = config.name === 'client' ? 'client' : 'server'
|
||||||
config.plugins = config.plugins || []
|
config.plugins ||= []
|
||||||
|
|
||||||
if (mode !== 'server') {
|
if (mode !== 'server') {
|
||||||
writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}')
|
writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}')
|
||||||
|
@ -58,7 +58,7 @@ export function TransformPlugin (nuxt: Nuxt, options: TransformPluginOptions) {
|
|||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
transformInclude (id) {
|
transformInclude (id) {
|
||||||
id = normalize(id)
|
id = normalize(id)
|
||||||
return id.startsWith('virtual:') || id.startsWith('\0virtual:') || id.startsWith(nuxt.options.buildDir) || !isIgnored(id)
|
return id.startsWith('virtual:') || id.startsWith('\0virtual:') || id.startsWith(nuxt.options.buildDir) || !isIgnored(id, undefined, nuxt)
|
||||||
},
|
},
|
||||||
async transform (code, id) {
|
async transform (code, id) {
|
||||||
// Virtual component wrapper
|
// Virtual component wrapper
|
||||||
|
@ -57,6 +57,7 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
|||||||
const templateContext = { nuxt, app }
|
const templateContext = { nuxt, app }
|
||||||
|
|
||||||
const writes: Array<() => void> = []
|
const writes: Array<() => void> = []
|
||||||
|
const dirs = new Set<string>()
|
||||||
const changedTemplates: Array<ResolvedNuxtTemplate<any>> = []
|
const changedTemplates: Array<ResolvedNuxtTemplate<any>> = []
|
||||||
const FORWARD_SLASH_RE = /\//g
|
const FORWARD_SLASH_RE = /\//g
|
||||||
async function processTemplate (template: ResolvedNuxtTemplate) {
|
async function processTemplate (template: ResolvedNuxtTemplate) {
|
||||||
@ -92,10 +93,8 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (template.modified && template.write) {
|
if (template.modified && template.write) {
|
||||||
writes.push(() => {
|
dirs.add(dirname(fullPath))
|
||||||
mkdirSync(dirname(fullPath), { recursive: true })
|
writes.push(() => writeFileSync(fullPath, contents, 'utf8'))
|
||||||
writeFileSync(fullPath, contents, 'utf8')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +103,12 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
|||||||
|
|
||||||
// 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 dir of dirs) {
|
||||||
|
mkdirSync(dir, { recursive: true })
|
||||||
|
}
|
||||||
|
for (const write of writes) {
|
||||||
|
write()
|
||||||
|
}
|
||||||
|
|
||||||
if (changedTemplates.length) {
|
if (changedTemplates.length) {
|
||||||
await nuxt.callHook('app:templatesGenerated', app, changedTemplates, options)
|
await nuxt.callHook('app:templatesGenerated', app, changedTemplates, options)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { EventType } from '@parcel/watcher'
|
import type { EventType } from '@parcel/watcher'
|
||||||
import type { FSWatcher } from 'chokidar'
|
import type { FSWatcher } from 'chokidar'
|
||||||
import { watch as chokidarWatch } from 'chokidar'
|
import { watch as chokidarWatch } from 'chokidar'
|
||||||
import { importModule, isIgnored, tryResolveModule, useNuxt } from '@nuxt/kit'
|
import { createIsIgnored, importModule, isIgnored, tryResolveModule, useNuxt } from '@nuxt/kit'
|
||||||
import { debounce } from 'perfect-debounce'
|
import { debounce } from 'perfect-debounce'
|
||||||
import { normalize, relative, resolve } from 'pathe'
|
import { normalize, relative, resolve } from 'pathe'
|
||||||
import type { Nuxt, NuxtBuilder } from 'nuxt/schema'
|
import type { Nuxt, NuxtBuilder } from 'nuxt/schema'
|
||||||
@ -63,8 +63,11 @@ export async function build (nuxt: Nuxt) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nuxt.options.dev) {
|
if (nuxt.options.dev && !nuxt.options.test) {
|
||||||
|
nuxt.hooks.hookOnce('build:done', () => {
|
||||||
checkForExternalConfigurationFiles()
|
checkForExternalConfigurationFiles()
|
||||||
|
.catch(e => logger.warn('Problem checking for external configuration files.', e))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await bundle(nuxt)
|
await bundle(nuxt)
|
||||||
@ -97,14 +100,12 @@ async function watch (nuxt: Nuxt) {
|
|||||||
|
|
||||||
function createWatcher () {
|
function createWatcher () {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
|
const isIgnored = createIsIgnored(nuxt)
|
||||||
|
|
||||||
const watcher = chokidarWatch(nuxt.options._layers.map(i => i.config.srcDir as string).filter(Boolean), {
|
const watcher = chokidarWatch(nuxt.options._layers.map(i => i.config.srcDir as string).filter(Boolean), {
|
||||||
...nuxt.options.watchers.chokidar,
|
...nuxt.options.watchers.chokidar,
|
||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
ignored: [
|
ignored: [isIgnored, /[\\/]node_modules[\\/]/],
|
||||||
isIgnored,
|
|
||||||
'node_modules',
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watcher.on('all', (event, path) => {
|
watcher.on('all', (event, path) => {
|
||||||
@ -118,6 +119,7 @@ function createWatcher () {
|
|||||||
|
|
||||||
function createGranularWatcher () {
|
function createGranularWatcher () {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
|
const isIgnored = createIsIgnored(nuxt)
|
||||||
|
|
||||||
if (nuxt.options.debug) {
|
if (nuxt.options.debug) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -136,7 +138,7 @@ function createGranularWatcher () {
|
|||||||
}
|
}
|
||||||
for (const dir of pathsToWatch) {
|
for (const dir of pathsToWatch) {
|
||||||
pending++
|
pending++
|
||||||
const watcher = chokidarWatch(dir, { ...nuxt.options.watchers.chokidar, ignoreInitial: false, depth: 0, ignored: [isIgnored, '**/node_modules'] })
|
const watcher = chokidarWatch(dir, { ...nuxt.options.watchers.chokidar, ignoreInitial: false, depth: 0, ignored: [isIgnored, /[\\/]node_modules[\\/]/] })
|
||||||
const watchers: Record<string, FSWatcher> = {}
|
const watchers: Record<string, FSWatcher> = {}
|
||||||
|
|
||||||
watcher.on('all', (event, path) => {
|
watcher.on('all', (event, path) => {
|
||||||
|
@ -2,7 +2,7 @@ import { mkdir, open, readFile, stat, unlink, writeFile } from 'node:fs/promises
|
|||||||
import type { FileHandle } from 'node:fs/promises'
|
import type { FileHandle } from 'node:fs/promises'
|
||||||
import { resolve } from 'node:path'
|
import { resolve } from 'node:path'
|
||||||
import { existsSync } from 'node:fs'
|
import { existsSync } from 'node:fs'
|
||||||
import { isIgnored } from '@nuxt/kit'
|
import { createIsIgnored } from '@nuxt/kit'
|
||||||
import type { Nuxt, NuxtConfig, NuxtConfigLayer } from '@nuxt/schema'
|
import type { Nuxt, NuxtConfig, NuxtConfigLayer } from '@nuxt/schema'
|
||||||
import { hash, murmurHash, objectHash } from 'ohash'
|
import { hash, murmurHash, objectHash } from 'ohash'
|
||||||
import { glob } from 'tinyglobby'
|
import { glob } from 'tinyglobby'
|
||||||
@ -119,6 +119,7 @@ async function getHashes (nuxt: Nuxt, options: GetHashOptions): Promise<Hashes>
|
|||||||
data: murmurHash(f.data as any /* ArrayBuffer */),
|
data: murmurHash(f.data as any /* ArrayBuffer */),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const isIgnored = createIsIgnored(nuxt)
|
||||||
const sourceFiles = await readFilesRecursive(options.cwd(layer), {
|
const sourceFiles = await readFilesRecursive(options.cwd(layer), {
|
||||||
shouldIgnore: isIgnored, // TODO: Validate if works with absolute paths
|
shouldIgnore: isIgnored, // TODO: Validate if works with absolute paths
|
||||||
cwd: nuxt.options.rootDir,
|
cwd: nuxt.options.rootDir,
|
||||||
|
@ -239,7 +239,11 @@ 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.push(
|
||||||
|
...resolveIgnorePatterns(nitroConfig.srcDir),
|
||||||
|
`!${join(nuxt.options.buildDir, 'dist/client', nuxt.options.app.buildAssetsDir, '**/*')}`,
|
||||||
|
)
|
||||||
|
|
||||||
// Resolve aliases in user-provided input - so `~/server/test` will work
|
// Resolve aliases in user-provided input - so `~/server/test` will work
|
||||||
nitroConfig.plugins = nitroConfig.plugins?.map(plugin => plugin ? resolveAlias(plugin, nuxt.options.alias) : plugin)
|
nitroConfig.plugins = nitroConfig.plugins?.map(plugin => plugin ? resolveAlias(plugin, nuxt.options.alias) : plugin)
|
||||||
@ -411,14 +415,16 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
const basePath = nitroConfig.typescript!.tsConfig!.compilerOptions?.baseUrl ? resolve(nuxt.options.buildDir, nitroConfig.typescript!.tsConfig!.compilerOptions?.baseUrl) : nuxt.options.buildDir
|
const basePath = nitroConfig.typescript!.tsConfig!.compilerOptions?.baseUrl ? resolve(nuxt.options.buildDir, nitroConfig.typescript!.tsConfig!.compilerOptions?.baseUrl) : nuxt.options.buildDir
|
||||||
const aliases = nitroConfig.alias!
|
const aliases = nitroConfig.alias!
|
||||||
const tsConfig = nitroConfig.typescript!.tsConfig!
|
const tsConfig = nitroConfig.typescript!.tsConfig!
|
||||||
tsConfig.compilerOptions = tsConfig.compilerOptions || {}
|
tsConfig.compilerOptions ||= {}
|
||||||
tsConfig.compilerOptions.paths = tsConfig.compilerOptions.paths || {}
|
tsConfig.compilerOptions.paths ||= {}
|
||||||
for (const _alias in aliases) {
|
for (const _alias in aliases) {
|
||||||
const alias = _alias as keyof typeof aliases
|
const alias = _alias as keyof typeof aliases
|
||||||
if (excludedAlias.some(pattern => typeof pattern === 'string' ? alias === pattern : pattern.test(alias))) {
|
if (excludedAlias.some(pattern => typeof pattern === 'string' ? alias === pattern : pattern.test(alias))) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (alias in tsConfig.compilerOptions.paths) { continue }
|
if (alias in tsConfig.compilerOptions.paths) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
const absolutePath = resolve(basePath, aliases[alias]!)
|
const absolutePath = resolve(basePath, aliases[alias]!)
|
||||||
const stats = await fsp.stat(absolutePath).catch(() => null /* file does not exist */)
|
const stats = await fsp.stat(absolutePath).catch(() => null /* file does not exist */)
|
||||||
@ -532,7 +538,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
await writeTypes(nitro)
|
await writeTypes(nitro)
|
||||||
}
|
}
|
||||||
// Exclude nitro output dir from typescript
|
// Exclude nitro output dir from typescript
|
||||||
opts.tsConfig.exclude = opts.tsConfig.exclude || []
|
opts.tsConfig.exclude ||= []
|
||||||
opts.tsConfig.exclude.push(relative(nuxt.options.buildDir, resolve(nuxt.options.rootDir, nitro.options.output.dir)))
|
opts.tsConfig.exclude.push(relative(nuxt.options.buildDir, resolve(nuxt.options.rootDir, nitro.options.output.dir)))
|
||||||
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/nitro.d.ts') })
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/nitro.d.ts') })
|
||||||
})
|
})
|
||||||
|
@ -105,7 +105,7 @@ const nightlies = {
|
|||||||
'@nuxt/kit': '@nuxt/kit-nightly',
|
'@nuxt/kit': '@nuxt/kit-nightly',
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyDependencies = [
|
export const keyDependencies = [
|
||||||
'@nuxt/kit',
|
'@nuxt/kit',
|
||||||
'@nuxt/schema',
|
'@nuxt/schema',
|
||||||
]
|
]
|
||||||
@ -423,8 +423,11 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
...nuxt.options._layers.filter(i => i.cwd.includes('node_modules')).map(i => i.cwd as string),
|
...nuxt.options._layers.filter(i => i.cwd.includes('node_modules')).map(i => i.cwd as string),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure we can resolve dependencies within layers
|
// Ensure we can resolve dependencies within layers - filtering out local `~/layers` directories
|
||||||
nuxt.options.modulesDir.push(...nuxt.options._layers.map(l => resolve(l.cwd, 'node_modules')))
|
const locallyScannedLayersDirs = nuxt.options._layers.map(l => resolve(l.cwd, 'layers').replace(/\/?$/, '/'))
|
||||||
|
nuxt.options.modulesDir.push(...nuxt.options._layers
|
||||||
|
.filter(l => l.cwd !== nuxt.options.rootDir && locallyScannedLayersDirs.every(dir => !l.cwd.startsWith(dir)))
|
||||||
|
.map(l => resolve(l.cwd, 'node_modules')))
|
||||||
|
|
||||||
// Init user modules
|
// Init user modules
|
||||||
await nuxt.callHook('modules:before')
|
await nuxt.callHook('modules:before')
|
||||||
@ -826,6 +829,15 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
|||||||
checkDependencyVersion(dep, nuxt._version)
|
checkDependencyVersion(dep, nuxt._version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nuxt.options.dev && !nuxt.options.test) {
|
||||||
|
nuxt.hooks.hookOnce('build:done', () => {
|
||||||
|
for (const dep of keyDependencies) {
|
||||||
|
checkDependencyVersion(dep, nuxt._version)
|
||||||
|
.catch(e => logger.warn(`Problem checking \`${dep}\` version.`, e))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// We register hooks layer-by-layer so any overrides need to be registered separately
|
// We register hooks layer-by-layer so any overrides need to be registered separately
|
||||||
if (opts.overrides?.hooks) {
|
if (opts.overrides?.hooks) {
|
||||||
nuxt.hooks.addHooks(opts.overrides.hooks)
|
nuxt.hooks.addHooks(opts.overrides.hooks)
|
||||||
@ -843,7 +855,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
|||||||
return nuxt
|
return nuxt
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkDependencyVersion (name: string, nuxtVersion: string): Promise<void> {
|
export async function checkDependencyVersion (name: string, nuxtVersion: string): Promise<void> {
|
||||||
const path = await resolvePath(name, { fallbackToOriginal: true }).catch(() => null)
|
const path = await resolvePath(name, { fallbackToOriginal: true }).catch(() => null)
|
||||||
|
|
||||||
if (!path || path === name) { return }
|
if (!path || path === name) { return }
|
||||||
|
@ -36,7 +36,7 @@ export const createImportProtectionPatterns = (nuxt: { options: NuxtOptions }, o
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:node_modules|presets|runtime|types))/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) {
|
for (const i of [/(^|node_modules\/)@nuxt\/(cli|kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:node_modules|presets|runtime|types))/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) {
|
||||||
patterns.push([i, `This module cannot be imported in ${context}.`])
|
patterns.push([i, `This module cannot be imported in ${context}.`])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,9 @@ export async function extractMetadata (code: string, loader = 'ts' as 'ts' | 'ts
|
|||||||
if (metaCache[code]) {
|
if (metaCache[code]) {
|
||||||
return metaCache[code]
|
return metaCache[code]
|
||||||
}
|
}
|
||||||
|
if (code.match(/defineNuxtPlugin\s*\([\w(]/)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
const js = await transform(code, { loader })
|
const js = await transform(code, { loader })
|
||||||
parseAndWalk(js.code, `file.${loader}`, (node) => {
|
parseAndWalk(js.code, `file.${loader}`, (node) => {
|
||||||
if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return }
|
if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return }
|
||||||
|
@ -38,25 +38,25 @@ export const VirtualFSPlugin = (nuxt: Nuxt, options: VirtualFSPluginOptions) =>
|
|||||||
|
|
||||||
const resolvedId = resolveWithExt(id)
|
const resolvedId = resolveWithExt(id)
|
||||||
if (resolvedId) {
|
if (resolvedId) {
|
||||||
return PREFIX + resolvedId
|
return PREFIX + encodeURIComponent(resolvedId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importer && RELATIVE_ID_RE.test(id)) {
|
if (importer && RELATIVE_ID_RE.test(id)) {
|
||||||
const path = resolve(dirname(withoutPrefix(importer)), id)
|
const path = resolve(dirname(withoutPrefix(decodeURIComponent(importer))), id)
|
||||||
const resolved = resolveWithExt(path)
|
const resolved = resolveWithExt(path)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
return PREFIX + resolved
|
return PREFIX + encodeURIComponent(resolved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
loadInclude (id) {
|
loadInclude (id) {
|
||||||
return id.startsWith(PREFIX) && withoutPrefix(id) in nuxt.vfs
|
return id.startsWith(PREFIX) && withoutPrefix(decodeURIComponent(id)) in nuxt.vfs
|
||||||
},
|
},
|
||||||
|
|
||||||
load (id) {
|
load (id) {
|
||||||
return {
|
return {
|
||||||
code: nuxt.vfs[withoutPrefix(id)] || '',
|
code: nuxt.vfs[withoutPrefix(decodeURIComponent(id))] || '',
|
||||||
map: null,
|
map: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ import { defineEventHandler, getRequestHeader } from 'h3'
|
|||||||
|
|
||||||
export default defineEventHandler((event) => {
|
export default defineEventHandler((event) => {
|
||||||
if (getRequestHeader(event, 'x-nuxt-no-ssr')) {
|
if (getRequestHeader(event, 'x-nuxt-no-ssr')) {
|
||||||
event.context.nuxt = event.context.nuxt || {}
|
event.context.nuxt ||= {}
|
||||||
event.context.nuxt.noSSR = true
|
event.context.nuxt.noSSR = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -488,8 +488,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove for v4
|
// TODO: remove for v4
|
||||||
islandHead.link = islandHead.link || []
|
islandHead.link ||= []
|
||||||
islandHead.style = islandHead.style || []
|
islandHead.style ||= []
|
||||||
|
|
||||||
const islandResponse: NuxtIslandResponse = {
|
const islandResponse: NuxtIslandResponse = {
|
||||||
id: islandContext.id,
|
id: islandContext.id,
|
||||||
|
@ -5,11 +5,8 @@ import { resolve } from 'pathe'
|
|||||||
import { watch } from 'chokidar'
|
import { watch } from 'chokidar'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import { debounce } from 'perfect-debounce'
|
import { debounce } from 'perfect-debounce'
|
||||||
import { createResolver, defineNuxtModule, importModule, tryResolveModule } from '@nuxt/kit'
|
import { createIsIgnored, createResolver, defineNuxtModule, importModule, tryResolveModule } from '@nuxt/kit'
|
||||||
import {
|
import { generateTypes, resolveSchema as resolveUntypedSchema } from 'untyped'
|
||||||
generateTypes,
|
|
||||||
resolveSchema as resolveUntypedSchema,
|
|
||||||
} from 'untyped'
|
|
||||||
import type { Schema, SchemaDefinition } from 'untyped'
|
import type { Schema, SchemaDefinition } from 'untyped'
|
||||||
import untypedPlugin from 'untyped/babel-plugin'
|
import untypedPlugin from 'untyped/babel-plugin'
|
||||||
import { createJiti } from 'jiti'
|
import { createJiti } from 'jiti'
|
||||||
@ -71,11 +68,17 @@ export default defineNuxtModule({
|
|||||||
logger.warn('Falling back to `chokidar` as `@parcel/watcher` cannot be resolved in your project.')
|
logger.warn('Falling back to `chokidar` as `@parcel/watcher` cannot be resolved in your project.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesToWatch = await Promise.all(nuxt.options._layers.map(layer =>
|
const isIgnored = createIsIgnored(nuxt)
|
||||||
resolver.resolve(layer.config.rootDir, 'nuxt.schema.*'),
|
const dirsToWatch = nuxt.options._layers.map(layer => resolver.resolve(layer.config.rootDir))
|
||||||
))
|
const SCHEMA_RE = /(?:^|\/)nuxt.schema.\w+$/
|
||||||
const watcher = watch(filesToWatch, {
|
const watcher = watch(dirsToWatch, {
|
||||||
...nuxt.options.watchers.chokidar,
|
...nuxt.options.watchers.chokidar,
|
||||||
|
depth: 1,
|
||||||
|
ignored: [
|
||||||
|
(path, stats) => (stats && !stats.isFile()) || !SCHEMA_RE.test(path),
|
||||||
|
isIgnored,
|
||||||
|
/[\\/]node_modules[\\/]/,
|
||||||
|
],
|
||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
})
|
})
|
||||||
watcher.on('all', onChange)
|
watcher.on('all', onChange)
|
||||||
|
@ -68,7 +68,7 @@ export const clientPluginTemplate: NuxtTemplate = {
|
|||||||
const imports: string[] = []
|
const imports: string[] = []
|
||||||
for (const plugin of clientPlugins) {
|
for (const plugin of clientPlugins) {
|
||||||
const path = relative(ctx.nuxt.options.rootDir, plugin.src)
|
const path = relative(ctx.nuxt.options.rootDir, plugin.src)
|
||||||
const variable = genSafeVariableName(filename(plugin.src)).replace(PLUGIN_TEMPLATE_RE, '_') + '_' + hash(path)
|
const variable = genSafeVariableName(filename(plugin.src) || path).replace(PLUGIN_TEMPLATE_RE, '_') + '_' + hash(path)
|
||||||
exports.push(variable)
|
exports.push(variable)
|
||||||
imports.push(genImport(plugin.src, variable))
|
imports.push(genImport(plugin.src, variable))
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ export const serverPluginTemplate: NuxtTemplate = {
|
|||||||
const imports: string[] = []
|
const imports: string[] = []
|
||||||
for (const plugin of serverPlugins) {
|
for (const plugin of serverPlugins) {
|
||||||
const path = relative(ctx.nuxt.options.rootDir, plugin.src)
|
const path = relative(ctx.nuxt.options.rootDir, plugin.src)
|
||||||
const variable = genSafeVariableName(filename(path)).replace(PLUGIN_TEMPLATE_RE, '_') + '_' + hash(path)
|
const variable = genSafeVariableName(filename(plugin.src) || path).replace(PLUGIN_TEMPLATE_RE, '_') + '_' + hash(path)
|
||||||
exports.push(variable)
|
exports.push(variable)
|
||||||
imports.push(genImport(plugin.src, variable))
|
imports.push(genImport(plugin.src, variable))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { existsSync } from 'node:fs'
|
import { existsSync } from 'node:fs'
|
||||||
import { addBuildPlugin, addTemplate, addTypeTemplate, defineNuxtModule, isIgnored, resolveAlias, tryResolveModule, updateTemplates, useNuxt } from '@nuxt/kit'
|
import { addBuildPlugin, addTemplate, addTypeTemplate, createIsIgnored, defineNuxtModule, resolveAlias, tryResolveModule, updateTemplates, useNuxt } from '@nuxt/kit'
|
||||||
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
|
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
|
||||||
import type { Import, Unimport } from 'unimport'
|
import type { Import, Unimport } from 'unimport'
|
||||||
import { createUnimport, scanDirExports, toExports } from 'unimport'
|
import { createUnimport, scanDirExports, toExports } from 'unimport'
|
||||||
@ -118,6 +118,7 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
|
|||||||
return IMPORTS_TEMPLATE_RE.test(template.filename)
|
return IMPORTS_TEMPLATE_RE.test(template.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isIgnored = createIsIgnored(nuxt)
|
||||||
const regenerateImports = async () => {
|
const regenerateImports = async () => {
|
||||||
await ctx.modifyDynamicImports(async (imports) => {
|
await ctx.modifyDynamicImports(async (imports) => {
|
||||||
// Clear old imports
|
// Clear old imports
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { existsSync, readdirSync } from 'node:fs'
|
import { existsSync, readdirSync } from 'node:fs'
|
||||||
import { mkdir, readFile } from 'node:fs/promises'
|
import { mkdir, readFile } from 'node:fs/promises'
|
||||||
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, defineNuxtModule, findPath, resolvePath, updateTemplates, useNitro } from '@nuxt/kit'
|
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, defineNuxtModule, findPath, resolvePath, useNitro } from '@nuxt/kit'
|
||||||
import { dirname, join, relative, resolve } from 'pathe'
|
import { dirname, join, relative, resolve } from 'pathe'
|
||||||
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
|
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
|
||||||
import { joinURL } from 'ufo'
|
import { joinURL } from 'ufo'
|
||||||
@ -93,10 +93,8 @@ export default defineNuxtModule({
|
|||||||
addPlugin(resolve(runtimeDir, 'plugins/check-if-page-unused'))
|
addPlugin(resolve(runtimeDir, 'plugins/check-if-page-unused'))
|
||||||
}
|
}
|
||||||
|
|
||||||
nuxt.hook('app:templates', async (app) => {
|
nuxt.hook('app:templates', (app) => {
|
||||||
app.pages = await resolvePagesRoutes(nuxt)
|
if (!nuxt.options.ssr && app.pages?.some(p => p.mode === 'server')) {
|
||||||
|
|
||||||
if (!nuxt.options.ssr && app.pages.some(p => p.mode === 'server')) {
|
|
||||||
logger.warn('Using server pages with `ssr: false` is not supported with auto-detected component islands. Set `experimental.componentIslands` to `true`.')
|
logger.warn('Using server pages with `ssr: false` is not supported with auto-detected component islands. Set `experimental.componentIslands` to `true`.')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -269,6 +267,13 @@ export default defineNuxtModule({
|
|||||||
if (!pages) { return false }
|
if (!pages) { return false }
|
||||||
return pages.some(page => page.file === file) || pages.some(page => page.children && isPage(file, page.children))
|
return pages.some(page => page.file === file) || pages.some(page => page.children && isPage(file, page.children))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nuxt.hooks.hookOnce('app:templates', async (app) => {
|
||||||
|
if (!app.pages) {
|
||||||
|
app.pages = await resolvePagesRoutes(nuxt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
nuxt.hook('builder:watch', async (event, relativePath) => {
|
nuxt.hook('builder:watch', async (event, relativePath) => {
|
||||||
const path = resolve(nuxt.options.srcDir, relativePath)
|
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||||
const shouldAlwaysRegenerate = nuxt.options.experimental.scanPageMeta && isPage(path)
|
const shouldAlwaysRegenerate = nuxt.options.experimental.scanPageMeta && isPage(path)
|
||||||
@ -276,9 +281,7 @@ export default defineNuxtModule({
|
|||||||
if (event === 'change' && !shouldAlwaysRegenerate) { return }
|
if (event === 'change' && !shouldAlwaysRegenerate) { return }
|
||||||
|
|
||||||
if (shouldAlwaysRegenerate || updateTemplatePaths.some(dir => path.startsWith(dir))) {
|
if (shouldAlwaysRegenerate || updateTemplatePaths.some(dir => path.startsWith(dir))) {
|
||||||
await updateTemplates({
|
nuxt.apps.default!.pages = await resolvePagesRoutes(nuxt)
|
||||||
filter: template => template.filename === 'routes.mjs',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -530,8 +533,8 @@ export default defineNuxtModule({
|
|||||||
getContents: () => 'export { START_LOCATION, useRoute } from \'vue-router\'',
|
getContents: () => 'export { START_LOCATION, useRoute } from \'vue-router\'',
|
||||||
})
|
})
|
||||||
|
|
||||||
nuxt.options.vite.resolve = nuxt.options.vite.resolve || {}
|
nuxt.options.vite.resolve ||= {}
|
||||||
nuxt.options.vite.resolve.dedupe = nuxt.options.vite.resolve.dedupe || []
|
nuxt.options.vite.resolve.dedupe ||= []
|
||||||
nuxt.options.vite.resolve.dedupe.push('vue-router')
|
nuxt.options.vite.resolve.dedupe.push('vue-router')
|
||||||
|
|
||||||
// Add router options template
|
// Add router options template
|
||||||
|
@ -287,9 +287,9 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
|||||||
handleHotUpdate: {
|
handleHotUpdate: {
|
||||||
order: 'post',
|
order: 'post',
|
||||||
handler: ({ file, modules, server }) => {
|
handler: ({ file, modules, server }) => {
|
||||||
if (options.isPage?.(file)) {
|
if (options.routesPath && options.isPage?.(file)) {
|
||||||
const macroModule = server.moduleGraph.getModuleById(file + '?macro=true')
|
const macroModule = server.moduleGraph.getModuleById(file + '?macro=true')
|
||||||
const routesModule = server.moduleGraph.getModuleById('virtual:nuxt:' + options.routesPath)
|
const routesModule = server.moduleGraph.getModuleById('virtual:nuxt:' + encodeURIComponent(options.routesPath))
|
||||||
return [
|
return [
|
||||||
...modules,
|
...modules,
|
||||||
...macroModule ? [macroModule] : [],
|
...macroModule ? [macroModule] : [],
|
||||||
|
@ -65,7 +65,7 @@ export default defineComponent({
|
|||||||
if (import.meta.dev) {
|
if (import.meta.dev) {
|
||||||
nuxtApp._isNuxtPageUsed = true
|
nuxtApp._isNuxtPageUsed = true
|
||||||
}
|
}
|
||||||
|
let pageLoadingEndHookAlreadyCalled = false
|
||||||
return () => {
|
return () => {
|
||||||
return h(RouterView, { name: props.name, route: props.route, ...attrs }, {
|
return h(RouterView, { name: props.name, route: props.route, ...attrs }, {
|
||||||
default: (routeProps: RouterViewSlotProps) => {
|
default: (routeProps: RouterViewSlotProps) => {
|
||||||
@ -99,6 +99,7 @@ export default defineComponent({
|
|||||||
const key = generateRouteKey(routeProps, props.pageKey)
|
const key = generateRouteKey(routeProps, props.pageKey)
|
||||||
if (!nuxtApp.isHydrating && !hasChildrenRoutes(forkRoute, routeProps.route, routeProps.Component) && previousPageKey === key) {
|
if (!nuxtApp.isHydrating && !hasChildrenRoutes(forkRoute, routeProps.route, routeProps.Component) && previousPageKey === key) {
|
||||||
nuxtApp.callHook('page:loading:end')
|
nuxtApp.callHook('page:loading:end')
|
||||||
|
pageLoadingEndHookAlreadyCalled = true
|
||||||
}
|
}
|
||||||
previousPageKey = key
|
previousPageKey = key
|
||||||
|
|
||||||
@ -115,7 +116,14 @@ export default defineComponent({
|
|||||||
wrapInKeepAlive(keepaliveConfig, h(Suspense, {
|
wrapInKeepAlive(keepaliveConfig, h(Suspense, {
|
||||||
suspensible: true,
|
suspensible: true,
|
||||||
onPending: () => nuxtApp.callHook('page:start', routeProps.Component),
|
onPending: () => nuxtApp.callHook('page:start', routeProps.Component),
|
||||||
onResolve: () => { nextTick(() => nuxtApp.callHook('page:finish', routeProps.Component).then(() => nuxtApp.callHook('page:loading:end')).finally(done)) },
|
onResolve: () => {
|
||||||
|
nextTick(() => nuxtApp.callHook('page:finish', routeProps.Component).then(() => {
|
||||||
|
if (!pageLoadingEndHookAlreadyCalled) {
|
||||||
|
return nuxtApp.callHook('page:loading:end')
|
||||||
|
}
|
||||||
|
pageLoadingEndHookAlreadyCalled = false
|
||||||
|
}).finally(done))
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
default: () => {
|
default: () => {
|
||||||
const providerVNode = h(RouteProvider, {
|
const providerVNode = h(RouteProvider, {
|
||||||
|
@ -68,7 +68,10 @@ export async function resolvePagesRoutes (nuxt = useNuxt()): Promise<NuxtPage[]>
|
|||||||
return pages
|
return pages
|
||||||
}
|
}
|
||||||
|
|
||||||
const augmentCtx = { extraExtractionKeys: nuxt.options.experimental.extraPageMetaExtractionKeys }
|
const augmentCtx = {
|
||||||
|
extraExtractionKeys: nuxt.options.experimental.extraPageMetaExtractionKeys,
|
||||||
|
fullyResolvedPaths: new Set(scannedFiles.map(file => file.absolutePath)),
|
||||||
|
}
|
||||||
if (shouldAugment === 'after-resolve') {
|
if (shouldAugment === 'after-resolve') {
|
||||||
await nuxt.callHook('pages:extend', pages)
|
await nuxt.callHook('pages:extend', pages)
|
||||||
await augmentPages(pages, nuxt.vfs, augmentCtx)
|
await augmentPages(pages, nuxt.vfs, augmentCtx)
|
||||||
@ -121,7 +124,7 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
|
|||||||
for (let i = 0; i < segments.length; i++) {
|
for (let i = 0; i < segments.length; i++) {
|
||||||
const segment = segments[i]
|
const segment = segments[i]
|
||||||
|
|
||||||
const tokens = parseSegment(segment!)
|
const tokens = parseSegment(segment!, file.absolutePath)
|
||||||
|
|
||||||
// Skip group segments
|
// Skip group segments
|
||||||
if (tokens.every(token => token.type === SegmentTokenType.group)) {
|
if (tokens.every(token => token.type === SegmentTokenType.group)) {
|
||||||
@ -154,6 +157,7 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AugmentPagesContext {
|
interface AugmentPagesContext {
|
||||||
|
fullyResolvedPaths?: Set<string>
|
||||||
pagesToSkip?: Set<string>
|
pagesToSkip?: Set<string>
|
||||||
augmentedPages?: Set<string>
|
augmentedPages?: Set<string>
|
||||||
extraExtractionKeys?: string[]
|
extraExtractionKeys?: string[]
|
||||||
@ -163,7 +167,9 @@ export async function augmentPages (routes: NuxtPage[], vfs: Record<string, stri
|
|||||||
ctx.augmentedPages ??= new Set()
|
ctx.augmentedPages ??= new Set()
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
if (route.file && !ctx.pagesToSkip?.has(route.file)) {
|
if (route.file && !ctx.pagesToSkip?.has(route.file)) {
|
||||||
const fileContent = route.file in vfs ? vfs[route.file]! : fs.readFileSync(await resolvePath(route.file), 'utf-8')
|
const fileContent = route.file in vfs
|
||||||
|
? vfs[route.file]!
|
||||||
|
: fs.readFileSync(ctx.fullyResolvedPaths?.has(route.file) ? route.file : await resolvePath(route.file), 'utf-8')
|
||||||
const routeMeta = await getRouteMeta(fileContent, route.file, ctx.extraExtractionKeys)
|
const routeMeta = await getRouteMeta(fileContent, route.file, ctx.extraExtractionKeys)
|
||||||
if (route.meta) {
|
if (route.meta) {
|
||||||
routeMeta.meta = { ...routeMeta.meta, ...route.meta }
|
routeMeta.meta = { ...routeMeta.meta, ...route.meta }
|
||||||
@ -331,7 +337,7 @@ function getRoutePath (tokens: SegmentToken[]): string {
|
|||||||
|
|
||||||
const PARAM_CHAR_RE = /[\w.]/
|
const PARAM_CHAR_RE = /[\w.]/
|
||||||
|
|
||||||
function parseSegment (segment: string) {
|
function parseSegment (segment: string, absolutePath: string) {
|
||||||
let state: SegmentParserState = SegmentParserState.initial
|
let state: SegmentParserState = SegmentParserState.initial
|
||||||
let i = 0
|
let i = 0
|
||||||
|
|
||||||
@ -418,8 +424,10 @@ function parseSegment (segment: string) {
|
|||||||
state = SegmentParserState.initial
|
state = SegmentParserState.initial
|
||||||
} else if (c && PARAM_CHAR_RE.test(c)) {
|
} else if (c && PARAM_CHAR_RE.test(c)) {
|
||||||
buffer += c
|
buffer += c
|
||||||
} else {
|
} else if (state === SegmentParserState.dynamic || state === SegmentParserState.optional) {
|
||||||
// console.debug(`[pages]Ignored character "${c}" while building param "${buffer}" from "segment"`)
|
if (c !== '[' && c !== ']') {
|
||||||
|
logger.warn(`'\`${c}\`' is not allowed in a dynamic route parameter and has been ignored. Consider renaming \`${absolutePath}\`.`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -292,6 +292,8 @@ async function getResolvedApp (files: Array<string | { name: string, contents: s
|
|||||||
}
|
}
|
||||||
for (const plugin of app.plugins) {
|
for (const plugin of app.plugins) {
|
||||||
plugin.src = normaliseToRepo(plugin.src)!
|
plugin.src = normaliseToRepo(plugin.src)!
|
||||||
|
// @ts-expect-error untyped symbol
|
||||||
|
delete plugin[Symbol.for('nuxt plugin')]
|
||||||
}
|
}
|
||||||
for (const mw of app.middleware) {
|
for (const mw of app.middleware) {
|
||||||
mw.path = normaliseToRepo(mw.path)!
|
mw.path = normaliseToRepo(mw.path)!
|
||||||
|
62
packages/nuxt/test/check-dependencies.test.ts
Normal file
62
packages/nuxt/test/check-dependencies.test.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { readPackageJSON } from 'pkg-types'
|
||||||
|
import { inc } from 'semver'
|
||||||
|
import { version } from '../package.json'
|
||||||
|
import { checkDependencyVersion, keyDependencies } from '../src/core/nuxt'
|
||||||
|
|
||||||
|
vi.stubGlobal('console', {
|
||||||
|
...console,
|
||||||
|
error: vi.fn(console.error),
|
||||||
|
warn: vi.fn(console.warn),
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock('pkg-types', async (og) => {
|
||||||
|
const originalPkgTypes = (await og<typeof import('pkg-types')>())
|
||||||
|
return {
|
||||||
|
...originalPkgTypes,
|
||||||
|
readPackageJSON: vi.fn(originalPkgTypes.readPackageJSON),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('dependency mismatch', () => {
|
||||||
|
it.sequential('expect mismatched dependency to log a warning', async () => {
|
||||||
|
vi.mocked(readPackageJSON).mockReturnValue(Promise.resolve({
|
||||||
|
version: '3.0.0',
|
||||||
|
}))
|
||||||
|
|
||||||
|
for (const dep of keyDependencies) {
|
||||||
|
await checkDependencyVersion(dep, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @nuxt/kit is explicitly installed in repo root but @nuxt/schema isn't, so we only
|
||||||
|
// get warnings about @nuxt/schema
|
||||||
|
expect(console.warn).toHaveBeenCalledWith(`[nuxt] Expected \`@nuxt/kit\` to be at least \`${version}\` but got \`3.0.0\`. This might lead to unexpected behavior. Check your package.json or refresh your lockfile.`)
|
||||||
|
|
||||||
|
vi.mocked(readPackageJSON).mockRestore()
|
||||||
|
})
|
||||||
|
it.sequential.each([
|
||||||
|
{
|
||||||
|
name: 'nuxt version is lower',
|
||||||
|
depVersion: inc(version, 'minor'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'version matches',
|
||||||
|
depVersion: version,
|
||||||
|
},
|
||||||
|
])('expect no warning when $name.', async ({ depVersion }) => {
|
||||||
|
vi.mocked(readPackageJSON).mockReturnValue(Promise.resolve({
|
||||||
|
depVersion,
|
||||||
|
}))
|
||||||
|
|
||||||
|
for (const dep of keyDependencies) {
|
||||||
|
await checkDependencyVersion(dep, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(console.warn).not.toHaveBeenCalled()
|
||||||
|
vi.mocked(readPackageJSON).mockRestore()
|
||||||
|
})
|
||||||
|
})
|
@ -2,13 +2,13 @@ import { fileURLToPath } from 'node:url'
|
|||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { normalize } from 'pathe'
|
import { normalize } from 'pathe'
|
||||||
import { withoutTrailingSlash } from 'ufo'
|
import { withoutTrailingSlash } from 'ufo'
|
||||||
import { loadNuxt } from '../src'
|
import { loadNuxt } from 'nuxt'
|
||||||
|
|
||||||
const emptyDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../node_modules/fixture', import.meta.url))))
|
const emptyDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../node_modules/fixture', import.meta.url))))
|
||||||
const basicTestFixtureDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../test/fixtures/basic', import.meta.url))))
|
const basicTestFixtureDir = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../test/fixtures/basic', import.meta.url))))
|
||||||
|
|
||||||
describe('loadNuxt', () => {
|
describe('loadNuxt', () => {
|
||||||
bench('empty directory', async () => {
|
bench('loadNuxt in an empty directory', async () => {
|
||||||
const nuxt = await loadNuxt({
|
const nuxt = await loadNuxt({
|
||||||
cwd: emptyDir,
|
cwd: emptyDir,
|
||||||
ready: true,
|
ready: true,
|
||||||
@ -16,7 +16,7 @@ describe('loadNuxt', () => {
|
|||||||
await nuxt.close()
|
await nuxt.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
bench('basic test fixture', async () => {
|
bench('loadNuxt in the basic test fixture', async () => {
|
||||||
const nuxt = await loadNuxt({
|
const nuxt = await loadNuxt({
|
||||||
cwd: basicTestFixtureDir,
|
cwd: basicTestFixtureDir,
|
||||||
ready: true,
|
ready: true,
|
||||||
|
@ -2,11 +2,8 @@ import { fileURLToPath } from 'node:url'
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { normalize } from 'pathe'
|
import { normalize } from 'pathe'
|
||||||
import { withoutTrailingSlash } from 'ufo'
|
import { withoutTrailingSlash } from 'ufo'
|
||||||
import { readPackageJSON } from 'pkg-types'
|
|
||||||
import { inc } from 'semver'
|
|
||||||
import { asyncNameStorage, logger, useNuxt } from '@nuxt/kit'
|
import { asyncNameStorage, logger, useNuxt } from '@nuxt/kit'
|
||||||
import { loadNuxt } from '../src'
|
import { loadNuxt } from '../src'
|
||||||
import { version } from '../package.json'
|
|
||||||
|
|
||||||
const repoRoot = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../', import.meta.url))))
|
const repoRoot = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../', import.meta.url))))
|
||||||
|
|
||||||
@ -80,45 +77,3 @@ describe('loadNuxt', () => {
|
|||||||
expect(loggerWarn).not.toHaveBeenCalled()
|
expect(loggerWarn).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('dependency mismatch', () => {
|
|
||||||
it('expect mismatched dependency to log a warning', async () => {
|
|
||||||
vi.mocked(readPackageJSON).mockReturnValue(Promise.resolve({
|
|
||||||
version: '3.0.0',
|
|
||||||
}))
|
|
||||||
|
|
||||||
const nuxt = await loadNuxt({
|
|
||||||
cwd: repoRoot,
|
|
||||||
})
|
|
||||||
|
|
||||||
// @nuxt/kit is explicitly installed in repo root but @nuxt/schema isn't, so we only
|
|
||||||
// get warnings about @nuxt/schema
|
|
||||||
expect(console.warn).toHaveBeenCalledWith(`[nuxt] Expected \`@nuxt/kit\` to be at least \`${version}\` but got \`3.0.0\`. This might lead to unexpected behavior. Check your package.json or refresh your lockfile.`)
|
|
||||||
|
|
||||||
vi.mocked(readPackageJSON).mockRestore()
|
|
||||||
await nuxt.close()
|
|
||||||
})
|
|
||||||
it.each([
|
|
||||||
{
|
|
||||||
name: 'nuxt version is lower',
|
|
||||||
depVersion: inc(version, 'minor'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'version matches',
|
|
||||||
depVersion: version,
|
|
||||||
},
|
|
||||||
])('expect no warning when $name.', async ({ depVersion }) => {
|
|
||||||
vi.mocked(readPackageJSON).mockReturnValue(Promise.resolve({
|
|
||||||
depVersion,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const nuxt = await loadNuxt({
|
|
||||||
cwd: repoRoot,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(console.warn).not.toHaveBeenCalled()
|
|
||||||
|
|
||||||
await nuxt.close()
|
|
||||||
vi.mocked(readPackageJSON).mockRestore()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
84
packages/nuxt/test/virtual.test.ts
Normal file
84
packages/nuxt/test/virtual.test.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import type { Nuxt } from '@nuxt/schema'
|
||||||
|
import { rollup } from 'rollup'
|
||||||
|
|
||||||
|
import { VirtualFSPlugin } from '../src/core/plugins/virtual'
|
||||||
|
|
||||||
|
describe('virtual fs plugin', () => {
|
||||||
|
it('should support loading files virtually', async () => {
|
||||||
|
const code = await generateCode('export { foo } from "#build/foo"', {
|
||||||
|
vfs: {
|
||||||
|
'/.nuxt/foo': 'export const foo = "hello world"',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"const foo = "hello world";
|
||||||
|
|
||||||
|
export { foo };"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support loading virtual files by suffix', async () => {
|
||||||
|
const code = await generateCode('export { foo } from "#build/foo"', {
|
||||||
|
mode: 'client',
|
||||||
|
vfs: {
|
||||||
|
'/.nuxt/foo.server.ts': 'export const foo = "foo server file"',
|
||||||
|
'/.nuxt/foo.client.ts': 'export const foo = "foo client file"',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"const foo = "foo client file";
|
||||||
|
|
||||||
|
export { foo };"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support loading files referenced relatively', async () => {
|
||||||
|
const code = await generateCode('export { foo } from "#build/foo"', {
|
||||||
|
vfs: {
|
||||||
|
'/.nuxt/foo': 'export { foo } from "./bar"',
|
||||||
|
'/.nuxt/bar': 'export const foo = "relative import"',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"const foo = "relative import";
|
||||||
|
|
||||||
|
export { foo };"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function generateCode (input: string, options: { mode?: 'client' | 'server', vfs: Record<string, string> }) {
|
||||||
|
const stubNuxt = {
|
||||||
|
options: {
|
||||||
|
extensions: ['.ts', '.js'],
|
||||||
|
alias: {
|
||||||
|
'~': '/',
|
||||||
|
'#build': '/.nuxt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vfs: options.vfs,
|
||||||
|
} as unknown as Nuxt
|
||||||
|
|
||||||
|
const bundle = await rollup({
|
||||||
|
input: 'entry.ts',
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'entry',
|
||||||
|
resolveId (id) {
|
||||||
|
if (id === 'entry.ts') {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load (id) {
|
||||||
|
if (id === 'entry.ts') {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VirtualFSPlugin(stubNuxt, { mode: options.mode || 'client', alias: stubNuxt.options.alias }).rollup(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
const { output: [chunk] } = await bundle.generate({})
|
||||||
|
return chunk.code.trim()
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { defineBuildConfig } from 'unbuild'
|
import { defineBuildConfig } from 'unbuild'
|
||||||
|
import { addRollupTimingsPlugin, stubOptions } from '../../debug/build-config'
|
||||||
import config from '../webpack/build.config'
|
import config from '../webpack/build.config'
|
||||||
|
|
||||||
export default defineBuildConfig({
|
export default defineBuildConfig({
|
||||||
@ -8,6 +9,12 @@ export default defineBuildConfig({
|
|||||||
'#builder',
|
'#builder',
|
||||||
'@nuxt/schema',
|
'@nuxt/schema',
|
||||||
],
|
],
|
||||||
|
stubOptions,
|
||||||
|
hooks: {
|
||||||
|
'rollup:options' (ctx, options) {
|
||||||
|
addRollupTimingsPlugin(options)
|
||||||
|
},
|
||||||
|
},
|
||||||
entries: [
|
entries: [
|
||||||
{
|
{
|
||||||
input: '../webpack/src/index',
|
input: '../webpack/src/index',
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
|
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@rspack/core": "^1.1.8",
|
"@rspack/core": "^1.2.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||||
@ -43,15 +43,15 @@
|
|||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^9.0.2",
|
"fork-ts-checker-webpack-plugin": "^9.0.2",
|
||||||
"globby": "^14.0.2",
|
"globby": "^14.0.2",
|
||||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.4.2",
|
||||||
"knitwork": "^1.2.0",
|
"knitwork": "^1.2.0",
|
||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.17",
|
||||||
"memfs": "^4.17.0",
|
"memfs": "^4.17.0",
|
||||||
"ohash": "^1.1.4",
|
"ohash": "^1.1.4",
|
||||||
"pathe": "^2.0.1",
|
"pathe": "^2.0.2",
|
||||||
"pify": "^6.1.0",
|
"pify": "^6.1.0",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.5.1",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-import-resolver": "^2.0.0",
|
"postcss-import-resolver": "^2.0.0",
|
||||||
"postcss-loader": "^8.1.1",
|
"postcss-loader": "^8.1.1",
|
||||||
@ -75,14 +75,14 @@
|
|||||||
"@types/pify": "5.0.4",
|
"@types/pify": "5.0.4",
|
||||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||||
"@types/webpack-hot-middleware": "2.25.9",
|
"@types/webpack-hot-middleware": "2.25.9",
|
||||||
"rollup": "4.30.1",
|
"rollup": "4.31.0",
|
||||||
"unbuild": "3.2.0",
|
"unbuild": "3.3.1",
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.3.4"
|
"vue": "^3.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.20.5 || ^20.9.0 || >=22.0.0"
|
"node": "^18.12.0 || ^20.9.0 || >=22.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { defineBuildConfig } from 'unbuild'
|
import { defineBuildConfig } from 'unbuild'
|
||||||
|
import { stubOptions } from '../../debug/build-config'
|
||||||
|
|
||||||
export default defineBuildConfig({
|
export default defineBuildConfig({
|
||||||
declaration: true,
|
declaration: true,
|
||||||
@ -20,22 +21,16 @@ export default defineBuildConfig({
|
|||||||
'src/index',
|
'src/index',
|
||||||
'src/builder-env',
|
'src/builder-env',
|
||||||
],
|
],
|
||||||
hooks: {
|
stubOptions,
|
||||||
'rollup:options' (ctx, options) {
|
rollup: {
|
||||||
ctx.options.rollup.dts.respectExternal = false
|
dts: { respectExternal: false },
|
||||||
const isExternal = options.external! as (id: string, importer?: string, isResolved?: boolean) => boolean
|
inlineDependencies: ['untyped', 'knitwork'],
|
||||||
options.external = (source, importer, isResolved) => {
|
|
||||||
if (source === 'untyped' || source === 'knitwork') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isExternal(source, importer, isResolved)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
// Type imports
|
// Type imports
|
||||||
'@unhead/schema',
|
'@unhead/schema',
|
||||||
'@vitejs/plugin-vue',
|
'@vitejs/plugin-vue',
|
||||||
|
'chokidar',
|
||||||
'@vitejs/plugin-vue-jsx',
|
'@vitejs/plugin-vue-jsx',
|
||||||
'@vue/language-core',
|
'@vue/language-core',
|
||||||
'autoprefixer',
|
'autoprefixer',
|
||||||
|
@ -37,29 +37,30 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/pug": "2.0.10",
|
"@types/pug": "2.0.10",
|
||||||
"@unhead/schema": "1.11.15",
|
"@unhead/schema": "1.11.18",
|
||||||
"@vitejs/plugin-vue": "5.2.1",
|
"@vitejs/plugin-vue": "5.2.1",
|
||||||
"@vitejs/plugin-vue-jsx": "4.1.1",
|
"@vitejs/plugin-vue-jsx": "4.1.1",
|
||||||
"@vue/compiler-core": "3.5.13",
|
"@vue/compiler-core": "3.5.13",
|
||||||
"@vue/compiler-sfc": "3.5.13",
|
"@vue/compiler-sfc": "3.5.13",
|
||||||
"@vue/language-core": "2.2.0",
|
"@vue/language-core": "2.2.0",
|
||||||
"c12": "2.0.1",
|
"c12": "2.0.1",
|
||||||
|
"chokidar": "4.0.3",
|
||||||
"compatx": "0.1.8",
|
"compatx": "0.1.8",
|
||||||
"esbuild-loader": "4.2.2",
|
"esbuild-loader": "4.2.2",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
|
||||||
"hookable": "5.5.3",
|
"hookable": "5.5.3",
|
||||||
"ignore": "7.0.0",
|
"ignore": "7.0.3",
|
||||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
"nitro": "npm:nitro-nightly@3.0.0-beta-28938837.19ec5395",
|
||||||
"ofetch": "1.4.1",
|
"ofetch": "1.4.1",
|
||||||
"pkg-types": "1.3.0",
|
"pkg-types": "1.3.1",
|
||||||
"sass-loader": "16.0.4",
|
"sass-loader": "16.0.4",
|
||||||
"scule": "1.3.0",
|
"scule": "1.3.0",
|
||||||
"unbuild": "3.2.0",
|
"unbuild": "3.3.1",
|
||||||
"unctx": "2.4.1",
|
"unctx": "2.4.1",
|
||||||
"unimport": "3.14.5",
|
"unimport": "4.0.0",
|
||||||
"untyped": "1.5.2",
|
"untyped": "1.5.2",
|
||||||
"vite": "6.0.7",
|
"vite": "6.0.11",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.13",
|
||||||
"vue-bundle-renderer": "2.1.1",
|
"vue-bundle-renderer": "2.1.1",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
@ -68,9 +69,9 @@
|
|||||||
"webpack-dev-middleware": "7.4.2"
|
"webpack-dev-middleware": "7.4.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"consola": "^3.3.3",
|
"consola": "^3.4.0",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"pathe": "^2.0.1",
|
"pathe": "^2.0.2",
|
||||||
"std-env": "^3.8.0"
|
"std-env": "^3.8.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -247,12 +247,16 @@ export default defineUntypedSchema({
|
|||||||
*
|
*
|
||||||
* Normally, you should not need to set this.
|
* Normally, you should not need to set this.
|
||||||
*/
|
*/
|
||||||
dev: Boolean(isDevelopment),
|
dev: {
|
||||||
|
$resolve: val => val ?? Boolean(isDevelopment),
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether your app is being unit tested.
|
* Whether your app is being unit tested.
|
||||||
*/
|
*/
|
||||||
test: Boolean(isTest),
|
test: {
|
||||||
|
$resolve: val => val ?? Boolean(isTest),
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to `true` to enable debug mode.
|
* Set to `true` to enable debug mode.
|
||||||
@ -519,9 +523,11 @@ export default defineUntypedSchema({
|
|||||||
/**
|
/**
|
||||||
* Options to pass directly to `chokidar`.
|
* Options to pass directly to `chokidar`.
|
||||||
* @see [chokidar](https://github.com/paulmillr/chokidar#api)
|
* @see [chokidar](https://github.com/paulmillr/chokidar#api)
|
||||||
|
* @type {typeof import('chokidar').ChokidarOptions}
|
||||||
*/
|
*/
|
||||||
chokidar: {
|
chokidar: {
|
||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
|
ignorePermissionErrors: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -17,22 +17,19 @@
|
|||||||
"prerender": "pnpm build && jiti ./lib/prerender"
|
"prerender": "pnpm build && jiti ./lib/prerender"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@unocss/reset": "65.4.0",
|
"@unocss/reset": "65.4.3",
|
||||||
"beasties": "0.2.0",
|
"beasties": "0.2.0",
|
||||||
"html-validate": "9.1.3",
|
"html-validate": "9.1.3",
|
||||||
"htmlnano": "2.1.1",
|
"htmlnano": "2.1.1",
|
||||||
"jiti": "2.4.2",
|
"jiti": "2.4.2",
|
||||||
"knitwork": "1.2.0",
|
"knitwork": "1.2.0",
|
||||||
"pathe": "2.0.1",
|
"pathe": "2.0.2",
|
||||||
"prettier": "3.4.2",
|
"prettier": "3.4.2",
|
||||||
"scule": "1.3.0",
|
"scule": "1.3.0",
|
||||||
"svgo": "3.3.2",
|
"svgo": "3.3.2",
|
||||||
"tinyexec": "0.3.2",
|
"tinyexec": "0.3.2",
|
||||||
"tinyglobby": "0.2.10",
|
"tinyglobby": "0.2.10",
|
||||||
"unocss": "65.4.0",
|
"unocss": "65.4.3",
|
||||||
"vite": "6.0.7"
|
"vite": "6.0.11"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { defineBuildConfig } from 'unbuild'
|
import { defineBuildConfig } from 'unbuild'
|
||||||
|
import { addRollupTimingsPlugin, stubOptions } from '../../debug/build-config'
|
||||||
|
|
||||||
export default defineBuildConfig({
|
export default defineBuildConfig({
|
||||||
declaration: true,
|
declaration: true,
|
||||||
@ -6,6 +7,12 @@ export default defineBuildConfig({
|
|||||||
'src/index',
|
'src/index',
|
||||||
{ input: 'src/runtime/', outDir: 'dist/runtime', format: 'esm' },
|
{ input: 'src/runtime/', outDir: 'dist/runtime', format: 'esm' },
|
||||||
],
|
],
|
||||||
|
stubOptions,
|
||||||
|
hooks: {
|
||||||
|
'rollup:options' (ctx, options) {
|
||||||
|
addRollupTimingsPlugin(options)
|
||||||
|
},
|
||||||
|
},
|
||||||
dependencies: [
|
dependencies: [
|
||||||
'vue',
|
'vue',
|
||||||
],
|
],
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user