mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 09:25:54 +00:00
Merge branch 'main' into fix/20667-omit-usefetch-body-for-get-method
This commit is contained in:
commit
fe4e742cfa
@ -1,4 +1,4 @@
|
|||||||
FROM node:lts@sha256:1f097426a7ddd1c5d0eacfe0402fdf91e38e4ecc37d23780428f6b87145ad2aa
|
FROM node:lts@sha256:99981c3d1aac0d98cd9f03f74b92dddf30f30ffb0b34e6df8bd96283f62f12c6
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -fy libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 && \
|
apt-get install -fy libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 && \
|
||||||
|
4
.github/workflows/autofix-docs.yml
vendored
4
.github/workflows/autofix-docs.yml
vendored
@ -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
|
||||||
|
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
@ -33,7 +33,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
|
||||||
|
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
|
||||||
|
47
.github/workflows/ci.yml
vendored
47
.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,11 +56,8 @@ 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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
name: dist
|
name: dist
|
||||||
@ -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@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||||
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@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||||
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
|
||||||
@ -260,20 +260,18 @@ jobs:
|
|||||||
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 +282,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
|
||||||
@ -303,18 +301,9 @@ jobs:
|
|||||||
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 +314,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 +326,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/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"
|
||||||
|
|
||||||
|
4
.github/workflows/scorecards.yml
vendored
4
.github/workflows/scorecards.yml
vendored
@ -59,7 +59,7 @@ jobs:
|
|||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
# format to the repository Actions tab.
|
# format to the repository Actions tab.
|
||||||
- name: "Upload artifact"
|
- name: "Upload artifact"
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
if: github.repository == 'nuxt/nuxt' && success()
|
if: github.repository == 'nuxt/nuxt' && success()
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
@ -68,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||||
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
|
||||||
|
@ -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`.
|
||||||
::
|
::
|
||||||
|
@ -71,7 +71,7 @@ export const useFoo = () => {
|
|||||||
|
|
||||||
### Access plugin injections
|
### Access plugin injections
|
||||||
|
|
||||||
You can access [plugin injections](/docs/guide/directory-structure/plugins#automatically-providing-helpers) from composables:
|
You can access [plugin injections](/docs/guide/directory-structure/plugins#providing-helpers) from composables:
|
||||||
|
|
||||||
```js [composables/test.ts]
|
```js [composables/test.ts]
|
||||||
export const useHello = () => {
|
export const useHello = () => {
|
||||||
|
@ -62,14 +62,23 @@ 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`)
|
||||||
- `immediate`: when set to `false`, will prevent the request from firing immediately. (defaults to `true`)
|
- `immediate`: when set to `false`, will prevent the request from firing immediately. (defaults to `true`)
|
||||||
- `default`: a factory function to set the default value of the `data`, before the async function resolves - useful with the `lazy: true` or `immediate: false` option
|
- `default`: a factory function to set the default value of the `data`, before the async function resolves - useful with the `lazy: true` or `immediate: false` option
|
||||||
- `transform`: a function that can be used to alter `handler` function result after resolving
|
- `transform`: a function that can be used to alter `handler` function result after resolving
|
||||||
- `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled.
|
- `getCachedData`: Provide a function which returns cached data. A `null` or `undefined` return value will trigger a fetch. By default, this is:
|
||||||
|
```ts
|
||||||
|
const getDefaultCachedData = (key) => nuxtApp.isHydrating
|
||||||
|
? nuxtApp.payload.data[key]
|
||||||
|
: nuxtApp.static.data[key]
|
||||||
|
```
|
||||||
|
Which only caches data when `experimental.payloadExtraction` of `nuxt.config` is enabled.
|
||||||
- `pick`: only pick specified keys in this array from the `handler` function result
|
- `pick`: only pick specified keys in this array from the `handler` function result
|
||||||
- `watch`: watch reactive sources to auto-refresh
|
- `watch`: watch reactive sources to auto-refresh
|
||||||
- `deep`: return data in a deep ref object. It is `false` by default to return data in a shallow ref object for performance.
|
- `deep`: return data in a deep ref object. It is `false` by default to return data in a shallow ref object for performance.
|
||||||
@ -94,7 +103,13 @@ Learn how to use `transform` and `getCachedData` to avoid superfluous calls to a
|
|||||||
- `data`: the result of the asynchronous function that is passed in.
|
- `data`: the result of the asynchronous function that is passed in.
|
||||||
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
|
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
|
||||||
- `error`: an error object if the data fetching failed.
|
- `error`: an error object if the data fetching failed.
|
||||||
- `status`: a string indicating the status of the data request (`"idle"`, `"pending"`, `"success"`, `"error"`).
|
- `status`: a string indicating the status of the data request:
|
||||||
|
- `idle`: when the request has not started, such as:
|
||||||
|
- when `execute` has not yet been called and `{ immediate: false }` is set
|
||||||
|
- when rendering HTML on the server and `{ server: false }` is set
|
||||||
|
- `pending`: the request is in progress
|
||||||
|
- `success`: the request has completed successfully
|
||||||
|
- `error`: the request has failed
|
||||||
- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `status` to `'idle'`, and mark any currently pending requests as cancelled.
|
- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `status` to `'idle'`, and mark any currently pending requests as cancelled.
|
||||||
|
|
||||||
By default, Nuxt waits until a `refresh` is finished before it can be executed again.
|
By default, Nuxt waits until a `refresh` is finished before it can be executed again.
|
||||||
|
@ -109,7 +109,13 @@ All fetch options can be given a `computed` or `ref` value. These will be watche
|
|||||||
- `immediate`: when set to `false`, will prevent the request from firing immediately. (defaults to `true`)
|
- `immediate`: when set to `false`, will prevent the request from firing immediately. (defaults to `true`)
|
||||||
- `default`: a factory function to set the default value of the `data`, before the async function resolves - useful with the `lazy: true` or `immediate: false` option
|
- `default`: a factory function to set the default value of the `data`, before the async function resolves - useful with the `lazy: true` or `immediate: false` option
|
||||||
- `transform`: a function that can be used to alter `handler` function result after resolving
|
- `transform`: a function that can be used to alter `handler` function result after resolving
|
||||||
- `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled.
|
- `getCachedData`: Provide a function which returns cached data. A `null` or `undefined` return value will trigger a fetch. By default, this is:
|
||||||
|
```ts
|
||||||
|
const getDefaultCachedData = (key) => nuxtApp.isHydrating
|
||||||
|
? nuxtApp.payload.data[key]
|
||||||
|
: nuxtApp.static.data[key]
|
||||||
|
```
|
||||||
|
Which only caches data when `experimental.payloadExtraction` of `nuxt.config` is enabled.
|
||||||
- `pick`: only pick specified keys in this array from the `handler` function result
|
- `pick`: only pick specified keys in this array from the `handler` function result
|
||||||
- `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`. (You can [see an example here](/docs/getting-started/data-fetching#watch) of using `watch`.)
|
- `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`. (You can [see an example here](/docs/getting-started/data-fetching#watch) of using `watch`.)
|
||||||
- `deep`: return data in a deep ref object. It is `false` by default to return data in a shallow ref object for performance.
|
- `deep`: return data in a deep ref object. It is `false` by default to return data in a shallow ref object for performance.
|
||||||
@ -134,7 +140,13 @@ Learn how to use `transform` and `getCachedData` to avoid superfluous calls to a
|
|||||||
- `data`: the result of the asynchronous function that is passed in.
|
- `data`: the result of the asynchronous function that is passed in.
|
||||||
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
|
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
|
||||||
- `error`: an error object if the data fetching failed.
|
- `error`: an error object if the data fetching failed.
|
||||||
- `status`: a string indicating the status of the data request (`"idle"`, `"pending"`, `"success"`, `"error"`).
|
- `status`: a string indicating the status of the data request:
|
||||||
|
- `idle`: when the request has not started, such as:
|
||||||
|
- when `execute` has not yet been called and `{ immediate: false }` is set
|
||||||
|
- when rendering HTML on the server and `{ server: false }` is set
|
||||||
|
- `pending`: the request is in progress
|
||||||
|
- `success`: the request has completed successfully
|
||||||
|
- `error`: the request has failed
|
||||||
- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `status` to `'idle'`, and mark any currently pending requests as cancelled.
|
- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `status` to `'idle'`, and mark any currently pending requests as cancelled.
|
||||||
|
|
||||||
By default, Nuxt waits until a `refresh` is finished before it can be executed again.
|
By default, Nuxt waits until a `refresh` is finished before it can be executed again.
|
||||||
@ -147,7 +159,7 @@ If you have not fetched data on the server (for example, with `server: false`),
|
|||||||
|
|
||||||
```ts [Signature]
|
```ts [Signature]
|
||||||
function useFetch<DataT, ErrorT>(
|
function useFetch<DataT, ErrorT>(
|
||||||
url: string | Request | Ref<string | Request> | (() => string) | Request,
|
url: string | Request | Ref<string | Request> | (() => string | Request),
|
||||||
options?: UseFetchOptions<DataT>
|
options?: UseFetchOptions<DataT>
|
||||||
): Promise<AsyncData<DataT, ErrorT>>
|
): Promise<AsyncData<DataT, ErrorT>>
|
||||||
|
|
||||||
|
@ -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/useHeadSafe.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/useHead.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/useSeoMeta.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/useServerSeoMeta.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ description: 'Nuxt command to build your Nuxt module before publishing.'
|
|||||||
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-module.ts
|
to: https://github.com/nuxt/module-builder/blob/main/src/cli.ts
|
||||||
size: xs
|
size: xs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ Each active version has its own nightly releases which are generated automatical
|
|||||||
|
|
||||||
Release | | Initial release | End Of Life | Docs
|
Release | | Initial release | End Of Life | Docs
|
||||||
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
|
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
|
||||||
**4.x** (scheduled) | | 2024 Q3 | |
|
**4.x** (scheduled) | | approximately 1 month after release of nitro v3 | |
|
||||||
**3.x** (stable) | <a href="https://npmjs.com/package/nuxt"><img alt="Nuxt latest 3.x version" src="https://flat.badgen.net/npm/v/nuxt?label=" class="not-prose"></a> | 2022-11-16 | TBA | [nuxt.com](/docs)
|
**3.x** (stable) | <a href="https://npmjs.com/package/nuxt"><img alt="Nuxt latest 3.x version" src="https://flat.badgen.net/npm/v/nuxt?label=" class="not-prose"></a> | 2022-11-16 | TBA | [nuxt.com](/docs)
|
||||||
**2.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 2.x version" src="https://flat.badgen.net/npm/v/nuxt/2x?label=" class="not-prose"></a> | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
|
**2.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 2.x version" src="https://flat.badgen.net/npm/v/nuxt/2x?label=" class="not-prose"></a> | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
|
||||||
**1.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 1.x version" src="https://flat.badgen.net/npm/v/nuxt/1x?label=" class="not-prose"></a> | 2018-01-08 | 2019-09-21 |
|
**1.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 1.x version" src="https://flat.badgen.net/npm/v/nuxt/1x?label=" class="not-prose"></a> | 2018-01-08 | 2019-09-21 |
|
||||||
|
61
package.json
61
package.json
@ -39,12 +39,12 @@
|
|||||||
"@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.6",
|
||||||
"@unhead/dom": "1.11.14",
|
"@unhead/dom": "1.11.18",
|
||||||
"@unhead/schema": "1.11.14",
|
"@unhead/schema": "1.11.18",
|
||||||
"@unhead/shared": "1.11.14",
|
"@unhead/shared": "1.11.18",
|
||||||
"@unhead/ssr": "1.11.14",
|
"@unhead/ssr": "1.11.18",
|
||||||
"@unhead/vue": "1.11.14",
|
"@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",
|
||||||
@ -55,62 +55,64 @@
|
|||||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"ohash": "1.1.4",
|
"ohash": "1.1.4",
|
||||||
"postcss": "8.4.49",
|
"postcss": "8.5.1",
|
||||||
"rollup": "4.30.0",
|
"rollup": "4.30.1",
|
||||||
"send": ">=1.1.0",
|
"send": ">=1.1.0",
|
||||||
"typescript": "5.7.2",
|
"typescript": "5.7.3",
|
||||||
"ufo": "1.5.4",
|
"ufo": "1.5.4",
|
||||||
"unbuild": "3.2.0",
|
"unbuild": "3.3.1",
|
||||||
"unhead": "1.11.14",
|
"unhead": "1.11.18",
|
||||||
"unimport": "3.14.5",
|
"unimport": "3.14.6",
|
||||||
"vite": "6.0.7",
|
"vite": "6.0.7",
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@arethetypeswrong/cli": "0.17.2",
|
"@arethetypeswrong/cli": "0.17.3",
|
||||||
"@nuxt/eslint-config": "0.7.4",
|
"@nuxt/cli": "3.20.0",
|
||||||
|
"@nuxt/eslint-config": "0.7.5",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@nuxt/rspack-builder": "workspace:*",
|
"@nuxt/rspack-builder": "workspace:*",
|
||||||
"@nuxt/test-utils": "3.15.1",
|
"@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/node": "22.10.6",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@unhead/schema": "1.11.14",
|
"@unhead/schema": "1.11.18",
|
||||||
"@unhead/vue": "1.11.14",
|
"@unhead/vue": "1.11.18",
|
||||||
"@vitest/coverage-v8": "2.1.8",
|
"@vitest/coverage-v8": "2.1.8",
|
||||||
"@vue/test-utils": "2.4.6",
|
"@vue/test-utils": "2.4.6",
|
||||||
"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.17.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.6.0",
|
||||||
"eslint-typegen": "0.3.2",
|
"eslint-typegen": "1.0.0",
|
||||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||||
"happy-dom": "16.3.0",
|
"happy-dom": "16.6.0",
|
||||||
"installed-check": "9.3.0",
|
"installed-check": "9.3.0",
|
||||||
"jiti": "2.4.2",
|
"jiti": "2.4.2",
|
||||||
"knip": "5.41.1",
|
"knip": "5.42.1",
|
||||||
"markdownlint-cli": "0.43.0",
|
"markdownlint-cli": "0.43.0",
|
||||||
"memfs": "4.15.3",
|
"memfs": "4.17.0",
|
||||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||||
"nuxi": "3.18.2",
|
|
||||||
"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.0",
|
"pathe": "2.0.1",
|
||||||
|
"pkg-pr-new": "0.0.39",
|
||||||
"playwright-core": "1.49.1",
|
"playwright-core": "1.49.1",
|
||||||
|
"rollup": "4.30.1",
|
||||||
"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",
|
||||||
"typescript": "5.7.2",
|
"typescript": "5.7.3",
|
||||||
"ufo": "1.5.4",
|
"ufo": "1.5.4",
|
||||||
"vitest": "2.1.8",
|
"vitest": "2.1.8",
|
||||||
"vitest-environment-nuxt": "1.0.1",
|
"vitest-environment-nuxt": "1.0.1",
|
||||||
@ -118,9 +120,6 @@
|
|||||||
"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": ""
|
||||||
}
|
}
|
||||||
|
@ -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.0",
|
"pathe": "^2.0.1",
|
||||||
"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": "^3.14.6",
|
||||||
"untyped": "^1.5.2"
|
"untyped": "^1.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rspack/core": "1.1.8",
|
"@rspack/core": "1.1.8",
|
||||||
"@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-28796231.359af68d",
|
||||||
"unbuild": "3.2.0",
|
"unbuild": "3.3.1",
|
||||||
"vite": "6.0.7",
|
"vite": "6.0.7",
|
||||||
"vitest": "2.1.8",
|
"vitest": "2.1.8",
|
||||||
"webpack": "5.97.1"
|
"webpack": "5.97.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.20.5"
|
"node": ">=18.0.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)
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import { MODE_RE } from './utils'
|
|||||||
export async function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) {
|
export async function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
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) })
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ export type AddComponentOptions = { name: string, filePath: string } & Partial<E
|
|||||||
export async function addComponent (opts: AddComponentOptions) {
|
export async function addComponent (opts: AddComponentOptions) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
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) || []
|
||||||
|
@ -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
|
||||||
|
@ -16,7 +16,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
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ export async function installModule<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -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))) {
|
||||||
@ -291,6 +292,10 @@ export async function _generateTypes (nuxt: Nuxt) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure `#build` is placed at the end of the paths object.
|
||||||
|
// https://github.com/nuxt/nuxt/issues/30325
|
||||||
|
sortTsPaths(tsConfig.compilerOptions.paths)
|
||||||
|
|
||||||
tsConfig.include = [...new Set(tsConfig.include.map(p => isAbsolute(p) ? relativeWithDot(nuxt.options.buildDir, p) : p))]
|
tsConfig.include = [...new Set(tsConfig.include.map(p => isAbsolute(p) ? relativeWithDot(nuxt.options.buildDir, p) : p))]
|
||||||
tsConfig.exclude = [...new Set(tsConfig.exclude!.map(p => isAbsolute(p) ? relativeWithDot(nuxt.options.buildDir, p) : p))]
|
tsConfig.exclude = [...new Set(tsConfig.exclude!.map(p => isAbsolute(p) ? relativeWithDot(nuxt.options.buildDir, p) : p))]
|
||||||
|
|
||||||
@ -330,6 +335,17 @@ export async function writeTypes (nuxt: Nuxt) {
|
|||||||
await writeFile()
|
await writeFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortTsPaths (paths: Record<string, string[]>) {
|
||||||
|
for (const pathKey in paths) {
|
||||||
|
if (pathKey.startsWith('#build')) {
|
||||||
|
const pathValue = paths[pathKey]!
|
||||||
|
// Delete & Reassign to ensure key is inserted at the end of object.
|
||||||
|
delete paths[pathKey]
|
||||||
|
paths[pathKey] = pathValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderAttrs (obj: Record<string, string>) {
|
function renderAttrs (obj: Record<string, string>) {
|
||||||
const attrs: string[] = []
|
const attrs: string[] = []
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
|
@ -3,4 +3,19 @@ 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+)*$/
|
||||||
|
@ -59,4 +59,42 @@ describe('tsConfig generation', () => {
|
|||||||
]
|
]
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should add #build after #components to paths', async () => {
|
||||||
|
const { tsConfig } = await _generateTypes(mockNuxtWithOptions({
|
||||||
|
alias: {
|
||||||
|
'~': '/my-app',
|
||||||
|
'@': '/my-app',
|
||||||
|
'some-custom-alias': '/my-app/some-alias',
|
||||||
|
'#build': './build-dir',
|
||||||
|
'#build/*': './build-dir/*',
|
||||||
|
'#imports': './imports',
|
||||||
|
'#components': './components',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
expect(tsConfig.compilerOptions?.paths).toMatchObject({
|
||||||
|
'~': [
|
||||||
|
'..',
|
||||||
|
],
|
||||||
|
'some-custom-alias': [
|
||||||
|
'../some-alias',
|
||||||
|
],
|
||||||
|
'@': [
|
||||||
|
'..',
|
||||||
|
],
|
||||||
|
'#imports': [
|
||||||
|
'./imports',
|
||||||
|
],
|
||||||
|
'#components': [
|
||||||
|
'./components',
|
||||||
|
],
|
||||||
|
'#build': [
|
||||||
|
'./build-dir',
|
||||||
|
],
|
||||||
|
'#build/*': [
|
||||||
|
'./build-dir/*',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import 'nuxi/cli'
|
import '@nuxt/cli/cli'
|
||||||
|
@ -22,7 +22,7 @@ export default defineBuildConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependencies: [
|
dependencies: [
|
||||||
'nuxi',
|
'@nuxt/cli',
|
||||||
'vue-router',
|
'vue-router',
|
||||||
'ofetch',
|
'ofetch',
|
||||||
],
|
],
|
||||||
|
@ -64,22 +64,23 @@
|
|||||||
"test:attw": "attw --pack"
|
"test:attw": "attw --pack"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nuxt/cli": "^3.20.0",
|
||||||
"@nuxt/devalue": "^2.0.2",
|
"@nuxt/devalue": "^2.0.2",
|
||||||
"@nuxt/devtools": "^1.7.0",
|
"@nuxt/devtools": "^1.7.0",
|
||||||
"@nuxt/kit": "workspace:*",
|
"@nuxt/kit": "workspace:*",
|
||||||
"@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.14",
|
"@unhead/dom": "^1.11.18",
|
||||||
"@unhead/shared": "^1.11.14",
|
"@unhead/shared": "^1.11.18",
|
||||||
"@unhead/ssr": "^1.11.14",
|
"@unhead/ssr": "^1.11.18",
|
||||||
"@unhead/vue": "^1.11.14",
|
"@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",
|
||||||
@ -91,35 +92,34 @@
|
|||||||
"globby": "^14.0.2",
|
"globby": "^14.0.2",
|
||||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||||
"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.1.1",
|
||||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||||
"nuxi": "^3.18.2",
|
|
||||||
"nypm": "^0.4.1",
|
"nypm": "^0.4.1",
|
||||||
"ofetch": "^1.4.1",
|
"ofetch": "^1.4.1",
|
||||||
"ohash": "^1.1.4",
|
"ohash": "^1.1.4",
|
||||||
"pathe": "^2.0.0",
|
"pathe": "^2.0.1",
|
||||||
"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.14",
|
"unhead": "^1.11.18",
|
||||||
"unimport": "^3.14.5",
|
"unimport": "^3.14.6",
|
||||||
"unplugin": "^2.1.2",
|
"unplugin": "^2.1.2",
|
||||||
"unplugin-vue-router": "^0.10.9",
|
"unplugin-vue-router": "^0.10.9",
|
||||||
"unstorage": "^1.14.4",
|
"unstorage": "^1.14.4",
|
||||||
@ -135,7 +135,7 @@
|
|||||||
"@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.7",
|
||||||
"vitest": "2.1.8"
|
"vitest": "2.1.8"
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
|
@ -250,7 +250,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 +275,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 = {}')
|
||||||
|
@ -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)
|
||||||
|
@ -53,7 +53,7 @@ export function installNuxtModule (name: string, options?: EnsurePackageInstalle
|
|||||||
installPrompts.add(name)
|
installPrompts.add(name)
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
return promptToInstall(name, async () => {
|
return promptToInstall(name, async () => {
|
||||||
const { runCommand } = await import('nuxi')
|
const { runCommand } = await import('@nuxt/cli')
|
||||||
await runCommand('module', ['add', name, '--cwd', nuxt.options.rootDir])
|
await runCommand('module', ['add', name, '--cwd', nuxt.options.rootDir])
|
||||||
}, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options })
|
}, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options })
|
||||||
}
|
}
|
||||||
|
@ -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') })
|
||||||
})
|
})
|
||||||
|
@ -81,7 +81,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',
|
||||||
]
|
]
|
||||||
@ -802,8 +802,13 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
|||||||
|
|
||||||
const nuxt = createNuxt(options)
|
const nuxt = createNuxt(options)
|
||||||
|
|
||||||
|
if (nuxt.options.dev && !nuxt.options.test) {
|
||||||
|
nuxt.hooks.hookOnce('build:done', () => {
|
||||||
for (const dep of keyDependencies) {
|
for (const dep of keyDependencies) {
|
||||||
checkDependencyVersion(dep, nuxt._version)
|
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
|
||||||
@ -822,7 +827,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}.`])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -53,12 +53,30 @@ export function withLocations<T> (node: T): WithLocations<T> {
|
|||||||
return node as WithLocations<T>
|
return node as WithLocations<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to check whether scope A is a child of scope B.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* isChildScope('0-1-2', '0-1') // true
|
||||||
|
* isChildScope('0-1', '0-1') // false
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param a the child scope
|
||||||
|
* @param b the parent scope
|
||||||
|
* @returns true if scope A is a child of scope B, false otherwise (also when they are the same)
|
||||||
|
*/
|
||||||
|
function isChildScope (a: string, b: string) {
|
||||||
|
return a.startsWith(b) && a.length > b.length
|
||||||
|
}
|
||||||
|
|
||||||
abstract class BaseNode<T extends Node = Node> {
|
abstract class BaseNode<T extends Node = Node> {
|
||||||
abstract type: string
|
abstract type: string
|
||||||
|
readonly scope: string
|
||||||
node: WithLocations<T>
|
node: WithLocations<T>
|
||||||
|
|
||||||
constructor (node: WithLocations<T>) {
|
constructor (node: WithLocations<T>, scope: string) {
|
||||||
this.node = node
|
this.node = node
|
||||||
|
this.scope = scope
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,6 +90,14 @@ abstract class BaseNode<T extends Node = Node> {
|
|||||||
* For instance, for a function parameter, this would be the end of the function declaration.
|
* For instance, for a function parameter, this would be the end of the function declaration.
|
||||||
*/
|
*/
|
||||||
abstract get end (): number
|
abstract get end (): number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the node is defined under a specific scope.
|
||||||
|
* @param scope
|
||||||
|
*/
|
||||||
|
isUnderScope (scope: string) {
|
||||||
|
return isChildScope(this.scope, scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IdentifierNode extends BaseNode<Identifier> {
|
class IdentifierNode extends BaseNode<Identifier> {
|
||||||
@ -90,8 +116,8 @@ class FunctionParamNode extends BaseNode {
|
|||||||
type = 'FunctionParam' as const
|
type = 'FunctionParam' as const
|
||||||
fnNode: WithLocations<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression>
|
fnNode: WithLocations<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression>
|
||||||
|
|
||||||
constructor (node: WithLocations<Node>, fnNode: WithLocations<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression>) {
|
constructor (node: WithLocations<Node>, scope: string, fnNode: WithLocations<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression>) {
|
||||||
super(node)
|
super(node, scope)
|
||||||
this.fnNode = fnNode
|
this.fnNode = fnNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,8 +146,8 @@ class VariableNode extends BaseNode<Identifier> {
|
|||||||
type = 'Variable' as const
|
type = 'Variable' as const
|
||||||
variableNode: WithLocations<VariableDeclaration>
|
variableNode: WithLocations<VariableDeclaration>
|
||||||
|
|
||||||
constructor (node: WithLocations<Identifier>, variableNode: WithLocations<VariableDeclaration>) {
|
constructor (node: WithLocations<Identifier>, scope: string, variableNode: WithLocations<VariableDeclaration>) {
|
||||||
super(node)
|
super(node, scope)
|
||||||
this.variableNode = variableNode
|
this.variableNode = variableNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,8 +164,8 @@ class ImportNode extends BaseNode<ImportSpecifier | ImportDefaultSpecifier | Imp
|
|||||||
type = 'Import' as const
|
type = 'Import' as const
|
||||||
importNode: WithLocations<Node>
|
importNode: WithLocations<Node>
|
||||||
|
|
||||||
constructor (node: WithLocations<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>, importNode: WithLocations<Node>) {
|
constructor (node: WithLocations<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>, scope: string, importNode: WithLocations<Node>) {
|
||||||
super(node)
|
super(node, scope)
|
||||||
this.importNode = importNode
|
this.importNode = importNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,8 +182,8 @@ class CatchParamNode extends BaseNode {
|
|||||||
type = 'CatchParam' as const
|
type = 'CatchParam' as const
|
||||||
catchNode: WithLocations<CatchClause>
|
catchNode: WithLocations<CatchClause>
|
||||||
|
|
||||||
constructor (node: WithLocations<Node>, catchNode: WithLocations<CatchClause>) {
|
constructor (node: WithLocations<Node>, scope: string, catchNode: WithLocations<CatchClause>) {
|
||||||
super(node)
|
super(node, scope)
|
||||||
this.catchNode = catchNode
|
this.catchNode = catchNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +290,7 @@ export class ScopeTracker {
|
|||||||
|
|
||||||
const identifiers = getPatternIdentifiers(param)
|
const identifiers = getPatternIdentifiers(param)
|
||||||
for (const identifier of identifiers) {
|
for (const identifier of identifiers) {
|
||||||
this.declareIdentifier(identifier.name, new FunctionParamNode(identifier, fn))
|
this.declareIdentifier(identifier.name, new FunctionParamNode(identifier, this.scopeIndexKey, fn))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,10 +302,10 @@ export class ScopeTracker {
|
|||||||
this.declareIdentifier(
|
this.declareIdentifier(
|
||||||
identifier.name,
|
identifier.name,
|
||||||
parent.type === 'VariableDeclaration'
|
parent.type === 'VariableDeclaration'
|
||||||
? new VariableNode(identifier, parent)
|
? new VariableNode(identifier, this.scopeIndexKey, parent)
|
||||||
: parent.type === 'CatchClause'
|
: parent.type === 'CatchClause'
|
||||||
? new CatchParamNode(identifier, parent)
|
? new CatchParamNode(identifier, this.scopeIndexKey, parent)
|
||||||
: new FunctionParamNode(identifier, parent),
|
: new FunctionParamNode(identifier, this.scopeIndexKey, parent),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -295,7 +321,7 @@ export class ScopeTracker {
|
|||||||
case 'FunctionDeclaration':
|
case 'FunctionDeclaration':
|
||||||
// declare function name for named functions, skip for `export default`
|
// declare function name for named functions, skip for `export default`
|
||||||
if (node.id?.name) {
|
if (node.id?.name) {
|
||||||
this.declareIdentifier(node.id.name, new FunctionNode(node))
|
this.declareIdentifier(node.id.name, new FunctionNode(node, this.scopeIndexKey))
|
||||||
}
|
}
|
||||||
this.pushScope()
|
this.pushScope()
|
||||||
for (const param of node.params) {
|
for (const param of node.params) {
|
||||||
@ -309,7 +335,7 @@ export class ScopeTracker {
|
|||||||
this.pushScope()
|
this.pushScope()
|
||||||
// can be undefined, for example in class method definitions
|
// can be undefined, for example in class method definitions
|
||||||
if (node.id?.name) {
|
if (node.id?.name) {
|
||||||
this.declareIdentifier(node.id.name, new FunctionNode(node))
|
this.declareIdentifier(node.id.name, new FunctionNode(node, this.scopeIndexKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pushScope()
|
this.pushScope()
|
||||||
@ -333,7 +359,7 @@ export class ScopeTracker {
|
|||||||
case 'ClassDeclaration':
|
case 'ClassDeclaration':
|
||||||
// declare class name for named classes, skip for `export default`
|
// declare class name for named classes, skip for `export default`
|
||||||
if (node.id?.name) {
|
if (node.id?.name) {
|
||||||
this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id)))
|
this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id), this.scopeIndexKey))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -342,13 +368,13 @@ export class ScopeTracker {
|
|||||||
// e.g. const MyClass = class InternalClassName { // InternalClassName is only available within the class body
|
// e.g. const MyClass = class InternalClassName { // InternalClassName is only available within the class body
|
||||||
this.pushScope()
|
this.pushScope()
|
||||||
if (node.id?.name) {
|
if (node.id?.name) {
|
||||||
this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id)))
|
this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id), this.scopeIndexKey))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'ImportDeclaration':
|
case 'ImportDeclaration':
|
||||||
for (const specifier of node.specifiers) {
|
for (const specifier of node.specifiers) {
|
||||||
this.declareIdentifier(specifier.local.name, new ImportNode(withLocations(specifier), node))
|
this.declareIdentifier(specifier.local.name, new ImportNode(withLocations(specifier), this.scopeIndexKey, node))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -429,6 +455,26 @@ export class ScopeTracker {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrentScope () {
|
||||||
|
return this.scopeIndexKey
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current scope is a child of a specific scope.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // current scope is 0-1
|
||||||
|
* isCurrentScopeUnder('0') // true
|
||||||
|
* isCurrentScopeUnder('0-1') // false
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param scope the parent scope
|
||||||
|
* @returns `true` if the current scope is a child of the specified scope, `false` otherwise (also when they are the same)
|
||||||
|
*/
|
||||||
|
isCurrentScopeUnder (scope: string) {
|
||||||
|
return isChildScope(this.scopeIndexKey, scope)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Freezes the scope tracker, preventing further declarations.
|
* Freezes the scope tracker, preventing further declarations.
|
||||||
* It also resets the scope index stack to its initial state, so that the scope tracker can be reused.
|
* It also resets the scope index stack to its initial state, so that the scope tracker can be reused.
|
||||||
|
@ -530,8 +530,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
|
||||||
@ -642,7 +642,7 @@ if (import.meta.hot) {
|
|||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
router.addRoute(route)
|
router.addRoute(route)
|
||||||
}
|
}
|
||||||
router.replace('')
|
router.replace(router.currentRoute.value.fullPath)
|
||||||
}
|
}
|
||||||
if (routes && 'then' in routes) {
|
if (routes && 'then' in routes) {
|
||||||
routes.then(addRoutes)
|
routes.then(addRoutes)
|
||||||
|
@ -228,6 +228,8 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
|||||||
|
|
||||||
if (!meta) { return }
|
if (!meta) { return }
|
||||||
|
|
||||||
|
const definePageMetaScope = scopeTracker.getCurrentScope()
|
||||||
|
|
||||||
walk(meta, {
|
walk(meta, {
|
||||||
scopeTracker,
|
scopeTracker,
|
||||||
enter (node, parent) {
|
enter (node, parent) {
|
||||||
@ -236,10 +238,24 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
|||||||
|| node.type !== 'Identifier' // checking for `node.type` to narrow down the type
|
|| node.type !== 'Identifier' // checking for `node.type` to narrow down the type
|
||||||
) { return }
|
) { return }
|
||||||
|
|
||||||
|
const declaration = scopeTracker.getDeclaration(node.name)
|
||||||
|
if (declaration) {
|
||||||
|
// check if the declaration was made inside `definePageMeta` and if so, do not process it
|
||||||
|
// (ensures that we don't hoist local variables in inline middleware, for example)
|
||||||
|
if (
|
||||||
|
declaration.isUnderScope(definePageMetaScope)
|
||||||
|
// ensures that we compare the correct declaration to the reference
|
||||||
|
// (when in the same scope, the declaration must come before the reference, otherwise it must be in a parent scope)
|
||||||
|
&& (scopeTracker.isCurrentScopeUnder(declaration.scope) || declaration.start < node.start)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isStaticIdentifier(node.name)) {
|
if (isStaticIdentifier(node.name)) {
|
||||||
addImport(node.name)
|
addImport(node.name)
|
||||||
} else {
|
} else if (declaration) {
|
||||||
processDeclaration(scopeTracker.getDeclaration(node.name))
|
processDeclaration(declaration)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -271,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,8 @@ 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"`)
|
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,10 +2,7 @@ import { fileURLToPath } from 'node:url'
|
|||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, 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 { 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))))
|
||||||
|
|
||||||
@ -45,45 +42,3 @@ describe('loadNuxt', () => {
|
|||||||
expect(hookRan).toBe(true)
|
expect(hookRan).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
@ -393,6 +393,188 @@ definePageMeta({
|
|||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not import static identifiers when shadowed in the same scope', () => {
|
||||||
|
const sfc = `
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useState } from '#app/composables/state'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: () => {
|
||||||
|
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||||
|
const auth = useState('auth')
|
||||||
|
if (!auth.value.isLoggedIn) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
`
|
||||||
|
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||||
|
expect(transformPlugin.transform.call({
|
||||||
|
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
locations: true,
|
||||||
|
...opts,
|
||||||
|
}),
|
||||||
|
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
||||||
|
"const __nuxt_page_meta = {
|
||||||
|
middleware: () => {
|
||||||
|
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||||
|
const auth = useState('auth')
|
||||||
|
if (!auth.value.isLoggedIn) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export default __nuxt_page_meta"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not import static identifiers when shadowed in parent scope', () => {
|
||||||
|
const sfc = `
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useState } from '#app/composables/state'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: () => {
|
||||||
|
function isLoggedIn() {
|
||||||
|
const auth = useState('auth')
|
||||||
|
return auth.value.isLoggedIn
|
||||||
|
}
|
||||||
|
|
||||||
|
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
`
|
||||||
|
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||||
|
expect(transformPlugin.transform.call({
|
||||||
|
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
locations: true,
|
||||||
|
...opts,
|
||||||
|
}),
|
||||||
|
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
||||||
|
"const __nuxt_page_meta = {
|
||||||
|
middleware: () => {
|
||||||
|
function isLoggedIn() {
|
||||||
|
const auth = useState('auth')
|
||||||
|
return auth.value.isLoggedIn
|
||||||
|
}
|
||||||
|
|
||||||
|
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export default __nuxt_page_meta"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should import static identifiers when a shadowed and a non-shadowed one is used', () => {
|
||||||
|
const sfc = `
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useState } from '#app/composables/state'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: [
|
||||||
|
() => {
|
||||||
|
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||||
|
const auth = useState('auth')
|
||||||
|
if (!auth.value.isLoggedIn) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
const auth = useState('auth')
|
||||||
|
if (!auth.value.isLoggedIn) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
`
|
||||||
|
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||||
|
expect(transformPlugin.transform.call({
|
||||||
|
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
locations: true,
|
||||||
|
...opts,
|
||||||
|
}),
|
||||||
|
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
||||||
|
"import { useState } from '#app/composables/state'
|
||||||
|
|
||||||
|
const __nuxt_page_meta = {
|
||||||
|
middleware: [
|
||||||
|
() => {
|
||||||
|
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||||
|
const auth = useState('auth')
|
||||||
|
if (!auth.value.isLoggedIn) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
const auth = useState('auth')
|
||||||
|
if (!auth.value.isLoggedIn) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
export default __nuxt_page_meta"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should import static identifiers when a shadowed and a non-shadowed one is used in the same scope', () => {
|
||||||
|
const sfc = `
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useState } from '#app/composables/state'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: () => {
|
||||||
|
const auth1 = useState('auth')
|
||||||
|
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||||
|
const auth2 = useState('auth')
|
||||||
|
if (!auth1.value.isLoggedIn || !auth2.value.isLoggedIn) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
`
|
||||||
|
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||||
|
expect(transformPlugin.transform.call({
|
||||||
|
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
locations: true,
|
||||||
|
...opts,
|
||||||
|
}),
|
||||||
|
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
||||||
|
"import { useState } from '#app/composables/state'
|
||||||
|
|
||||||
|
const __nuxt_page_meta = {
|
||||||
|
middleware: () => {
|
||||||
|
const auth1 = useState('auth')
|
||||||
|
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||||
|
const auth2 = useState('auth')
|
||||||
|
if (!auth1.value.isLoggedIn || !auth2.value.isLoggedIn) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export default __nuxt_page_meta"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
it('should work with esbuild.keepNames = true', async () => {
|
it('should work with esbuild.keepNames = true', async () => {
|
||||||
const sfc = `
|
const sfc = `
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -516,7 +698,12 @@ definePageMeta({
|
|||||||
test () {}
|
test () {}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(hoisted.value)
|
const someFunction = () => {
|
||||||
|
const someValue = 'someValue'
|
||||||
|
console.log(someValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(hoisted.value, val)
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
validate: (route) => {
|
validate: (route) => {
|
||||||
@ -564,7 +751,12 @@ const hoisted = ref('hoisted')
|
|||||||
test () {}
|
test () {}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(hoisted.value)
|
const someFunction = () => {
|
||||||
|
const someValue = 'someValue'
|
||||||
|
console.log(someValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(hoisted.value, val)
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
validate: (route) => {
|
validate: (route) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { assert, describe, expect, it } from 'vitest'
|
||||||
import { getUndeclaredIdentifiersInFunction, parseAndWalk } from '../src/core/utils/parse'
|
import { getUndeclaredIdentifiersInFunction, parseAndWalk } from '../src/core/utils/parse'
|
||||||
import { TestScopeTracker } from './fixture/scope-tracker'
|
import { TestScopeTracker } from './fixture/scope-tracker'
|
||||||
|
|
||||||
@ -667,4 +667,87 @@ describe('parsing', () => {
|
|||||||
|
|
||||||
expect(processedFunctions).toBe(5)
|
expect(processedFunctions).toBe(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it ('should correctly compare identifiers defined in different scopes', () => {
|
||||||
|
const code = `
|
||||||
|
// ""
|
||||||
|
const a = 1
|
||||||
|
|
||||||
|
// ""
|
||||||
|
const func = () => {
|
||||||
|
// "0-0"
|
||||||
|
const b = 2
|
||||||
|
|
||||||
|
// "0-0"
|
||||||
|
function foo() {
|
||||||
|
// "0-0-0-0"
|
||||||
|
const c = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ""
|
||||||
|
const func2 = () => {
|
||||||
|
// "1-0"
|
||||||
|
const d = 2
|
||||||
|
|
||||||
|
// "1-0"
|
||||||
|
function bar() {
|
||||||
|
// "1-0-0-0"
|
||||||
|
const e = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ""
|
||||||
|
const f = 4
|
||||||
|
`
|
||||||
|
|
||||||
|
const scopeTracker = new TestScopeTracker({
|
||||||
|
keepExitedScopes: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
parseAndWalk(code, filename, {
|
||||||
|
scopeTracker,
|
||||||
|
})
|
||||||
|
|
||||||
|
const a = scopeTracker.getDeclarationFromScope('a', '')
|
||||||
|
const func = scopeTracker.getDeclarationFromScope('func', '')
|
||||||
|
const foo = scopeTracker.getDeclarationFromScope('foo', '0-0')
|
||||||
|
const b = scopeTracker.getDeclarationFromScope('b', '0-0')
|
||||||
|
const c = scopeTracker.getDeclarationFromScope('c', '0-0-0-0')
|
||||||
|
const func2 = scopeTracker.getDeclarationFromScope('func2', '')
|
||||||
|
const bar = scopeTracker.getDeclarationFromScope('bar', '1-0')
|
||||||
|
const d = scopeTracker.getDeclarationFromScope('d', '1-0')
|
||||||
|
const e = scopeTracker.getDeclarationFromScope('e', '1-0-0-0')
|
||||||
|
const f = scopeTracker.getDeclarationFromScope('f', '')
|
||||||
|
|
||||||
|
assert(a && func && foo && b && c && func2 && bar && d && e && f, 'All declarations should be found')
|
||||||
|
|
||||||
|
// identifiers in the same scope should be equal
|
||||||
|
expect(f.isUnderScope(a.scope)).toBe(false)
|
||||||
|
expect(func.isUnderScope(a.scope)).toBe(false)
|
||||||
|
expect(d.isUnderScope(bar.scope)).toBe(false)
|
||||||
|
|
||||||
|
// identifiers in deeper scopes should be under the scope of the parent scope
|
||||||
|
expect(b.isUnderScope(a.scope)).toBe(true)
|
||||||
|
expect(b.isUnderScope(func.scope)).toBe(true)
|
||||||
|
expect(c.isUnderScope(a.scope)).toBe(true)
|
||||||
|
expect(c.isUnderScope(b.scope)).toBe(true)
|
||||||
|
expect(d.isUnderScope(a.scope)).toBe(true)
|
||||||
|
expect(d.isUnderScope(func2.scope)).toBe(true)
|
||||||
|
expect(e.isUnderScope(a.scope)).toBe(true)
|
||||||
|
expect(e.isUnderScope(d.scope)).toBe(true)
|
||||||
|
|
||||||
|
// identifiers in parent scope should not be under the scope of the children
|
||||||
|
expect(a.isUnderScope(b.scope)).toBe(false)
|
||||||
|
expect(a.isUnderScope(c.scope)).toBe(false)
|
||||||
|
expect(a.isUnderScope(d.scope)).toBe(false)
|
||||||
|
expect(a.isUnderScope(e.scope)).toBe(false)
|
||||||
|
expect(b.isUnderScope(c.scope)).toBe(false)
|
||||||
|
|
||||||
|
// identifiers in parallel scopes should not influence each other
|
||||||
|
expect(d.isUnderScope(b.scope)).toBe(false)
|
||||||
|
expect(e.isUnderScope(b.scope)).toBe(false)
|
||||||
|
expect(b.isUnderScope(d.scope)).toBe(false)
|
||||||
|
expect(c.isUnderScope(e.scope)).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
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()
|
||||||
|
}
|
@ -47,11 +47,11 @@
|
|||||||
"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.15.3",
|
"memfs": "^4.17.0",
|
||||||
"ohash": "^1.1.4",
|
"ohash": "^1.1.4",
|
||||||
"pathe": "^2.0.0",
|
"pathe": "^2.0.1",
|
||||||
"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.0",
|
"rollup": "4.30.1",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,9 @@ export default defineBuildConfig({
|
|||||||
'src/index',
|
'src/index',
|
||||||
'src/builder-env',
|
'src/builder-env',
|
||||||
],
|
],
|
||||||
hooks: {
|
rollup: {
|
||||||
'rollup:options' (ctx, options) {
|
dts: { respectExternal: false },
|
||||||
ctx.options.rollup.dts.respectExternal = false
|
inlineDependencies: ['untyped', 'knitwork'],
|
||||||
const isExternal = options.external! as (id: string, importer?: string, isResolved?: boolean) => boolean
|
|
||||||
options.external = (source, importer, isResolved) => {
|
|
||||||
if (source === 'untyped' || source === 'knitwork') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isExternal(source, importer, isResolved)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
// Type imports
|
// Type imports
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/pug": "2.0.10",
|
"@types/pug": "2.0.10",
|
||||||
"@unhead/schema": "1.11.14",
|
"@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",
|
||||||
@ -49,15 +49,15 @@
|
|||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||||
"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-28796231.359af68d",
|
||||||
"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": "3.14.6",
|
||||||
"untyped": "1.5.2",
|
"untyped": "1.5.2",
|
||||||
"vite": "6.0.7",
|
"vite": "6.0.7",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.13",
|
||||||
@ -68,9 +68,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.0",
|
"pathe": "^2.0.1",
|
||||||
"std-env": "^3.8.0"
|
"std-env": "^3.8.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -17,22 +17,19 @@
|
|||||||
"prerender": "pnpm build && jiti ./lib/prerender"
|
"prerender": "pnpm build && jiti ./lib/prerender"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@unocss/reset": "0.65.3",
|
"@unocss/reset": "65.4.0",
|
||||||
"beasties": "0.2.0",
|
"beasties": "0.2.0",
|
||||||
"html-validate": "9.1.1",
|
"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.0",
|
"pathe": "2.0.1",
|
||||||
"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": "0.65.3",
|
"unocss": "65.4.0",
|
||||||
"vite": "6.0.7"
|
"vite": "6.0.7"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"rollup": "4.30.0",
|
"rollup": "4.30.1",
|
||||||
"unbuild": "3.2.0",
|
"unbuild": "3.3.1",
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -36,7 +36,7 @@
|
|||||||
"@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",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"consola": "^3.3.3",
|
"consola": "^3.4.0",
|
||||||
"cssnano": "^7.0.6",
|
"cssnano": "^7.0.6",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"esbuild": "^0.24.2",
|
"esbuild": "^0.24.2",
|
||||||
@ -47,10 +47,10 @@
|
|||||||
"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",
|
||||||
"mlly": "^1.7.3",
|
"mlly": "^1.7.4",
|
||||||
"pathe": "^2.0.0",
|
"pathe": "^2.0.1",
|
||||||
"pkg-types": "^1.3.0",
|
"pkg-types": "^1.3.1",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.5.1",
|
||||||
"rollup-plugin-visualizer": "^5.13.1",
|
"rollup-plugin-visualizer": "^5.13.1",
|
||||||
"std-env": "^3.8.0",
|
"std-env": "^3.8.0",
|
||||||
"ufo": "^1.5.4",
|
"ufo": "^1.5.4",
|
||||||
@ -65,6 +65,6 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,9 @@ export async function buildClient (ctx: ViteBuildContext) {
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
// work around vite optimizer bug
|
||||||
|
'#app-manifest': 'unenv/runtime/mock/empty',
|
||||||
|
// user aliases
|
||||||
...nodeCompat.alias,
|
...nodeCompat.alias,
|
||||||
...ctx.config.resolve?.alias,
|
...ctx.config.resolve?.alias,
|
||||||
'nitro/runtime': join(ctx.nuxt.options.buildDir, 'nitro.client.mjs'),
|
'nitro/runtime': join(ctx.nuxt.options.buildDir, 'nitro.client.mjs'),
|
||||||
|
@ -44,7 +44,7 @@ export function viteNodePlugin (ctx: ViteBuildContext): VitePlugin {
|
|||||||
// invalidate changed virtual modules when templates are regenerated
|
// invalidate changed virtual modules when templates are regenerated
|
||||||
ctx.nuxt.hook('app:templatesGenerated', (_app, changedTemplates) => {
|
ctx.nuxt.hook('app:templatesGenerated', (_app, changedTemplates) => {
|
||||||
for (const template of changedTemplates) {
|
for (const template of changedTemplates) {
|
||||||
const mods = server.moduleGraph.getModulesByFile(`virtual:nuxt:${template.dst}`)
|
const mods = server.moduleGraph.getModulesByFile(`virtual:nuxt:${encodeURIComponent(template.dst)}`)
|
||||||
|
|
||||||
for (const mod of mods || []) {
|
for (const mod of mods || []) {
|
||||||
markInvalidate(mod)
|
markInvalidate(mod)
|
||||||
|
@ -212,7 +212,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
|||||||
// Invalidate virtual modules when templates are re-generated
|
// Invalidate virtual modules when templates are re-generated
|
||||||
ctx.nuxt.hook('app:templatesGenerated', (_app, changedTemplates) => {
|
ctx.nuxt.hook('app:templatesGenerated', (_app, changedTemplates) => {
|
||||||
for (const template of changedTemplates) {
|
for (const template of changedTemplates) {
|
||||||
for (const mod of server.moduleGraph.getModulesByFile(`virtual:nuxt:${template.dst}`) || []) {
|
for (const mod of server.moduleGraph.getModulesByFile(`virtual:nuxt:${encodeURIComponent(template.dst)}`) || []) {
|
||||||
server.moduleGraph.invalidateModule(mod)
|
server.moduleGraph.invalidateModule(mod)
|
||||||
server.reloadModule(mod)
|
server.reloadModule(mod)
|
||||||
}
|
}
|
||||||
|
@ -46,12 +46,12 @@
|
|||||||
"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.15.3",
|
"memfs": "^4.17.0",
|
||||||
"mini-css-extract-plugin": "^2.9.2",
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
"ohash": "^1.1.4",
|
"ohash": "^1.1.4",
|
||||||
"pathe": "^2.0.0",
|
"pathe": "^2.0.1",
|
||||||
"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",
|
||||||
@ -77,14 +77,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.0",
|
"rollup": "4.30.1",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ function clientNodeCompat (ctx: WebpackConfigContext) {
|
|||||||
}
|
}
|
||||||
ctx.config.plugins!.push(new webpack.DefinePlugin({ global: 'globalThis' }))
|
ctx.config.plugins!.push(new webpack.DefinePlugin({ global: 'globalThis' }))
|
||||||
|
|
||||||
ctx.config.resolve = ctx.config.resolve || {}
|
ctx.config.resolve ||= {}
|
||||||
ctx.config.resolve.fallback = {
|
ctx.config.resolve.fallback = {
|
||||||
...env(nodeless).alias,
|
...env(nodeless).alias,
|
||||||
...ctx.config.resolve.fallback,
|
...ctx.config.resolve.fallback,
|
||||||
@ -92,7 +92,7 @@ function clientHMR (ctx: WebpackConfigContext) {
|
|||||||
`webpack-hot-middleware/client?${hotMiddlewareClientOptionsStr}`,
|
`webpack-hot-middleware/client?${hotMiddlewareClientOptionsStr}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx.config.plugins = ctx.config.plugins || []
|
ctx.config.plugins ||= []
|
||||||
ctx.config.plugins.push(new webpack.HotModuleReplacementPlugin())
|
ctx.config.plugins.push(new webpack.HotModuleReplacementPlugin())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ function serverStandalone (ctx: WebpackConfigContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function serverPlugins (ctx: WebpackConfigContext) {
|
function serverPlugins (ctx: WebpackConfigContext) {
|
||||||
ctx.config.plugins = ctx.config.plugins || []
|
ctx.config.plugins ||= []
|
||||||
|
|
||||||
// Server polyfills
|
// Server polyfills
|
||||||
if (ctx.userConfig.serverURLPolyfill) {
|
if (ctx.userConfig.serverURLPolyfill) {
|
||||||
|
@ -50,7 +50,7 @@ function baseConfig (ctx: WebpackConfigContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function basePlugins (ctx: WebpackConfigContext) {
|
function basePlugins (ctx: WebpackConfigContext) {
|
||||||
ctx.config.plugins = ctx.config.plugins || []
|
ctx.config.plugins ||= []
|
||||||
|
|
||||||
// Add timefix-plugin before other plugins
|
// Add timefix-plugin before other plugins
|
||||||
if (ctx.options.dev) {
|
if (ctx.options.dev) {
|
||||||
|
3070
pnpm-lock.yaml
3070
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,10 @@
|
|||||||
"main",
|
"main",
|
||||||
"3.x"
|
"3.x"
|
||||||
],
|
],
|
||||||
|
"ignoreDeps": [
|
||||||
|
"node",
|
||||||
|
"npm"
|
||||||
|
],
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
"groupName": "vitest",
|
"groupName": "vitest",
|
||||||
|
@ -625,6 +625,44 @@ describe('pages', () => {
|
|||||||
const html = await $fetch('/prerender/test')
|
const html = await $fetch('/prerender/test')
|
||||||
expect(html).toContain('should be prerendered: true')
|
expect(html).toContain('should be prerendered: true')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should trigger page:loading:end only once', async () => {
|
||||||
|
const { page, consoleLogs } = await renderPage('/')
|
||||||
|
|
||||||
|
await page.getByText('to page load hook').click()
|
||||||
|
await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, '/page-load-hook')
|
||||||
|
const loadingEndLogs = consoleLogs.filter(c => c.text.includes('page:loading:end'))
|
||||||
|
expect(loadingEndLogs.length).toBe(1)
|
||||||
|
|
||||||
|
await page.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should hide nuxt page load indicator after navigate back from nested page', async () => {
|
||||||
|
const LOAD_INDICATOR_SELECTOR = '.nuxt-loading-indicator'
|
||||||
|
const { page } = await renderPage('/page-load-hook')
|
||||||
|
await page.getByText('To sub page').click()
|
||||||
|
await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, '/page-load-hook/subpage')
|
||||||
|
|
||||||
|
await page.waitForSelector(LOAD_INDICATOR_SELECTOR)
|
||||||
|
let isVisible = await page.isVisible(LOAD_INDICATOR_SELECTOR)
|
||||||
|
expect(isVisible).toBe(true)
|
||||||
|
|
||||||
|
await page.waitForSelector(LOAD_INDICATOR_SELECTOR, { state: 'hidden' })
|
||||||
|
isVisible = await page.isVisible(LOAD_INDICATOR_SELECTOR)
|
||||||
|
expect(isVisible).toBe(false)
|
||||||
|
|
||||||
|
await page.goBack()
|
||||||
|
|
||||||
|
await page.waitForSelector(LOAD_INDICATOR_SELECTOR)
|
||||||
|
isVisible = await page.isVisible(LOAD_INDICATOR_SELECTOR)
|
||||||
|
expect(isVisible).toBe(true)
|
||||||
|
|
||||||
|
await page.waitForSelector(LOAD_INDICATOR_SELECTOR, { state: 'hidden' })
|
||||||
|
isVisible = await page.isVisible(LOAD_INDICATOR_SELECTOR)
|
||||||
|
expect(isVisible).toBe(false)
|
||||||
|
|
||||||
|
await page.close()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('nuxt composables', () => {
|
describe('nuxt composables', () => {
|
||||||
@ -2738,7 +2776,7 @@ describe('teleports', () => {
|
|||||||
const html = await $fetch<string>('/nuxt-teleport')
|
const html = await $fetch<string>('/nuxt-teleport')
|
||||||
|
|
||||||
// Teleport is appended to body, after the __nuxt div
|
// Teleport is appended to body, after the __nuxt div
|
||||||
expect(html).toContain('<div><!--teleport start--><!--teleport end--><h1>Normal content</h1></div></div></div><span id="nuxt-teleport"><!--teleport start anchor--><div>Nuxt Teleport</div><!--teleport anchor--></span><script')
|
expect(html).toContain('<div><!--teleport start--><!--teleport end--><h1>Normal content</h1></div></div><!--]--></div><span id="nuxt-teleport"><!--teleport start anchor--><div>Nuxt Teleport</div><!--teleport anchor--></span><script')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"210k"`)
|
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"210k"`)
|
||||||
|
|
||||||
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
||||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1396k"`)
|
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1398k"`)
|
||||||
|
|
||||||
const packages = modules.files
|
const packages = modules.files
|
||||||
.filter(m => m.endsWith('package.json'))
|
.filter(m => m.endsWith('package.json'))
|
||||||
@ -86,6 +86,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
"entities",
|
"entities",
|
||||||
"estree-walker",
|
"estree-walker",
|
||||||
"hookable",
|
"hookable",
|
||||||
|
"packrup",
|
||||||
"source-map-js",
|
"source-map-js",
|
||||||
"ufo",
|
"ufo",
|
||||||
"unhead",
|
"unhead",
|
||||||
@ -102,7 +103,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"560k"`)
|
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"560k"`)
|
||||||
|
|
||||||
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
||||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"94.4k"`)
|
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"96.4k"`)
|
||||||
|
|
||||||
const packages = modules.files
|
const packages = modules.files
|
||||||
.filter(m => m.endsWith('package.json'))
|
.filter(m => m.endsWith('package.json'))
|
||||||
@ -116,6 +117,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
"db0",
|
"db0",
|
||||||
"devalue",
|
"devalue",
|
||||||
"hookable",
|
"hookable",
|
||||||
|
"packrup",
|
||||||
"unhead",
|
"unhead",
|
||||||
]
|
]
|
||||||
`)
|
`)
|
||||||
@ -128,7 +130,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"303k"`)
|
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"303k"`)
|
||||||
|
|
||||||
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
||||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1396k"`)
|
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1398k"`)
|
||||||
|
|
||||||
const packages = modules.files
|
const packages = modules.files
|
||||||
.filter(m => m.endsWith('package.json'))
|
.filter(m => m.endsWith('package.json'))
|
||||||
@ -153,6 +155,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
"entities",
|
"entities",
|
||||||
"estree-walker",
|
"estree-walker",
|
||||||
"hookable",
|
"hookable",
|
||||||
|
"packrup",
|
||||||
"source-map-js",
|
"source-map-js",
|
||||||
"ufo",
|
"ufo",
|
||||||
"unhead",
|
"unhead",
|
||||||
|
6
test/fixtures/basic/app.vue
vendored
Normal file
6
test/fixtures/basic/app.vue
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<NuxtLoadingIndicator :throttle="0" />
|
||||||
|
<NuxtLayout>
|
||||||
|
<NuxtPage />
|
||||||
|
</NuxtLayout>
|
||||||
|
</template>
|
3
test/fixtures/basic/pages/index.vue
vendored
3
test/fixtures/basic/pages/index.vue
vendored
@ -94,6 +94,9 @@
|
|||||||
<NuxtLink to="/server-page">
|
<NuxtLink to="/server-page">
|
||||||
to server page
|
to server page
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
<NuxtLink to="/page-load-hook">
|
||||||
|
to page load hook
|
||||||
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
9
test/fixtures/basic/pages/page-load-hook.vue
vendored
Normal file
9
test/fixtures/basic/pages/page-load-hook.vue
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Page for hook tests.
|
||||||
|
<NuxtLink to="/page-load-hook/subpage">
|
||||||
|
To sub page
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtPage />
|
||||||
|
</div>
|
||||||
|
</template>
|
7
test/fixtures/basic/pages/page-load-hook/[slug].vue
vendored
Normal file
7
test/fixtures/basic/pages/page-load-hook/[slug].vue
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtLink to="/page-load-hook">
|
||||||
|
Back to parent
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
8
test/fixtures/basic/plugins/page-hook-plugin.ts
vendored
Normal file
8
test/fixtures/basic/plugins/page-hook-plugin.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
const route = useRoute()
|
||||||
|
nuxtApp.hook('page:loading:end', () => {
|
||||||
|
if (route.path === '/page-load-hook') {
|
||||||
|
console.log('page:loading:end')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
@ -134,7 +134,7 @@ if (process.env.TEST_ENV !== 'built' && !isWindows) {
|
|||||||
'type': 'debug',
|
'type': 'debug',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'text': `[vite] hot updated: /@id/virtual:nuxt:${fixturePath}/.nuxt/routes.mjs`,
|
'text': `[vite] hot updated: /@id/virtual:nuxt:${encodeURIComponent(join(fixturePath, '.nuxt/routes.mjs'))}`,
|
||||||
'type': 'debug',
|
'type': 'debug',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
Loading…
Reference in New Issue
Block a user