mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-19 15:10:58 +00:00
Merge branch 'nuxt:main' into main
This commit is contained in:
commit
25a21f4d5a
@ -1,4 +1,4 @@
|
||||
FROM node:lts@sha256:fffa89e023a3351904c04284029105d9e2ac7020886d683775a298569591e5bb
|
||||
FROM node:lts@sha256:de4c8be8232b7081d8846360d73ce6dbff33c6636f2259cd14d82c0de1ac3ff2
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -fy libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 && \
|
||||
|
4
.github/workflows/autofix-docs.yml
vendored
4
.github/workflows/autofix-docs.yml
vendored
@ -17,9 +17,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/autofix.yml
vendored
4
.github/workflows/autofix.yml
vendored
@ -13,9 +13,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
6
.github/workflows/benchmark.yml
vendored
6
.github/workflows/benchmark.yml
vendored
@ -29,9 +29,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -46,7 +46,7 @@ jobs:
|
||||
run: pnpm build
|
||||
|
||||
- name: Run benchmarks
|
||||
uses: CodSpeedHQ/action@ab07afd34cbbb7a1306e8d14b7cc44e029eee37a # v3.0.0
|
||||
uses: CodSpeedHQ/action@b587655f756aab640e742fec141261bc6f0a569d # v3.0.1
|
||||
with:
|
||||
run: pnpm vitest bench
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
4
.github/workflows/changelog.yml
vendored
4
.github/workflows/changelog.yml
vendored
@ -22,11 +22,11 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@ -37,9 +37,9 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -72,10 +72,10 @@ jobs:
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
|
||||
uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
||||
with:
|
||||
config: |
|
||||
paths:
|
||||
@ -91,7 +91,7 @@ jobs:
|
||||
queries: +security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
|
||||
uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
||||
with:
|
||||
category: "/language:javascript-typescript"
|
||||
|
||||
@ -107,9 +107,9 @@ jobs:
|
||||
module: ["bundler", "node"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -138,9 +138,9 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -162,9 +162,9 @@ jobs:
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -219,9 +219,9 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: "pnpm"
|
||||
@ -271,11 +271,11 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@ -312,11 +312,11 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@ -17,6 +17,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
|
||||
uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0
|
||||
|
6
.github/workflows/docs-check-links.yml
vendored
6
.github/workflows/docs-check-links.yml
vendored
@ -19,17 +19,17 @@ jobs:
|
||||
steps:
|
||||
# Cache lychee results (e.g. to avoid hitting rate limits)
|
||||
- name: Restore lychee cache
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
|
||||
with:
|
||||
path: .lycheecache
|
||||
key: cache-lychee-${{ github.sha }}
|
||||
restore-keys: cache-lychee-
|
||||
|
||||
# check links with Lychee
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Lychee link checker
|
||||
uses: lycheeverse/lychee-action@2bb232618be239862e31382c5c0eaeba12e5e966 # for v1.8.0
|
||||
uses: lycheeverse/lychee-action@ae4699150ab670dcfb64cc74e8680e776d9caae2 # for v1.8.0
|
||||
with:
|
||||
# arguments with file types to check
|
||||
args: >-
|
||||
|
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
@ -21,9 +21,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/lint-sherif.yml
vendored
4
.github/workflows/lint-sherif.yml
vendored
@ -23,9 +23,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/lint-workflows.yml
vendored
4
.github/workflows/lint-workflows.yml
vendored
@ -23,9 +23,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
# From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
|
||||
- name: Check workflow files
|
||||
uses: docker://rhysd/actionlint:1.7.3@sha256:7617f05bd698cd2f1c3aedc05bc733ccec92cca0738f3e8722c32c5b42c70ae6
|
||||
uses: docker://rhysd/actionlint:1.7.4@sha256:82244e1db1c60d82c7792180a48dd0bcb838370bb589d53ff132503fc9485868
|
||||
with:
|
||||
args: -color
|
||||
|
4
.github/workflows/release-pr.yml
vendored
4
.github/workflows/release-pr.yml
vendored
@ -48,13 +48,13 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ steps.pr.outputs.head_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -19,11 +19,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org/"
|
||||
|
2
.github/workflows/reproduire.yml
vendored
2
.github/workflows/reproduire.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
reproduire:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
|
||||
with:
|
||||
label: needs reproduction
|
||||
|
4
.github/workflows/scorecards.yml
vendored
4
.github/workflows/scorecards.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
|
||||
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
||||
if: github.repository == 'nuxt/nuxt' && success()
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
2
.github/workflows/stackblitz-link.yml
vendored
2
.github/workflows/stackblitz-link.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
stackblitz:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: huang-julien/reproduire-sur-stackblitz@9ceccbfbb0f2f9a9a8db2d1f0dd909cf5cfe67aa # v1.0.2
|
||||
with:
|
||||
reproduction-heading: '### Reproduction'
|
||||
|
@ -68,6 +68,10 @@ When importing `@nuxt/test-utils` in your vitest config, It is necessary to have
|
||||
> ie. `vitest.config.m{ts,js}`.
|
||||
::
|
||||
|
||||
::tip
|
||||
It is possible to set environment variables for testing by using the `.env.test` file.
|
||||
::
|
||||
|
||||
### Using a Nuxt Runtime Environment
|
||||
|
||||
By default, `@nuxt/test-utils` will not change your default Vitest environment, so you can do fine-grained opt-in and run Nuxt tests together with other unit tests.
|
||||
@ -285,7 +289,7 @@ import { mockNuxtImport } from '@nuxt/test-utils/runtime'
|
||||
|
||||
const { useStorageMock } = vi.hoisted(() => {
|
||||
return {
|
||||
useStorageMock: vi.fn().mockImplementation(() => {
|
||||
useStorageMock: vi.fn(() => {
|
||||
return { value: 'mocked storage'}
|
||||
})
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ export default defineNuxtConfig({
|
||||
// app: 'app'
|
||||
// },
|
||||
// experimental: {
|
||||
// scanPageMeta: 'after-resolve',
|
||||
// sharedPrerenderData: false,
|
||||
// compileTemplate: true,
|
||||
// resetAsyncDataToUndefined: true,
|
||||
@ -236,6 +237,45 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
#### Scan Page Meta After Resolution
|
||||
|
||||
🚦 **Impact Level**: Minimal
|
||||
|
||||
##### What Changed
|
||||
|
||||
We now scan page metadata (defined in `definePageMeta`) _after_ calling the `pages:extend` hook rather than before.
|
||||
|
||||
##### Reasons for Change
|
||||
|
||||
This was to allow scanning metadata for pages that users wanted to add in `pages:extend`. We still offer an opportunity to change or override page metadata in a new `pages:resolved` hook.
|
||||
|
||||
##### Migration Steps
|
||||
|
||||
If you want to override page metadata, do that in `pages:resolved` rather than in `pages:extend`.
|
||||
|
||||
```diff
|
||||
export default defineNuxtConfig({
|
||||
hooks: {
|
||||
- 'pages:extend'(pages) {
|
||||
+ 'pages:resolved'(pages) {
|
||||
const myPage = pages.find(page => page.path === '/')
|
||||
myPage.meta ||= {}
|
||||
myPage.meta.layout = 'overridden-layout'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Alternatively, you can revert to the previous behaviour with:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
experimental: {
|
||||
scanPageMeta: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Shared Prerender Data
|
||||
|
||||
🚦 **Impact Level**: Medium
|
||||
|
@ -33,6 +33,7 @@ You don't have to use TypeScript to build an application with Nuxt. However, it
|
||||
You can configure fully typed, per-environment overrides in your nuxt.config
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
// @errors: 2353
|
||||
export default defineNuxtConfig({
|
||||
$production: {
|
||||
routeRules: {
|
||||
@ -41,10 +42,17 @@ export default defineNuxtConfig({
|
||||
},
|
||||
$development: {
|
||||
//
|
||||
}
|
||||
},
|
||||
$myCustomName: {
|
||||
//
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
To select an environment when running a Nuxt CLI command, simply pass the name to the `--envName` flag, like so: `nuxi build --envName myCustomName`.
|
||||
|
||||
To learn more about the mechanism behind these overrides, please refer to the `c12` documentation on [environment-specific configuration](https://github.com/unjs/c12?tab=readme-ov-file#environment-specific-configuration).
|
||||
|
||||
::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=DFZI2iVCrNc" target="_blank"}
|
||||
Watch a video from Alexander Lichter about the env-aware `nuxt.config.ts`.
|
||||
::
|
||||
|
@ -88,6 +88,37 @@ It is recommended to use `$fetch` for client-side interactions (event based) or
|
||||
Read more about `$fetch`.
|
||||
::
|
||||
|
||||
### Pass Client Headers to the API
|
||||
|
||||
During server-side-rendering, since the `$fetch` request takes place 'internally' within the server, it won't include the user's browser cookies.
|
||||
|
||||
We can use [`useRequestHeaders`](/docs/api/composables/use-request-headers) to access and proxy cookies to the API from server-side.
|
||||
|
||||
The example below adds the request headers to an isomorphic `$fetch` call to ensure that the API endpoint has access to the same `cookie` header originally sent by the user.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const headers = useRequestHeaders(['cookie'])
|
||||
|
||||
async function getCurrentUser() {
|
||||
return await $fetch('/api/me', { headers: headers.value })
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
::caution
|
||||
Be very careful before proxying headers to an external API and just include headers that you need. Not all headers are safe to be bypassed and might introduce unwanted behavior. Here is a list of common headers that are NOT to be proxied:
|
||||
|
||||
- `host`, `accept`
|
||||
- `content-length`, `content-md5`, `content-type`
|
||||
- `x-forwarded-host`, `x-forwarded-port`, `x-forwarded-proto`
|
||||
- `cf-connecting-ip`, `cf-ray`
|
||||
::
|
||||
|
||||
::tip
|
||||
You can also use [`useRequestFetch`](/docs/api/composables/use-request-fetch) to proxy headers to the call automatically.
|
||||
::
|
||||
|
||||
## `useFetch`
|
||||
|
||||
The [`useFetch`](/docs/api/composables/use-fetch) composable uses `$fetch` under-the-hood to make SSR-safe network calls in the setup function.
|
||||
@ -117,8 +148,8 @@ Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way!
|
||||
The `useAsyncData` composable is responsible for wrapping async logic and returning the result once it is resolved.
|
||||
|
||||
::tip
|
||||
`useFetch(url)` is nearly equivalent to `useAsyncData(url, () => $fetch(url))`. :br
|
||||
It's developer experience sugar for the most common use case.
|
||||
`useFetch(url)` is nearly equivalent to `useAsyncData(url, () => event.$fetch(url))`. :br
|
||||
It's developer experience sugar for the most common use case. (You can find out more about `event.fetch` at [`useRequestFetch`](/docs/api/composables/use-request-fetch).)
|
||||
::
|
||||
|
||||
::tip{icon="i-ph-video" to="https://www.youtube.com/watch?v=0X-aOpSGabA" target="_blank"}
|
||||
@ -458,32 +489,13 @@ For finer control, the `status` variable can be:
|
||||
- `error` when the fetch fails
|
||||
- `success` when the fetch is completed successfully
|
||||
|
||||
## Passing Headers and cookies
|
||||
## Passing Headers and Cookies
|
||||
|
||||
When we call `$fetch` in the browser, user headers like `cookie` will be directly sent to the API. But during server-side-rendering, since the `$fetch` request takes place 'internally' within the server, it doesn't include the user's browser cookies, nor does it pass on cookies from the fetch response.
|
||||
When we call `$fetch` in the browser, user headers like `cookie` will be directly sent to the API.
|
||||
|
||||
### Pass Client Headers to the API
|
||||
Normally, during server-side-rendering, since the `$fetch` request takes place 'internally' within the server, it wouldn't include the user's browser cookies, nor pass on cookies from the fetch response.
|
||||
|
||||
We can use [`useRequestHeaders`](/docs/api/composables/use-request-headers) to access and proxy cookies to the API from server-side.
|
||||
|
||||
The example below adds the request headers to an isomorphic `$fetch` call to ensure that the API endpoint has access to the same `cookie` header originally sent by the user.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const headers = useRequestHeaders(['cookie'])
|
||||
|
||||
const { data } = await useFetch('/api/me', { headers })
|
||||
</script>
|
||||
```
|
||||
|
||||
::caution
|
||||
Be very careful before proxying headers to an external API and just include headers that you need. Not all headers are safe to be bypassed and might introduce unwanted behavior. Here is a list of common headers that are NOT to be proxied:
|
||||
|
||||
- `host`, `accept`
|
||||
- `content-length`, `content-md5`, `content-type`
|
||||
- `x-forwarded-host`, `x-forwarded-port`, `x-forwarded-proto`
|
||||
- `cf-connecting-ip`, `cf-ray`
|
||||
::
|
||||
However, when calling `useFetch` on the server, Nuxt will use [`useRequestFetch`](/docs/api/composables/use-request-fetch) to proxy headers and cookies (with the exception of headers not meant to be forwarded, like `host`).
|
||||
|
||||
### Pass Cookies From Server-side API Calls on SSR Response
|
||||
|
||||
|
@ -48,7 +48,7 @@ const double = computed(() => count.value * 2)
|
||||
</script>
|
||||
```
|
||||
|
||||
### Vue and Nuxt composables
|
||||
### Vue and Nuxt Composables
|
||||
|
||||
<!-- TODO: move to separate page with https://github.com/nuxt/nuxt/issues/14723 and add more information -->
|
||||
|
||||
@ -159,7 +159,7 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
## Auto-import from third-party packages
|
||||
## Auto-import from Third-Party Packages
|
||||
|
||||
Nuxt also allows auto-importing from third-party packages.
|
||||
|
||||
|
@ -323,6 +323,10 @@ You may define a name for this page's route.
|
||||
|
||||
You may define a path matcher, if you have a more complex pattern than can be expressed with the file name. See [the `vue-router` docs](https://router.vuejs.org/guide/essentials/route-matching-syntax.html#custom-regex-in-params) for more information.
|
||||
|
||||
#### `props`
|
||||
|
||||
Allows accessing the route `params` as props passed to the page component. See[the `vue-router` docs](https://router.vuejs.org/guide/essentials/passing-props) for more information.
|
||||
|
||||
### Typing Custom Metadata
|
||||
|
||||
If you add custom metadata for your pages, you may wish to do so in a type-safe way. It is possible to augment the type of the object accepted by `definePageMeta`:
|
||||
|
@ -158,7 +158,7 @@ export default defineEventHandler((event) => {
|
||||
})
|
||||
```
|
||||
|
||||
::tip
|
||||
::tip{to="https://h3.unjs.io/examples/validate-data#validate-params"}
|
||||
Alternatively, use `getValidatedRouterParams` with a schema validator such as Zod for runtime and type safety.
|
||||
::
|
||||
|
||||
|
@ -19,6 +19,10 @@ export default defineAppConfig({
|
||||
Do not put any secret values inside `app.config` file. It is exposed to the user client bundle.
|
||||
::
|
||||
|
||||
::note
|
||||
When configuring a custom [`srcDir`](/docs/api/nuxt-config#srcdir), make sure to place the `app.config` file at the root of the new `srcDir` path.
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
To expose config and environment variables to the rest of your app, you will need to define configuration in `app.config` file.
|
||||
@ -31,7 +35,7 @@ export default defineAppConfig({
|
||||
})
|
||||
```
|
||||
|
||||
When adding `theme` to the `app.config`, Nuxt uses Vite or webpack to bundle the code. We can universally access `theme` both when server-rendering the page and in the browser using [`useAppConfig`](/docs/api/composables/use-app-config) composable.
|
||||
We can now universally access `theme` both when server-rendering the page and in the browser using [`useAppConfig`](/docs/api/composables/use-app-config) composable.
|
||||
|
||||
```vue [pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
@ -41,7 +45,23 @@ console.log(appConfig.theme)
|
||||
</script>
|
||||
```
|
||||
|
||||
When configuring a custom [`srcDir`](/docs/api/nuxt-config#srcdir), make sure to place the `app.config` file at the root of the new `srcDir` path.
|
||||
The [`updateAppConfig`](/docs/api/utils/update-app-config) utility can be used to update the `app.config` at runtime.
|
||||
|
||||
```vue [pages/index.vue]
|
||||
<script setup>
|
||||
const appConfig = useAppConfig() // { foo: 'bar' }
|
||||
|
||||
const newAppConfig = { foo: 'baz' }
|
||||
|
||||
updateAppConfig(newAppConfig)
|
||||
|
||||
console.log(appConfig) // { foo: 'baz' }
|
||||
</script>
|
||||
```
|
||||
|
||||
::read-more{to="/docs/api/utils/update-app-config"}
|
||||
Read more about the `updateAppConfig` utility.
|
||||
::
|
||||
|
||||
## Typing App Config
|
||||
|
||||
|
@ -59,14 +59,16 @@ This feature will likely be removed in a near future.
|
||||
|
||||
## emitRouteChunkError
|
||||
|
||||
Emits `app:chunkError` hook when there is an error loading vite/webpack chunks. Default behavior is to perform a hard reload of the new route when a chunk fails to load.
|
||||
Emits `app:chunkError` hook when there is an error loading vite/webpack chunks. Default behavior is to perform a reload of the new route on navigation to a new route when a chunk fails to load.
|
||||
|
||||
If you set this to `'automatic-immediate'` Nuxt will reload the current route immediatly, instead of waiting for a navigation. This is useful for chunk errors that are not triggered by navigation, e.g., when your Nuxt app fails to load a [lazy component](/docs/guide/directory-structure/components#dynamic-imports). A potential downside of this behavior is undesired reloads, e.g., when your app does not need the chunk that caused the error.
|
||||
|
||||
You can disable automatic handling by setting this to `false`, or handle chunk errors manually by setting it to `manual`.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
experimental: {
|
||||
emitRouteChunkError: 'automatic' // or 'manual' or false
|
||||
emitRouteChunkError: 'automatic' // or 'automatic-immediate', 'manual' or false
|
||||
}
|
||||
})
|
||||
```
|
||||
@ -334,6 +336,8 @@ This option allows exposing some route metadata defined in `definePageMeta` at b
|
||||
|
||||
This only works with static or strings/arrays rather than variables or conditional assignment. See [original issue](https://github.com/nuxt/nuxt/issues/24770) for more information and context.
|
||||
|
||||
It is also possible to scan page metadata only after all routes have been registered in `pages:extend`. Then another hook, `pages:resolved` will be called. To enable this behavior, set `scanPageMeta: 'after-resolve'`.
|
||||
|
||||
You can disable this feature if it causes issues in your project.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
|
@ -61,6 +61,7 @@ export default defineNuxtConfig({
|
||||
app: 'app'
|
||||
},
|
||||
experimental: {
|
||||
scanPageMeta: 'after-resolve',
|
||||
sharedPrerenderData: false,
|
||||
compileTemplate: true,
|
||||
resetAsyncDataToUndefined: true,
|
||||
|
@ -77,7 +77,7 @@ export function useAPI<T>(
|
||||
) {
|
||||
return useFetch(url, {
|
||||
...options,
|
||||
$fetch: useNuxtApp().$api
|
||||
$fetch: useNuxtApp().$api as typeof $fetch
|
||||
})
|
||||
}
|
||||
```
|
||||
|
43
docs/3.api/2.composables/use-runtime-hook.md
Normal file
43
docs/3.api/2.composables/use-runtime-hook.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: useRuntimeHook
|
||||
description: Registers a runtime hook in a Nuxt application and ensures it is properly disposed of when the scope is destroyed.
|
||||
links:
|
||||
- label: Source
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/runtime-hook.ts
|
||||
size: xs
|
||||
---
|
||||
|
||||
::important
|
||||
This composable is available in Nuxt v3.14+.
|
||||
::
|
||||
|
||||
```ts [signature]
|
||||
function useRuntimeHook<THookName extends keyof RuntimeNuxtHooks>(
|
||||
name: THookName,
|
||||
fn: RuntimeNuxtHooks[THookName] extends HookCallback ? RuntimeNuxtHooks[THookName] : never
|
||||
): void
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Parameters
|
||||
|
||||
- `name`: The name of the runtime hook to register. You can see the full list of [runtime Nuxt hooks here](/docs/api/advanced/hooks#app-hooks-runtime).
|
||||
- `fn`: The callback function to execute when the hook is triggered. The function signature varies based on the hook name.
|
||||
|
||||
### Returns
|
||||
|
||||
The composable doesn't return a value, but it automatically unregisters the hook when the component's scope is destroyed.
|
||||
|
||||
## Example
|
||||
|
||||
```vue twoslash [pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
// Register a hook that runs every time a link is prefetched, but which will be
|
||||
// automatically cleaned up (and not called again) when the component is unmounted
|
||||
useRuntimeHook('link:prefetch', (link) => {
|
||||
console.log('Prefetching', link)
|
||||
})
|
||||
</script>
|
||||
```
|
@ -30,6 +30,7 @@ interface PageMeta {
|
||||
redirect?: RouteRecordRedirectOption
|
||||
name?: string
|
||||
path?: string
|
||||
props?: RouteRecordRaw['props']
|
||||
alias?: string | string[]
|
||||
pageTransition?: boolean | TransitionProps
|
||||
layoutTransition?: boolean | TransitionProps
|
||||
@ -63,6 +64,12 @@ interface PageMeta {
|
||||
|
||||
You may define a [custom regular expression](#using-a-custom-regular-expression) if you have a more complex pattern than can be expressed with the file name.
|
||||
|
||||
**`props`**
|
||||
|
||||
- **Type**: [`RouteRecordRaw['props']`](https://router.vuejs.org/guide/essentials/passing-props)
|
||||
|
||||
Allows accessing the route `params` as props passed to the page component.
|
||||
|
||||
**`alias`**
|
||||
|
||||
- **Type**: `string | string[]`
|
||||
|
@ -125,6 +125,19 @@ Make sure to always use `await` or `return` on result of `navigateTo` when calli
|
||||
|
||||
`to` can be a plain string or a route object to redirect to. When passed as `undefined` or `null`, it will default to `'/'`.
|
||||
|
||||
#### Example
|
||||
|
||||
```ts
|
||||
// Passing the URL directly will redirect to the '/blog' page
|
||||
await navigateTo('/blog')
|
||||
|
||||
// Using the route object, will redirect to the route with the name 'blog'
|
||||
await navigateTo({ name: 'blog' })
|
||||
|
||||
// Redirects to the 'product' route while passing a parameter (id = 1) using the route object.
|
||||
await navigateTo({ name: 'product', params: { id: 1 } })
|
||||
```
|
||||
|
||||
### `options` (optional)
|
||||
|
||||
**Type**: `NavigateToOptions`
|
||||
|
@ -52,7 +52,8 @@ Hook | Arguments | Description
|
||||
`build:manifest` | `manifest` | Called during the manifest build by Vite and webpack. This allows customizing the manifest that Nitro will use to render `<script>` and `<link>` tags in the final HTML.
|
||||
`builder:generateApp` | `options` | Called before generating the app.
|
||||
`builder:watch` | `event, path` | Called at build time in development when the watcher spots a change to a file or directory in the project.
|
||||
`pages:extend` | `pages` | Called after pages routes are resolved.
|
||||
`pages:extend` | `pages` | Called after page routes are scanned from the file system.
|
||||
`pages:resolved` | `pages` | Called after page routes have been augmented with scanned metadata.
|
||||
`pages:routerOptions` | `{ files: Array<{ path: string, optional?: boolean }> }` | Called when resolving `router.options` files. Later items in the array override earlier ones.
|
||||
`server:devHandler` | `handler` | Called when the dev middleware is being registered on the Nitro dev server.
|
||||
`imports:sources` | `presets` | Called at setup allowing modules to extend sources.
|
||||
|
@ -3,8 +3,6 @@
|
||||
import { addPluginTemplate, addRouteMiddleware } from 'nuxt/kit'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
typescript: { shim: process.env.DOCS_TYPECHECK === 'true' },
|
||||
pages: process.env.DOCS_TYPECHECK === 'true',
|
||||
modules: [
|
||||
function () {
|
||||
if (!process.env.DOCS_TYPECHECK) { return }
|
||||
@ -18,4 +16,6 @@ export default defineNuxtConfig({
|
||||
})
|
||||
},
|
||||
],
|
||||
pages: process.env.DOCS_TYPECHECK === 'true',
|
||||
typescript: { shim: process.env.DOCS_TYPECHECK === 'true' },
|
||||
})
|
||||
|
66
package.json
66
package.json
@ -40,41 +40,47 @@
|
||||
"@nuxt/ui-templates": "workspace:*",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@types/node": "20.16.11",
|
||||
"@vue/compiler-core": "3.5.11",
|
||||
"@vue/compiler-dom": "3.5.11",
|
||||
"@vue/shared": "3.5.11",
|
||||
"@types/node": "22.9.0",
|
||||
"@unhead/dom": "1.11.11",
|
||||
"@unhead/shared": "1.11.11",
|
||||
"@unhead/vue": "1.11.11",
|
||||
"@unhead/schema": "1.11.11",
|
||||
"@unhead/ssr": "1.11.11",
|
||||
"@vue/compiler-core": "3.5.12",
|
||||
"@vue/compiler-dom": "3.5.12",
|
||||
"@vue/shared": "3.5.12",
|
||||
"c12": "2.0.1",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"jiti": "2.3.3",
|
||||
"jiti": "2.4.0",
|
||||
"magic-string": "^0.30.12",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"nuxt": "workspace:*",
|
||||
"ohash": "1.1.4",
|
||||
"postcss": "8.4.47",
|
||||
"rollup": "4.24.0",
|
||||
"rollup": "4.24.4",
|
||||
"send": ">=1.1.0",
|
||||
"typescript": "5.6.3",
|
||||
"ufo": "1.5.4",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vite": "5.4.8",
|
||||
"vue": "3.5.11"
|
||||
"unhead": "1.11.11",
|
||||
"vite": "5.4.10",
|
||||
"vue": "3.5.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.12.0",
|
||||
"@nuxt/eslint-config": "0.5.7",
|
||||
"@eslint/js": "9.14.0",
|
||||
"@nuxt/eslint-config": "0.6.1",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/rspack-builder": "workspace:*",
|
||||
"@nuxt/test-utils": "3.14.3",
|
||||
"@nuxt/test-utils": "3.14.4",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/eslint__js": "8.42.3",
|
||||
"@types/node": "20.16.11",
|
||||
"@types/node": "22.9.0",
|
||||
"@types/semver": "7.5.8",
|
||||
"@unhead/schema": "1.11.7",
|
||||
"@unhead/vue": "1.11.7",
|
||||
"@unhead/schema": "1.11.11",
|
||||
"@unhead/vue": "1.11.11",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vitest/coverage-v8": "2.1.2",
|
||||
"@vitest/coverage-v8": "2.1.4",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"autoprefixer": "10.4.20",
|
||||
"case-police": "0.7.0",
|
||||
@ -83,36 +89,36 @@
|
||||
"cssnano": "7.0.6",
|
||||
"destr": "2.0.3",
|
||||
"devalue": "5.1.1",
|
||||
"eslint": "9.12.0",
|
||||
"eslint": "9.14.0",
|
||||
"eslint-plugin-no-only-tests": "3.3.0",
|
||||
"eslint-plugin-perfectionist": "3.8.0",
|
||||
"eslint-plugin-perfectionist": "3.9.1",
|
||||
"eslint-typegen": "0.3.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"happy-dom": "15.7.4",
|
||||
"jiti": "2.3.3",
|
||||
"happy-dom": "15.9.0",
|
||||
"jiti": "2.4.0",
|
||||
"markdownlint-cli": "0.42.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nuxi": "3.14.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"nuxi": "3.15.0",
|
||||
"nuxt": "workspace:*",
|
||||
"nuxt-content-twoslash": "0.1.1",
|
||||
"ofetch": "1.4.1",
|
||||
"pathe": "1.1.2",
|
||||
"playwright-core": "1.48.0",
|
||||
"playwright-core": "1.48.2",
|
||||
"rimraf": "6.0.1",
|
||||
"semver": "7.6.3",
|
||||
"sherif": "1.0.0",
|
||||
"sherif": "1.0.1",
|
||||
"std-env": "3.7.0",
|
||||
"tinyexec": "0.3.0",
|
||||
"tinyglobby": "0.2.9",
|
||||
"tinyexec": "0.3.1",
|
||||
"tinyglobby": "0.2.10",
|
||||
"typescript": "5.6.3",
|
||||
"ufo": "1.5.4",
|
||||
"vitest": "2.1.2",
|
||||
"vitest": "2.1.4",
|
||||
"vitest-environment-nuxt": "1.0.1",
|
||||
"vue": "3.5.11",
|
||||
"vue": "3.5.12",
|
||||
"vue-router": "4.4.5",
|
||||
"vue-tsc": "2.1.6"
|
||||
"vue-tsc": "2.1.10"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.1",
|
||||
"packageManager": "pnpm@9.12.3",
|
||||
"engines": {
|
||||
"node": "^16.10.0 || >=18.0.0"
|
||||
},
|
||||
|
@ -35,7 +35,7 @@
|
||||
"globby": "^14.0.2",
|
||||
"hash-sum": "^2.0.0",
|
||||
"ignore": "^6.0.2",
|
||||
"jiti": "^2.3.3",
|
||||
"jiti": "^2.4.0",
|
||||
"klona": "^2.0.6",
|
||||
"mlly": "^1.7.2",
|
||||
"pathe": "^1.1.2",
|
||||
@ -48,14 +48,14 @@
|
||||
"untyped": "^1.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rspack/core": "1.0.10",
|
||||
"@rspack/core": "1.0.14",
|
||||
"@types/hash-sum": "1.0.2",
|
||||
"@types/semver": "7.5.8",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vite": "5.4.8",
|
||||
"vitest": "2.1.2",
|
||||
"webpack": "5.95.0"
|
||||
"vite": "5.4.10",
|
||||
"vitest": "2.1.4",
|
||||
"webpack": "5.96.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
|
@ -3,8 +3,9 @@ import { readPackageJSON } from 'pkg-types'
|
||||
import type { Nuxt, NuxtCompatibility, NuxtCompatibilityIssues } from '@nuxt/schema'
|
||||
import { useNuxt } from './context'
|
||||
|
||||
const SEMANTIC_VERSION_RE = /-\d+\.[0-9a-f]+/
|
||||
export function normalizeSemanticVersion (version: string) {
|
||||
return version.replace(/-\d+\.[0-9a-f]+/, '') // Remove edge prefix
|
||||
return version.replace(SEMANTIC_VERSION_RE, '') // Remove edge prefix
|
||||
}
|
||||
|
||||
const builderMap = {
|
||||
@ -104,6 +105,7 @@ export function isNuxt3 (nuxt: Nuxt = useNuxt()) {
|
||||
return isNuxtMajorVersion(3, nuxt)
|
||||
}
|
||||
|
||||
const NUXT_VERSION_RE = /^v/g
|
||||
/**
|
||||
* Get nuxt version
|
||||
*/
|
||||
@ -112,5 +114,5 @@ export function getNuxtVersion (nuxt: Nuxt | any = useNuxt() /* TODO: LegacyNuxt
|
||||
if (typeof rawVersion !== 'string') {
|
||||
throw new TypeError('Cannot determine nuxt version! Is current instance passed?')
|
||||
}
|
||||
return rawVersion.replace(/^v/g, '')
|
||||
return rawVersion.replace(NUXT_VERSION_RE, '')
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import type { Component, ComponentsDir } from '@nuxt/schema'
|
||||
import { useNuxt } from './context'
|
||||
import { assertNuxtCompatibility } from './compatibility'
|
||||
import { logger } from './logger'
|
||||
import { MODE_RE } from './utils'
|
||||
|
||||
/**
|
||||
* Register a directory to be scanned for components and imported only when used.
|
||||
@ -28,7 +29,7 @@ export async function addComponent (opts: AddComponentOptions) {
|
||||
nuxt.options.components = nuxt.options.components || []
|
||||
|
||||
if (!opts.mode) {
|
||||
const [, mode = 'all'] = opts.filePath.match(/\.(server|client)(\.\w+)*$/) || []
|
||||
const [, mode = 'all'] = opts.filePath.match(MODE_RE) || []
|
||||
opts.mode = mode as 'all' | 'client' | 'server'
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,11 @@ import { useNuxt } from './context'
|
||||
import { logger } from './logger'
|
||||
import { addTemplate } from './template'
|
||||
|
||||
const LAYOUT_RE = /["']/g
|
||||
export function addLayout (template: NuxtTemplate | string, name?: string) {
|
||||
const nuxt = useNuxt()
|
||||
const { filename, src } = addTemplate(template)
|
||||
const layoutName = kebabCase(name || parse(filename).name).replace(/["']/g, '')
|
||||
const layoutName = kebabCase(name || parse(filename).name).replace(LAYOUT_RE, '')
|
||||
|
||||
// Nuxt 3 adds layouts on app
|
||||
nuxt.hook('app:templates', (app) => {
|
||||
|
@ -58,8 +58,8 @@ export async function getNuxtModuleVersion (module: string | NuxtModule, nuxt: N
|
||||
}
|
||||
// it's possible that the module will be installed, it just hasn't been done yet, preemptively load the instance
|
||||
if (hasNuxtModule(moduleMeta.name)) {
|
||||
const { buildTimeModuleMeta } = await loadNuxtModuleInstance(moduleMeta.name, nuxt)
|
||||
return buildTimeModuleMeta.version || false
|
||||
const { nuxtModule, buildTimeModuleMeta } = await loadNuxtModuleInstance(moduleMeta.name, nuxt)
|
||||
return buildTimeModuleMeta.version || await nuxtModule.getMeta?.().then(r => r.version) || false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { existsSync, promises as fsp, lstatSync } from 'node:fs'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import type { ModuleMeta, Nuxt, NuxtConfig, NuxtModule } from '@nuxt/schema'
|
||||
import { dirname, isAbsolute, join, resolve } from 'pathe'
|
||||
import { defu } from 'defu'
|
||||
import { createJiti } from 'jiti'
|
||||
import { resolve as resolveModule } from 'mlly'
|
||||
import { useNuxt } from '../context'
|
||||
import { resolveAlias } from '../resolve'
|
||||
import { resolveAlias, resolvePath } from '../resolve'
|
||||
import { logger } from '../logger'
|
||||
|
||||
const NODE_MODULES_RE = /[/\\]node_modules[/\\]/
|
||||
@ -77,11 +79,14 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
|
||||
// Import if input is string
|
||||
if (typeof nuxtModule === 'string') {
|
||||
const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule, join(nuxt.options.rootDir, nuxtModule)]
|
||||
|
||||
for (const parentURL of nuxt.options.modulesDir) {
|
||||
for (const path of paths) {
|
||||
for (const path of paths) {
|
||||
for (const parentURL of nuxt.options.modulesDir) {
|
||||
try {
|
||||
const src = jiti.esmResolve(path, { parentURL: parentURL.replace(/\/node_modules\/?$/, '') })
|
||||
const resolved = resolveAlias(path, nuxt.options.alias)
|
||||
const src = isAbsolute(resolved)
|
||||
? await resolvePath(resolved, { cwd: parentURL, fallbackToOriginal: false, extensions: nuxt.options.extensions })
|
||||
: await resolveModule(resolved, { url: pathToFileURL(parentURL.replace(/\/node_modules\/?$/, '')), extensions: nuxt.options.extensions })
|
||||
|
||||
nuxtModule = await jiti.import(src, { default: true }) as NuxtModule
|
||||
|
||||
// nuxt-module-builder generates a module.json with metadata including the version
|
||||
@ -92,7 +97,7 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
|
||||
break
|
||||
} catch (error: unknown) {
|
||||
const code = (error as Error & { code?: string }).code
|
||||
if (code === 'MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
|
||||
if (code === 'MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_UNSUPPORTED_DIR_IMPORT' || code === 'ENOTDIR') {
|
||||
continue
|
||||
}
|
||||
logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`)
|
||||
|
@ -4,13 +4,14 @@ import { normalize } from 'pathe'
|
||||
import { useNuxt } from './context'
|
||||
import { toArray } from './utils'
|
||||
|
||||
const HANDLER_METHOD_RE = /\.(get|head|patch|post|put|delete|connect|options|trace)(\.\w+)*$/
|
||||
/**
|
||||
* normalize handler object
|
||||
*
|
||||
*/
|
||||
function normalizeHandlerMethod (handler: NitroEventHandler) {
|
||||
// retrieve method from handler file name
|
||||
const [, method = undefined] = handler.handler.match(/\.(get|head|patch|post|put|delete|connect|options|trace)(\.\w+)*$/) || []
|
||||
const [, method = undefined] = handler.handler.match(HANDLER_METHOD_RE) || []
|
||||
return {
|
||||
method: method as 'get' | 'head' | 'patch' | 'post' | 'put' | 'delete' | 'connect' | 'options' | 'trace' | undefined,
|
||||
...handler,
|
||||
|
@ -3,6 +3,7 @@ import type { NuxtPlugin, NuxtPluginTemplate } from '@nuxt/schema'
|
||||
import { useNuxt } from './context'
|
||||
import { addTemplate } from './template'
|
||||
import { resolveAlias } from './resolve'
|
||||
import { MODE_RE } from './utils'
|
||||
|
||||
/**
|
||||
* Normalize a nuxt plugin object
|
||||
@ -27,7 +28,7 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
|
||||
plugin.mode = 'server'
|
||||
}
|
||||
if (!plugin.mode) {
|
||||
const [, mode = 'all'] = plugin.src.match(/\.(server|client)(\.\w+)*$/) || []
|
||||
const [, mode = 'all'] = plugin.src.match(MODE_RE) || []
|
||||
plugin.mode = mode as 'all' | 'client' | 'server'
|
||||
}
|
||||
|
||||
|
@ -123,6 +123,9 @@ export async function updateTemplates (options?: { filter?: (template: ResolvedN
|
||||
return await tryUseNuxt()?.hooks.callHook('builder:generateApp', options)
|
||||
}
|
||||
|
||||
const EXTENSION_RE = /\b\.\w+$/g
|
||||
// Exclude bridge alias types to support Volar
|
||||
const excludedAlias = [/^@vue\/.*$/, /^#internal\/nuxt/]
|
||||
export async function _generateTypes (nuxt: Nuxt) {
|
||||
const rootDirWithSlash = withTrailingSlash(nuxt.options.rootDir)
|
||||
const relativeRootDir = relativeWithDot(nuxt.options.buildDir, nuxt.options.rootDir)
|
||||
@ -225,9 +228,6 @@ export async function _generateTypes (nuxt: Nuxt) {
|
||||
|
||||
const aliases: Record<string, string> = nuxt.options.alias
|
||||
|
||||
// Exclude bridge alias types to support Volar
|
||||
const excludedAlias = [/^@vue\/.*$/, /^#internal\/nuxt/]
|
||||
|
||||
const basePath = tsConfig.compilerOptions!.baseUrl
|
||||
? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl)
|
||||
: nuxt.options.buildDir
|
||||
@ -260,7 +260,7 @@ export async function _generateTypes (nuxt: Nuxt) {
|
||||
} else {
|
||||
const path = stats?.isFile()
|
||||
// remove extension
|
||||
? relativePath.replace(/\b\.\w+$/g, '')
|
||||
? relativePath.replace(EXTENSION_RE, '')
|
||||
// non-existent file probably shouldn't be resolved
|
||||
: aliases[alias]!
|
||||
|
||||
@ -289,7 +289,7 @@ export async function _generateTypes (nuxt: Nuxt) {
|
||||
tsConfig.compilerOptions!.paths[alias] = await Promise.all(paths.map(async (path: string) => {
|
||||
if (!isAbsolute(path)) { return path }
|
||||
const stats = await fsp.stat(path).catch(() => null /* file does not exist */)
|
||||
return relativeWithDot(nuxt.options.buildDir, stats?.isFile() ? path.replace(/\b\.\w+$/g, '') /* remove extension */ : path)
|
||||
return relativeWithDot(nuxt.options.buildDir, stats?.isFile() ? path.replace(EXTENSION_RE, '') /* remove extension */ : path)
|
||||
}))
|
||||
}
|
||||
|
||||
@ -344,6 +344,7 @@ function renderAttr (key: string, value?: string) {
|
||||
return value ? `${key}="${value}"` : ''
|
||||
}
|
||||
|
||||
const RELATIVE_WITH_DOT_RE = /^([^.])/
|
||||
function relativeWithDot (from: string, to: string) {
|
||||
return relative(from, to).replace(/^([^.])/, './$1') || '.'
|
||||
return relative(from, to).replace(RELATIVE_WITH_DOT_RE, './$1') || '.'
|
||||
}
|
||||
|
@ -2,3 +2,5 @@
|
||||
export function toArray<T> (value: T | T[]): T[] {
|
||||
return Array.isArray(value) ? value : [value]
|
||||
}
|
||||
|
||||
export const MODE_RE = /\.(server|client)(\.\w+)*$/
|
||||
|
@ -60,17 +60,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/devalue": "^2.0.2",
|
||||
"@nuxt/devtools": "^1.5.2",
|
||||
"@nuxt/devtools": "^1.6.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/telemetry": "^2.6.0",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@unhead/dom": "^1.11.7",
|
||||
"@unhead/shared": "^1.11.7",
|
||||
"@unhead/ssr": "^1.11.7",
|
||||
"@unhead/vue": "^1.11.7",
|
||||
"@vue/shared": "^3.5.11",
|
||||
"acorn": "8.12.1",
|
||||
"@unhead/dom": "^1.11.11",
|
||||
"@unhead/shared": "^1.11.11",
|
||||
"@unhead/ssr": "^1.11.11",
|
||||
"@unhead/vue": "^1.11.11",
|
||||
"@vue/shared": "^3.5.12",
|
||||
"acorn": "8.14.0",
|
||||
"c12": "^2.0.1",
|
||||
"chokidar": "^4.0.1",
|
||||
"compatx": "^0.1.8",
|
||||
@ -88,14 +88,14 @@
|
||||
"hookable": "^5.5.3",
|
||||
"ignore": "^6.0.2",
|
||||
"impound": "^0.2.0",
|
||||
"jiti": "^2.3.3",
|
||||
"jiti": "^2.4.0",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.1.0",
|
||||
"magic-string": "^0.30.12",
|
||||
"mlly": "^1.7.2",
|
||||
"nanotar": "^0.1.1",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nuxi": "^3.14.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"nuxi": "^3.15.0",
|
||||
"nypm": "^0.3.12",
|
||||
"ofetch": "^1.4.1",
|
||||
"ohash": "^1.1.4",
|
||||
@ -107,33 +107,33 @@
|
||||
"semver": "^7.6.3",
|
||||
"std-env": "^3.7.0",
|
||||
"strip-literal": "^2.1.0",
|
||||
"tinyglobby": "0.2.9",
|
||||
"tinyglobby": "0.2.10",
|
||||
"ufo": "^1.5.4",
|
||||
"ultrahtml": "^1.5.3",
|
||||
"uncrypto": "^0.1.3",
|
||||
"unctx": "^2.3.1",
|
||||
"unenv": "^1.10.0",
|
||||
"unhead": "^1.11.7",
|
||||
"unhead": "^1.11.11",
|
||||
"unimport": "^3.13.1",
|
||||
"unplugin": "^1.14.1",
|
||||
"unplugin": "^1.15.0",
|
||||
"unplugin-vue-router": "^0.10.8",
|
||||
"unstorage": "^1.12.0",
|
||||
"unstorage": "^1.13.1",
|
||||
"untyped": "^1.5.1",
|
||||
"vue": "^3.5.11",
|
||||
"vue": "^3.5.12",
|
||||
"vue-bundle-renderer": "^2.1.1",
|
||||
"vue-devtools-stub": "^0.1.0",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/scripts": "0.9.4",
|
||||
"@nuxt/scripts": "0.9.5",
|
||||
"@nuxt/ui-templates": "1.3.4",
|
||||
"@parcel/watcher": "2.4.1",
|
||||
"@parcel/watcher": "2.5.0",
|
||||
"@types/estree": "1.0.6",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vue/compiler-sfc": "3.5.11",
|
||||
"@vue/compiler-sfc": "3.5.12",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vite": "5.4.8",
|
||||
"vitest": "2.1.2"
|
||||
"vite": "5.4.10",
|
||||
"vitest": "2.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@parcel/watcher": "^2.1.0",
|
||||
|
@ -22,6 +22,7 @@ const SSR_UID_RE = /data-island-uid="([^"]*)"/
|
||||
const DATA_ISLAND_UID_RE = /data-island-uid(="")?(?!="[^"])/g
|
||||
const SLOTNAME_RE = /data-island-slot="([^"]*)"/g
|
||||
const SLOT_FALLBACK_RE = / data-island-slot="([^"]*)"[^>]*>/g
|
||||
const ISLAND_SCOPE_ID_RE = /^<[^> ]*/
|
||||
|
||||
let id = 1
|
||||
const getId = import.meta.client ? () => (id++).toString() : randomUUID
|
||||
@ -142,7 +143,7 @@ export default defineComponent({
|
||||
let html = ssrHTML.value
|
||||
|
||||
if (props.scopeId) {
|
||||
html = html.replace(/^<[^> ]*/, full => full + ' ' + props.scopeId)
|
||||
html = html.replace(ISLAND_SCOPE_ID_RE, full => full + ' ' + props.scopeId)
|
||||
}
|
||||
|
||||
if (import.meta.client && !canLoadClientComponent.value) {
|
||||
|
@ -521,11 +521,12 @@ function useObserver (): { observe: ObserveFn } | undefined {
|
||||
return _observer
|
||||
}
|
||||
|
||||
const IS_2G_RE = /2g/
|
||||
function isSlowConnection () {
|
||||
if (import.meta.server) { return }
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/connection
|
||||
const cn = (navigator as any).connection as { saveData: boolean, effectiveType: string } | null
|
||||
if (cn && (cn.saveData || /2g/.test(cn.effectiveType))) { return true }
|
||||
if (cn && (cn.saveData || IS_2G_RE.test(cn.effectiveType))) { return true }
|
||||
return false
|
||||
}
|
||||
|
@ -15,13 +15,16 @@ export const _wrapIf = (component: Component, props: any, slots: any) => {
|
||||
return { default: () => props ? h(component, props, slots) : slots.default?.() }
|
||||
}
|
||||
|
||||
const ROUTE_KEY_PARENTHESES_RE = /(:\w+)\([^)]+\)/g
|
||||
const ROUTE_KEY_SYMBOLS_RE = /(:\w+)[?+*]/g
|
||||
const ROUTE_KEY_NORMAL_RE = /:\w+/g
|
||||
// TODO: consider refactoring into single utility
|
||||
// See https://github.com/nuxt/nuxt/tree/main/packages/nuxt/src/pages/runtime/utils.ts#L8-L19
|
||||
function generateRouteKey (route: RouteLocationNormalized) {
|
||||
const source = route?.meta.key ?? route.path
|
||||
.replace(/(:\w+)\([^)]+\)/g, '$1')
|
||||
.replace(/(:\w+)[?+*]/g, '$1')
|
||||
.replace(/:\w+/g, r => route.params[r.slice(1)]?.toString() || '')
|
||||
.replace(ROUTE_KEY_PARENTHESES_RE, '$1')
|
||||
.replace(ROUTE_KEY_SYMBOLS_RE, '$1')
|
||||
.replace(ROUTE_KEY_NORMAL_RE, r => route.params[r.slice(1)]?.toString() || '')
|
||||
return typeof source === 'function' ? source(route) : source
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,6 @@ export const defineNuxtComponent: typeof defineComponent =
|
||||
}
|
||||
|
||||
if (options.head) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
useHead(typeof options.head === 'function' ? () => options.head(nuxtApp) : options.head)
|
||||
}
|
||||
|
||||
|
@ -38,3 +38,4 @@ export { useRequestURL } from './url'
|
||||
export { usePreviewMode } from './preview'
|
||||
export { useId } from './id'
|
||||
export { useRouteAnnouncer } from './route-announcer'
|
||||
export { useRuntimeHook } from './runtime-hook'
|
||||
|
@ -114,6 +114,7 @@ export interface NavigateToOptions {
|
||||
open?: OpenOptions
|
||||
}
|
||||
|
||||
const URL_QUOTE_RE = /"/g
|
||||
/** @since 3.0.0 */
|
||||
export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: NavigateToOptions): Promise<void | NavigationFailure | false> | false | void | RouteLocationRaw => {
|
||||
if (!to) {
|
||||
@ -166,7 +167,7 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
|
||||
const redirect = async function (response: any) {
|
||||
// TODO: consider deprecating in favour of `app:rendered` and removing
|
||||
await nuxtApp.callHook('app:redirected')
|
||||
const encodedLoc = location.replace(/"/g, '%22')
|
||||
const encodedLoc = location.replace(URL_QUOTE_RE, '%22')
|
||||
const encodedHeader = encodeURL(location, isExternalHost)
|
||||
|
||||
nuxtApp.ssrContext!._renderResponse = {
|
||||
|
21
packages/nuxt/src/app/composables/runtime-hook.ts
Normal file
21
packages/nuxt/src/app/composables/runtime-hook.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { onScopeDispose } from 'vue'
|
||||
import type { HookCallback } from 'hookable'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
import type { RuntimeNuxtHooks } from '../nuxt'
|
||||
|
||||
/**
|
||||
* Registers a runtime hook in a Nuxt application and ensures it is properly disposed of when the scope is destroyed.
|
||||
* @param name - The name of the hook to register.
|
||||
* @param fn - The callback function to be executed when the hook is triggered.
|
||||
* @since 3.14.0
|
||||
*/
|
||||
export function useRuntimeHook<THookName extends keyof RuntimeNuxtHooks> (
|
||||
name: THookName,
|
||||
fn: RuntimeNuxtHooks[THookName] extends HookCallback ? RuntimeNuxtHooks[THookName] : never,
|
||||
): void {
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
const unregister = nuxtApp.hook(name, fn)
|
||||
|
||||
onScopeDispose(unregister)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
export { applyPlugin, applyPlugins, callWithNuxt, createNuxtApp, defineAppConfig, defineNuxtPlugin, definePayloadPlugin, isNuxtPlugin, registerPluginHooks, tryUseNuxtApp, useNuxtApp, useRuntimeConfig } from './nuxt'
|
||||
export type { CreateOptions, NuxtApp, NuxtPayload, NuxtPluginIndicator, NuxtSSRContext, ObjectPlugin, Plugin, PluginEnvContext, PluginMeta, ResolvedPluginMeta, RuntimeNuxtHooks } from './nuxt'
|
||||
|
||||
export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index'
|
||||
export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, useResponseHeader, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta, useRuntimeHook } from './composables/index'
|
||||
export type { AddRouteMiddlewareOptions, AsyncData, AsyncDataOptions, AsyncDataRequestStatus, CookieOptions, CookieRef, FetchResult, NuxtAppManifest, NuxtAppManifestMeta, NuxtError, ReloadNuxtAppOptions, RouteMiddleware, UseFetchOptions } from './composables/index'
|
||||
|
||||
export { defineNuxtLink } from './components/index'
|
||||
|
@ -45,7 +45,7 @@ export interface RuntimeNuxtHooks {
|
||||
'app:chunkError': (options: { error: any }) => HookResult
|
||||
'app:data:refresh': (keys?: string[]) => HookResult
|
||||
'app:manifest:update': (meta?: NuxtAppManifestMeta) => HookResult
|
||||
'dev:ssr-logs': (logs: LogObject[]) => void | Promise<void>
|
||||
'dev:ssr-logs': (logs: LogObject[]) => HookResult
|
||||
'link:prefetch': (link: string) => HookResult
|
||||
'page:start': (Component?: VNode) => HookResult
|
||||
'page:finish': (Component?: VNode) => HookResult
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { defineNuxtPlugin } from '../nuxt'
|
||||
import { reloadNuxtApp } from '../composables/chunk'
|
||||
import { addRouteMiddleware } from '../composables/router'
|
||||
|
||||
const reloadNuxtApp_ = (path: string) => { reloadNuxtApp({ persistState: true, path }) }
|
||||
|
||||
// See https://github.com/nuxt/nuxt/issues/23612 for more context
|
||||
export default defineNuxtPlugin({
|
||||
name: 'nuxt:chunk-reload-immediate',
|
||||
setup (nuxtApp) {
|
||||
// Remember `to.path` when navigating to a new path: A `chunkError` may occur during navigation, we then want to then reload at `to.path`
|
||||
let currentlyNavigationTo: null | string = null
|
||||
addRouteMiddleware((to) => {
|
||||
currentlyNavigationTo = to.path
|
||||
})
|
||||
|
||||
// Reload when a `chunkError` is thrown
|
||||
nuxtApp.hook('app:chunkError', () => reloadNuxtApp_(currentlyNavigationTo ?? nuxtApp._route.path))
|
||||
|
||||
// Reload when the app manifest updates
|
||||
nuxtApp.hook('app:manifest:update', () => reloadNuxtApp_(nuxtApp._route.path))
|
||||
},
|
||||
})
|
@ -16,11 +16,13 @@ import { ComponentNamePlugin } from './plugins/component-names'
|
||||
|
||||
const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string'
|
||||
const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch { return false } }
|
||||
const SLASH_SEPARATOR_RE = /[\\/]/
|
||||
function compareDirByPathLength ({ path: pathA }: { path: string }, { path: pathB }: { path: string }) {
|
||||
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
|
||||
return pathB.split(SLASH_SEPARATOR_RE).filter(Boolean).length - pathA.split(SLASH_SEPARATOR_RE).filter(Boolean).length
|
||||
}
|
||||
|
||||
const DEFAULT_COMPONENTS_DIRS_RE = /\/components(?:\/(?:global|islands))?$/
|
||||
const STARTER_DOT_RE = /^\./g
|
||||
|
||||
export type getComponentsT = (mode?: 'client' | 'server' | 'all') => Component[]
|
||||
|
||||
@ -89,7 +91,7 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
const dirOptions: ComponentsDir = typeof dir === 'object' ? dir : { path: dir }
|
||||
const dirPath = resolveAlias(dirOptions.path)
|
||||
const transpile = typeof dirOptions.transpile === 'boolean' ? dirOptions.transpile : 'auto'
|
||||
const extensions = (dirOptions.extensions || nuxt.options.extensions).map(e => e.replace(/^\./g, ''))
|
||||
const extensions = (dirOptions.extensions || nuxt.options.extensions).map(e => e.replace(STARTER_DOT_RE, ''))
|
||||
|
||||
const present = isDirectory(dirPath)
|
||||
if (!present && !DEFAULT_COMPONENTS_DIRS_RE.test(dirOptions.path)) {
|
||||
|
@ -12,6 +12,7 @@ interface LoaderOptions {
|
||||
}
|
||||
const CLIENT_FALLBACK_RE = /<(?:NuxtClientFallback|nuxt-client-fallback)(?: [^>]*)?>/
|
||||
const CLIENT_FALLBACK_GLOBAL_RE = /<(NuxtClientFallback|nuxt-client-fallback)( [^>]*)?>/g
|
||||
const UID_RE = / :?uid=/
|
||||
export const ClientFallbackAutoIdPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
const exclude = options.transform?.exclude || []
|
||||
const include = options.transform?.include || []
|
||||
@ -37,7 +38,7 @@ export const ClientFallbackAutoIdPlugin = (options: LoaderOptions) => createUnpl
|
||||
|
||||
s.replace(CLIENT_FALLBACK_GLOBAL_RE, (full, name, attrs) => {
|
||||
count++
|
||||
if (/ :?uid=/.test(attrs)) { return full }
|
||||
if (UID_RE.test(attrs)) { return full }
|
||||
return `<${name} :uid="'${hash(relativeID)}' + JSON.stringify($props) + '${count}'" ${attrs ?? ''}>`
|
||||
})
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import MagicString from 'magic-string'
|
||||
import type { Component } from 'nuxt/schema'
|
||||
import { isVue } from '../../core/utils'
|
||||
import { SX_RE, isVue } from '../../core/utils'
|
||||
|
||||
interface NameDevPluginOptions {
|
||||
sourcemap: boolean
|
||||
getComponents: () => Component[]
|
||||
}
|
||||
const FILENAME_RE = /([^/\\]+)\.\w+$/
|
||||
/**
|
||||
* Set the default name of components to their PascalCase name
|
||||
*/
|
||||
@ -15,10 +16,10 @@ export const ComponentNamePlugin = (options: NameDevPluginOptions) => createUnpl
|
||||
name: 'nuxt:component-name-plugin',
|
||||
enforce: 'post',
|
||||
transformInclude (id) {
|
||||
return isVue(id) || !!id.match(/\.[tj]sx$/)
|
||||
return isVue(id) || !!id.match(SX_RE)
|
||||
},
|
||||
transform (code, id) {
|
||||
const filename = id.match(/([^/\\]+)\.\w+$/)?.[1]
|
||||
const filename = id.match(FILENAME_RE)?.[1]
|
||||
if (!filename) {
|
||||
return
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ const TEMPLATE_RE = /<template>([\s\S]*)<\/template>/
|
||||
const NUXTCLIENT_ATTR_RE = /\s:?nuxt-client(="[^"]*")?/g
|
||||
const IMPORT_CODE = '\nimport { mergeProps as __mergeProps } from \'vue\'' + '\nimport { vforToArray as __vforToArray } from \'#app/components/utils\'' + '\nimport NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'' + '\nimport NuxtTeleportSsrSlot from \'#app/components/nuxt-teleport-island-slot\''
|
||||
const EXTRACTED_ATTRS_RE = /v-(?:if|else-if|else)(="[^"]*")?/g
|
||||
const KEY_RE = /:?key="[^"]"/g
|
||||
|
||||
function wrapWithVForDiv (code: string, vfor: string): string {
|
||||
return `<div v-for="${vfor}" style="display: contents;">${code}</div>`
|
||||
@ -90,7 +91,7 @@ export const IslandsTransformPlugin = (options: ServerOnlyComponentTransformPlug
|
||||
if (children.length) {
|
||||
// pass slot fallback to NuxtTeleportSsrSlot fallback
|
||||
const attrString = attributeToString(attributes)
|
||||
const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(/:?key="[^"]"/g, '')
|
||||
const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(KEY_RE, '')
|
||||
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[1].end, `<slot${attrString.replaceAll(EXTRACTED_ATTRS_RE, '')}/><template #fallback>${attributes['v-for'] ? wrapWithVForDiv(slice, attributes['v-for']) : slice}</template>`)
|
||||
} else {
|
||||
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[0].end, code.slice(startingIndex + loc[0].start, startingIndex + loc[0].end).replaceAll(EXTRACTED_ATTRS_RE, ''))
|
||||
|
@ -6,7 +6,7 @@ import { relative } from 'pathe'
|
||||
import type { Component, ComponentsOptions } from 'nuxt/schema'
|
||||
|
||||
import { logger, tryUseNuxt } from '@nuxt/kit'
|
||||
import { isVue } from '../../core/utils'
|
||||
import { QUOTE_RE, SX_RE, isVue } from '../../core/utils'
|
||||
|
||||
interface LoaderOptions {
|
||||
getComponents (): Component[]
|
||||
@ -17,6 +17,7 @@ interface LoaderOptions {
|
||||
experimentalComponentIslands?: boolean
|
||||
}
|
||||
|
||||
const REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE = /(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy(?=[A-Z]))?([^'"]*)["'][^)]*\)/g
|
||||
export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
const exclude = options.transform?.exclude || []
|
||||
const include = options.transform?.include || []
|
||||
@ -32,7 +33,7 @@ export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
if (include.some(pattern => pattern.test(id))) {
|
||||
return true
|
||||
}
|
||||
return isVue(id, { type: ['template', 'script'] }) || !!id.match(/\.[tj]sx$/)
|
||||
return isVue(id, { type: ['template', 'script'] }) || !!id.match(SX_RE)
|
||||
},
|
||||
transform (code, id) {
|
||||
const components = options.getComponents()
|
||||
@ -43,7 +44,7 @@ export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
const s = new MagicString(code)
|
||||
|
||||
// replace `_resolveComponent("...")` to direct import
|
||||
s.replace(/(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy(?=[A-Z]))?([^'"]*)["'][^)]*\)/g, (full: string, lazy: string, name: string) => {
|
||||
s.replace(REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE, (full: string, lazy: string, name: string) => {
|
||||
const component = findComponent(components, name, options.mode)
|
||||
if (component) {
|
||||
// TODO: refactor to nuxi
|
||||
@ -111,7 +112,7 @@ export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
|
||||
})
|
||||
|
||||
function findComponent (components: Component[], name: string, mode: LoaderOptions['mode']) {
|
||||
const id = pascalCase(name).replace(/["']/g, '')
|
||||
const id = pascalCase(name).replace(QUOTE_RE, '')
|
||||
// Prefer exact match
|
||||
const component = components.find(component => id === component.pascalName && ['all', mode, undefined].includes(component.mode))
|
||||
if (component) { return component }
|
||||
|
@ -6,8 +6,12 @@ import { isIgnored, logger, useNuxt } from '@nuxt/kit'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import type { Component, ComponentsDir } from 'nuxt/schema'
|
||||
|
||||
import { resolveComponentNameSegments } from '../core/utils'
|
||||
import { QUOTE_RE, resolveComponentNameSegments } from '../core/utils'
|
||||
|
||||
const ISLAND_RE = /\.island(?:\.global)?$/
|
||||
const GLOBAL_RE = /\.global(?:\.island)?$/
|
||||
const COMPONENT_MODE_RE = /(?<=\.)(client|server)(\.global|\.island)*$/
|
||||
const MODE_REPLACEMENT_RE = /(\.(client|server))?(\.global|\.island)*$/
|
||||
/**
|
||||
* Scan the components inside different components folders
|
||||
* and return a unique list of components
|
||||
@ -83,17 +87,17 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
||||
*/
|
||||
let fileName = basename(filePath, extname(filePath))
|
||||
|
||||
const island = /\.island(?:\.global)?$/.test(fileName) || dir.island
|
||||
const global = /\.global(?:\.island)?$/.test(fileName) || dir.global
|
||||
const mode = island ? 'server' : (fileName.match(/(?<=\.)(client|server)(\.global|\.island)*$/)?.[1] || 'all') as 'client' | 'server' | 'all'
|
||||
fileName = fileName.replace(/(\.(client|server))?(\.global|\.island)*$/, '')
|
||||
const island = ISLAND_RE.test(fileName) || dir.island
|
||||
const global = GLOBAL_RE.test(fileName) || dir.global
|
||||
const mode = island ? 'server' : (fileName.match(COMPONENT_MODE_RE)?.[1] || 'all') as 'client' | 'server' | 'all'
|
||||
fileName = fileName.replace(MODE_REPLACEMENT_RE, '')
|
||||
|
||||
if (fileName.toLowerCase() === 'index') {
|
||||
fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */
|
||||
}
|
||||
|
||||
const suffix = (mode !== 'all' ? `-${mode}` : '')
|
||||
const componentNameSegments = resolveComponentNameSegments(fileName.replace(/["']/g, ''), prefixParts)
|
||||
const componentNameSegments = resolveComponentNameSegments(fileName.replace(QUOTE_RE, ''), prefixParts)
|
||||
const pascalName = pascalCase(componentNameSegments)
|
||||
|
||||
if (LAZY_COMPONENT_NAME_REGEX.test(pascalName)) {
|
||||
|
@ -102,14 +102,15 @@ export const componentsIslandsTemplate: NuxtTemplate = {
|
||||
},
|
||||
}
|
||||
|
||||
const NON_VUE_RE = /\b\.(?!vue)\w+$/g
|
||||
export const componentsTypeTemplate = {
|
||||
filename: 'components.d.ts' as const,
|
||||
getContents: ({ app, nuxt }) => {
|
||||
const buildDir = nuxt.options.buildDir
|
||||
const componentTypes = app.components.filter(c => !c.island).map((c) => {
|
||||
const type = `typeof ${genDynamicImport(isAbsolute(c.filePath)
|
||||
? relative(buildDir, c.filePath).replace(/\b\.(?!vue)\w+$/g, '')
|
||||
: c.filePath.replace(/\b\.(?!vue)\w+$/g, ''), { wrapper: false })}['${c.export}']`
|
||||
? relative(buildDir, c.filePath).replace(NON_VUE_RE, '')
|
||||
: c.filePath.replace(NON_VUE_RE, ''), { wrapper: false })}['${c.export}']`
|
||||
return [
|
||||
c.pascalName,
|
||||
c.island || c.mode === 'server' ? `IslandComponent<${type}>` : type,
|
||||
|
@ -57,7 +57,7 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
||||
|
||||
const writes: Array<() => void> = []
|
||||
const changedTemplates: Array<ResolvedNuxtTemplate<any>> = []
|
||||
|
||||
const FORWARD_SLASH_RE = /\//g
|
||||
async function processTemplate (template: ResolvedNuxtTemplate) {
|
||||
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!)
|
||||
const start = performance.now()
|
||||
@ -77,7 +77,7 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
||||
|
||||
// In case a non-normalized absolute path is called for on Windows
|
||||
if (process.platform === 'win32') {
|
||||
nuxt.vfs[fullPath.replace(/\//g, '\\')] = contents
|
||||
nuxt.vfs[fullPath.replace(FORWARD_SLASH_RE, '\\')] = contents
|
||||
}
|
||||
|
||||
changedTemplates.push(template)
|
||||
|
@ -10,6 +10,7 @@ import { generateApp as _generateApp, createApp } from './app'
|
||||
import { checkForExternalConfigurationFiles } from './external-config-files'
|
||||
import { cleanupCaches, getVueHash } from './cache'
|
||||
|
||||
const IS_RESTART_PATH_RE = /^(?:app\.|error\.|plugins\/|middleware\/|layouts\/)/i
|
||||
export async function build (nuxt: Nuxt) {
|
||||
const app = createApp(nuxt)
|
||||
nuxt.apps.default = app
|
||||
@ -23,7 +24,7 @@ export async function build (nuxt: Nuxt) {
|
||||
if (event === 'change') { return }
|
||||
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||
const relativePaths = nuxt.options._layers.map(l => relative(l.config.srcDir || l.cwd, path))
|
||||
const restartPath = relativePaths.find(relativePath => /^(?:app\.|error\.|plugins\/|middleware\/|layouts\/)/i.test(relativePath))
|
||||
const restartPath = relativePaths.find(relativePath => IS_RESTART_PATH_RE.test(relativePath))
|
||||
if (restartPath) {
|
||||
if (restartPath.startsWith('app')) {
|
||||
app.mainComponent = undefined
|
||||
|
@ -17,7 +17,8 @@ import { version as nuxtVersion } from '../../package.json'
|
||||
import { distDir } from '../dirs'
|
||||
import { toArray } from '../utils'
|
||||
import { template as defaultSpaLoadingTemplate } from '../../../ui-templates/dist/templates/spa-loading-icon'
|
||||
import { nuxtImportProtections } from './plugins/import-protection'
|
||||
import { createImportProtectionPatterns } from './plugins/import-protection'
|
||||
import { EXTENSION_RE } from './utils'
|
||||
|
||||
const logLevelMapReverse = {
|
||||
silent: 0,
|
||||
@ -25,12 +26,14 @@ const logLevelMapReverse = {
|
||||
verbose: 3,
|
||||
} satisfies Record<NuxtOptions['logLevel'], NitroConfig['logLevel']>
|
||||
|
||||
const NODE_MODULES_RE = /(?<=\/)node_modules\/(.+)$/
|
||||
const PNPM_NODE_MODULES_RE = /\.pnpm\/.+\/node_modules\/(.+)$/
|
||||
export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
// Resolve config
|
||||
const excludePaths = nuxt.options._layers
|
||||
.flatMap(l => [
|
||||
l.cwd.match(/(?<=\/)node_modules\/(.+)$/)?.[1],
|
||||
l.cwd.match(/\.pnpm\/.+\/node_modules\/(.+)$/)?.[1],
|
||||
l.cwd.match(NODE_MODULES_RE)?.[1],
|
||||
l.cwd.match(PNPM_NODE_MODULES_RE)?.[1],
|
||||
])
|
||||
.filter((dir): dir is string => Boolean(dir))
|
||||
.map(dir => escapeRE(dir))
|
||||
@ -46,6 +49,8 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
.map(m => m.entryPath!),
|
||||
)
|
||||
|
||||
const isNuxtV4 = nuxt.options.future?.compatibilityVersion === 4
|
||||
|
||||
const nitroConfig: NitroConfig = defu(nuxt.options.nitro, {
|
||||
debug: nuxt.options.debug,
|
||||
rootDir: nuxt.options.rootDir,
|
||||
@ -63,6 +68,12 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
},
|
||||
imports: {
|
||||
autoImport: nuxt.options.imports.autoImport as boolean,
|
||||
dirs: isNuxtV4
|
||||
? [
|
||||
resolve(nuxt.options.rootDir, 'shared', 'utils'),
|
||||
resolve(nuxt.options.rootDir, 'shared', 'types'),
|
||||
]
|
||||
: [],
|
||||
imports: [
|
||||
{
|
||||
as: '__buildAssetsURL',
|
||||
@ -339,11 +350,12 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
}
|
||||
|
||||
// Add fallback server for `ssr: false`
|
||||
const FORWARD_SLASH_RE = /\//g
|
||||
if (!nuxt.options.ssr) {
|
||||
nitroConfig.virtual!['#build/dist/server/server.mjs'] = 'export default () => {}'
|
||||
// In case a non-normalized absolute path is called for on Windows
|
||||
if (process.platform === 'win32') {
|
||||
nitroConfig.virtual!['#build/dist/server/server.mjs'.replace(/\//g, '\\')] = 'export default () => {}'
|
||||
nitroConfig.virtual!['#build/dist/server/server.mjs'.replace(FORWARD_SLASH_RE, '\\')] = 'export default () => {}'
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,18 +363,27 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
nitroConfig.virtual!['#build/dist/server/styles.mjs'] = 'export default {}'
|
||||
// In case a non-normalized absolute path is called for on Windows
|
||||
if (process.platform === 'win32') {
|
||||
nitroConfig.virtual!['#build/dist/server/styles.mjs'.replace(/\//g, '\\')] = 'export default {}'
|
||||
nitroConfig.virtual!['#build/dist/server/styles.mjs'.replace(FORWARD_SLASH_RE, '\\')] = 'export default {}'
|
||||
}
|
||||
}
|
||||
|
||||
// Register nuxt protection patterns
|
||||
nitroConfig.rollupConfig!.plugins = await nitroConfig.rollupConfig!.plugins || []
|
||||
nitroConfig.rollupConfig!.plugins = toArray(nitroConfig.rollupConfig!.plugins)
|
||||
|
||||
const sharedDir = withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared))
|
||||
const relativeSharedDir = withTrailingSlash(relative(nuxt.options.rootDir, resolve(nuxt.options.rootDir, nuxt.options.dir.shared)))
|
||||
const sharedPatterns = [/^#shared\//, new RegExp('^' + escapeRE(sharedDir)), new RegExp('^' + escapeRE(relativeSharedDir))]
|
||||
nitroConfig.rollupConfig!.plugins!.push(
|
||||
ImpoundPlugin.rollup({
|
||||
cwd: nuxt.options.rootDir,
|
||||
patterns: nuxtImportProtections(nuxt, { isNitro: true }),
|
||||
exclude: [/core[\\/]runtime[\\/]nitro[\\/]renderer/],
|
||||
include: sharedPatterns,
|
||||
patterns: createImportProtectionPatterns(nuxt, { context: 'shared' }),
|
||||
}),
|
||||
ImpoundPlugin.rollup({
|
||||
cwd: nuxt.options.rootDir,
|
||||
patterns: createImportProtectionPatterns(nuxt, { context: 'nitro-app' }),
|
||||
exclude: [/core[\\/]runtime[\\/]nitro[\\/]renderer/, ...sharedPatterns],
|
||||
}),
|
||||
)
|
||||
|
||||
@ -389,7 +410,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
tsConfig.compilerOptions.paths[alias] = [absolutePath]
|
||||
tsConfig.compilerOptions.paths[`${alias}/*`] = [`${absolutePath}/*`]
|
||||
} else {
|
||||
tsConfig.compilerOptions.paths[alias] = [absolutePath.replace(/\b\.\w+$/g, '')] /* remove extension */
|
||||
tsConfig.compilerOptions.paths[alias] = [absolutePath.replace(EXTENSION_RE, '')] /* remove extension */
|
||||
}
|
||||
}
|
||||
|
||||
@ -566,8 +587,9 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
}
|
||||
}
|
||||
|
||||
const RELATIVE_RE = /^([^.])/
|
||||
function relativeWithDot (from: string, to: string) {
|
||||
return relative(from, to).replace(/^([^.])/, './$1') || '.'
|
||||
return relative(from, to).replace(RELATIVE_RE, './$1') || '.'
|
||||
}
|
||||
|
||||
async function spaLoadingTemplatePath (nuxt: Nuxt) {
|
||||
|
@ -18,7 +18,6 @@ import type { DateString } from 'compatx'
|
||||
import escapeRE from 'escape-string-regexp'
|
||||
import { withTrailingSlash, withoutLeadingSlash } from 'ufo'
|
||||
import { ImpoundPlugin } from 'impound'
|
||||
import type { ImpoundOptions } from 'impound'
|
||||
import defu from 'defu'
|
||||
import { gt, satisfies } from 'semver'
|
||||
import { hasTTY, isCI } from 'std-env'
|
||||
@ -32,7 +31,7 @@ import { distDir, pkgDir } from '../dirs'
|
||||
import { version } from '../../package.json'
|
||||
import { scriptsStubsPreset } from '../imports/presets'
|
||||
import { resolveTypePath } from './utils/types'
|
||||
import { nuxtImportProtections } from './plugins/import-protection'
|
||||
import { createImportProtectionPatterns } from './plugins/import-protection'
|
||||
import { UnctxTransformPlugin } from './plugins/unctx'
|
||||
import { TreeShakeComposablesPlugin } from './plugins/tree-shake'
|
||||
import { DevOnlyPlugin } from './plugins/dev-only'
|
||||
@ -178,9 +177,10 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
|
||||
const coreTypePackages = nuxt.options.typescript.hoist || []
|
||||
const packageJSON = await readPackageJSON(nuxt.options.rootDir).catch(() => ({}) as PackageJson)
|
||||
const NESTED_PKG_RE = /^[^@]+\//
|
||||
nuxt._dependencies = new Set([...Object.keys(packageJSON.dependencies || {}), ...Object.keys(packageJSON.devDependencies || {})])
|
||||
const paths = Object.fromEntries(await Promise.all(coreTypePackages.map(async (pkg) => {
|
||||
const [_pkg = pkg, _subpath] = /^[^@]+\//.test(pkg) ? pkg.split('/') : [pkg]
|
||||
const [_pkg = pkg, _subpath] = NESTED_PKG_RE.test(pkg) ? pkg.split('/') : [pkg]
|
||||
const subpath = _subpath ? '/' + _subpath : ''
|
||||
|
||||
// ignore packages that exist in `package.json` as these can be resolved by TypeScript
|
||||
@ -248,16 +248,28 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
// Add plugin normalization plugin
|
||||
addBuildPlugin(RemovePluginMetadataPlugin(nuxt))
|
||||
|
||||
// shared folder import protection
|
||||
const sharedDir = withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared))
|
||||
const relativeSharedDir = withTrailingSlash(relative(nuxt.options.rootDir, resolve(nuxt.options.rootDir, nuxt.options.dir.shared)))
|
||||
const sharedPatterns = [/^#shared\//, new RegExp('^' + escapeRE(sharedDir)), new RegExp('^' + escapeRE(relativeSharedDir))]
|
||||
const sharedProtectionConfig = {
|
||||
cwd: nuxt.options.rootDir,
|
||||
include: sharedPatterns,
|
||||
patterns: createImportProtectionPatterns(nuxt, { context: 'shared' }),
|
||||
}
|
||||
addVitePlugin(() => ImpoundPlugin.vite(sharedProtectionConfig), { server: false })
|
||||
addWebpackPlugin(() => ImpoundPlugin.webpack(sharedProtectionConfig), { server: false })
|
||||
|
||||
// Add import protection
|
||||
const config: ImpoundOptions = {
|
||||
const nuxtProtectionConfig = {
|
||||
cwd: nuxt.options.rootDir,
|
||||
// Exclude top-level resolutions by plugins
|
||||
exclude: [join(nuxt.options.srcDir, 'index.html')],
|
||||
patterns: nuxtImportProtections(nuxt),
|
||||
exclude: [relative(nuxt.options.rootDir, join(nuxt.options.srcDir, 'index.html')), ...sharedPatterns],
|
||||
patterns: createImportProtectionPatterns(nuxt, { context: 'nuxt-app' }),
|
||||
}
|
||||
addVitePlugin(() => Object.assign(ImpoundPlugin.vite({ ...config, error: false }), { name: 'nuxt:import-protection' }), { client: false })
|
||||
addVitePlugin(() => Object.assign(ImpoundPlugin.vite({ ...config, error: true }), { name: 'nuxt:import-protection' }), { server: false })
|
||||
addWebpackPlugin(() => ImpoundPlugin.webpack(config))
|
||||
addVitePlugin(() => Object.assign(ImpoundPlugin.vite({ ...nuxtProtectionConfig, error: false }), { name: 'nuxt:import-protection' }), { client: false })
|
||||
addVitePlugin(() => Object.assign(ImpoundPlugin.vite({ ...nuxtProtectionConfig, error: true }), { name: 'nuxt:import-protection' }), { server: false })
|
||||
addWebpackPlugin(() => ImpoundPlugin.webpack(nuxtProtectionConfig))
|
||||
|
||||
// add resolver for modules used in virtual files
|
||||
addVitePlugin(() => resolveDeepImportsPlugin(nuxt), { client: false })
|
||||
@ -564,6 +576,11 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
if (nuxt.options.experimental.emitRouteChunkError === 'automatic') {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/chunk-reload.client'))
|
||||
}
|
||||
// Add experimental immediate page reload support
|
||||
if (nuxt.options.experimental.emitRouteChunkError === 'automatic-immediate') {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/chunk-reload-immediate.client'))
|
||||
}
|
||||
|
||||
// Add experimental session restoration support
|
||||
if (nuxt.options.experimental.restoreState) {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/restore-state.client'))
|
||||
|
@ -9,12 +9,17 @@ interface ImportProtectionOptions {
|
||||
exclude?: Array<RegExp | string>
|
||||
}
|
||||
|
||||
export const nuxtImportProtections = (nuxt: { options: NuxtOptions }, options: { isNitro?: boolean } = {}) => {
|
||||
interface NuxtImportProtectionOptions {
|
||||
context: 'nuxt-app' | 'nitro-app' | 'shared'
|
||||
}
|
||||
|
||||
export const createImportProtectionPatterns = (nuxt: { options: NuxtOptions }, options: NuxtImportProtectionOptions) => {
|
||||
const patterns: ImportProtectionOptions['patterns'] = []
|
||||
const context = contextFlags[options.context]
|
||||
|
||||
patterns.push([
|
||||
/^(nuxt|nuxt3|nuxt-nightly)$/,
|
||||
'`nuxt`, `nuxt3` or `nuxt-nightly` cannot be imported directly.' + (options.isNitro ? '' : ' Instead, import runtime Nuxt composables from `#app` or `#imports`.'),
|
||||
`\`nuxt\`, or \`nuxt-nightly\` cannot be imported directly in ${context}.` + (options.context === 'nuxt-app' ? ' Instead, import runtime Nuxt composables from `#app` or `#imports`.' : ''),
|
||||
])
|
||||
|
||||
patterns.push([
|
||||
@ -26,27 +31,33 @@ export const nuxtImportProtections = (nuxt: { options: NuxtOptions }, options: {
|
||||
|
||||
for (const mod of nuxt.options.modules.filter(m => typeof m === 'string')) {
|
||||
patterns.push([
|
||||
new RegExp(`^${escapeRE(mod as string)}$`),
|
||||
new RegExp(`^${escapeRE(mod)}$`),
|
||||
'Importing directly from module entry-points is not allowed.',
|
||||
])
|
||||
}
|
||||
|
||||
for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?runtime|types)/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) {
|
||||
patterns.push([i, 'This module cannot be imported' + (options.isNitro ? ' in server runtime.' : ' in the Vue part of your app.')])
|
||||
for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:presets|runtime|types))/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) {
|
||||
patterns.push([i, `This module cannot be imported in ${context}.`])
|
||||
}
|
||||
|
||||
if (options.isNitro) {
|
||||
if (options.context === 'nitro-app' || options.context === 'shared') {
|
||||
for (const i of ['#app', /^#build(\/|$)/]) {
|
||||
patterns.push([i, 'Vue app aliases are not allowed in server runtime.'])
|
||||
patterns.push([i, `Vue app aliases are not allowed in ${context}.`])
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.isNitro) {
|
||||
if (options.context === 'nuxt-app' || options.context === 'shared') {
|
||||
patterns.push([
|
||||
new RegExp(escapeRE(relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, nuxt.options.serverDir || 'server'))) + '\\/(api|routes|middleware|plugins)\\/'),
|
||||
'Importing from server is not allowed in the Vue part of your app.',
|
||||
`Importing from server is not allowed in ${context}.`,
|
||||
])
|
||||
}
|
||||
|
||||
return patterns
|
||||
}
|
||||
|
||||
const contextFlags = {
|
||||
'nitro-app': 'server runtime',
|
||||
'nuxt-app': 'the Vue part of your app',
|
||||
'shared': 'the #shared directory',
|
||||
} as const
|
||||
|
@ -10,6 +10,7 @@ interface VirtualFSPluginOptions {
|
||||
alias?: Record<string, string>
|
||||
}
|
||||
|
||||
const RELATIVE_ID_RE = /^\.{1,2}[\\/]/
|
||||
export const VirtualFSPlugin = (nuxt: Nuxt, options: VirtualFSPluginOptions) => createUnplugin(() => {
|
||||
const extensions = ['', ...nuxt.options.extensions]
|
||||
const alias = { ...nuxt.options.alias, ...options.alias }
|
||||
@ -40,7 +41,7 @@ export const VirtualFSPlugin = (nuxt: Nuxt, options: VirtualFSPluginOptions) =>
|
||||
return PREFIX + resolvedId
|
||||
}
|
||||
|
||||
if (importer && /^\.{1,2}[\\/]/.test(id)) {
|
||||
if (importer && RELATIVE_ID_RE.test(id)) {
|
||||
const path = resolve(dirname(withoutPrefix(importer)), id)
|
||||
const resolved = resolveWithExt(path)
|
||||
if (resolved) {
|
||||
|
@ -11,6 +11,7 @@ import type { NuxtTemplate } from 'nuxt/schema'
|
||||
import type { Nitro } from 'nitro/types'
|
||||
|
||||
import { annotatePlugins, checkForCircularDependencies } from './app'
|
||||
import { EXTENSION_RE } from './utils'
|
||||
|
||||
export const vueShim: NuxtTemplate = {
|
||||
filename: 'types/vue-shim.d.ts',
|
||||
@ -57,6 +58,7 @@ export const cssTemplate: NuxtTemplate = {
|
||||
getContents: ctx => ctx.nuxt.options.css.map(i => genImport(i)).join('\n'),
|
||||
}
|
||||
|
||||
const PLUGIN_TEMPLATE_RE = /_(45|46|47)/g
|
||||
export const clientPluginTemplate: NuxtTemplate = {
|
||||
filename: 'plugins.client.mjs',
|
||||
async getContents (ctx) {
|
||||
@ -66,7 +68,7 @@ export const clientPluginTemplate: NuxtTemplate = {
|
||||
const imports: string[] = []
|
||||
for (const plugin of clientPlugins) {
|
||||
const path = relative(ctx.nuxt.options.rootDir, plugin.src)
|
||||
const variable = genSafeVariableName(filename(plugin.src)).replace(/_(45|46|47)/g, '_') + '_' + hash(path)
|
||||
const variable = genSafeVariableName(filename(plugin.src)).replace(PLUGIN_TEMPLATE_RE, '_') + '_' + hash(path)
|
||||
exports.push(variable)
|
||||
imports.push(genImport(plugin.src, variable))
|
||||
}
|
||||
@ -86,7 +88,7 @@ export const serverPluginTemplate: NuxtTemplate = {
|
||||
const imports: string[] = []
|
||||
for (const plugin of serverPlugins) {
|
||||
const path = relative(ctx.nuxt.options.rootDir, plugin.src)
|
||||
const variable = genSafeVariableName(filename(path)).replace(/_(45|46|47)/g, '_') + '_' + hash(path)
|
||||
const variable = genSafeVariableName(filename(path)).replace(PLUGIN_TEMPLATE_RE, '_') + '_' + hash(path)
|
||||
exports.push(variable)
|
||||
imports.push(genImport(plugin.src, variable))
|
||||
}
|
||||
@ -98,7 +100,9 @@ export const serverPluginTemplate: NuxtTemplate = {
|
||||
}
|
||||
|
||||
const TS_RE = /\.[cm]?tsx?$/
|
||||
|
||||
const JS_LETTER_RE = /\.(?<letter>[cm])?jsx?$/
|
||||
const JS_RE = /\.[cm]jsx?$/
|
||||
const JS_CAPTURE_RE = /\.[cm](jsx?)$/
|
||||
export const pluginsDeclaration: NuxtTemplate = {
|
||||
filename: 'types/plugins.d.ts',
|
||||
getContents: async ({ nuxt, app }) => {
|
||||
@ -120,18 +124,18 @@ export const pluginsDeclaration: NuxtTemplate = {
|
||||
const pluginPath = resolve(typesDir, plugin.src)
|
||||
const relativePath = relative(typesDir, pluginPath)
|
||||
|
||||
const correspondingDeclaration = pluginPath.replace(/\.(?<letter>[cm])?jsx?$/, '.d.$<letter>ts')
|
||||
const correspondingDeclaration = pluginPath.replace(JS_LETTER_RE, '.d.$<letter>ts')
|
||||
// if `.d.ts` file exists alongside a `.js` plugin, or if `.d.mts` file exists alongside a `.mjs` plugin, we can use the entire path
|
||||
if (correspondingDeclaration !== pluginPath && exists(correspondingDeclaration)) {
|
||||
tsImports.push(relativePath)
|
||||
continue
|
||||
}
|
||||
|
||||
const incorrectDeclaration = pluginPath.replace(/\.[cm]jsx?$/, '.d.ts')
|
||||
const incorrectDeclaration = pluginPath.replace(JS_RE, '.d.ts')
|
||||
// if `.d.ts` file exists, but plugin is `.mjs`, add `.js` extension to the import
|
||||
// to hotfix issue until ecosystem updates to `@nuxt/module-builder@>=0.8.0`
|
||||
if (incorrectDeclaration !== pluginPath && exists(incorrectDeclaration)) {
|
||||
tsImports.push(relativePath.replace(/\.[cm](jsx?)$/, '.$1'))
|
||||
tsImports.push(relativePath.replace(JS_CAPTURE_RE, '.$1'))
|
||||
continue
|
||||
}
|
||||
|
||||
@ -174,11 +178,13 @@ export { }
|
||||
}
|
||||
|
||||
const adHocModules = ['router', 'pages', 'imports', 'meta', 'components', 'nuxt-config-schema']
|
||||
const IMPORT_NAME_RE = /\.\w+$/
|
||||
const GIT_RE = /^git\+/
|
||||
export const schemaTemplate: NuxtTemplate = {
|
||||
filename: 'types/schema.d.ts',
|
||||
getContents: async ({ nuxt }) => {
|
||||
const relativeRoot = relative(resolve(nuxt.options.buildDir, 'types'), nuxt.options.rootDir)
|
||||
const getImportName = (name: string) => (name[0] === '.' ? './' + join(relativeRoot, name) : name).replace(/\.\w+$/, '')
|
||||
const getImportName = (name: string) => (name[0] === '.' ? './' + join(relativeRoot, name) : name).replace(IMPORT_NAME_RE, '')
|
||||
|
||||
const modules = nuxt.options._installedModules
|
||||
.filter(m => m.meta && m.meta.configKey && m.meta.name && !adHocModules.includes(m.meta.name))
|
||||
@ -210,7 +216,7 @@ export const schemaTemplate: NuxtTemplate = {
|
||||
}
|
||||
if (link) {
|
||||
if (link.startsWith('git+')) {
|
||||
link = link.replace(/^git\+/, '')
|
||||
link = link.replace(GIT_RE, '')
|
||||
}
|
||||
if (!link.startsWith('http')) {
|
||||
link = 'https://github.com/' + link
|
||||
@ -377,7 +383,7 @@ export const appConfigDeclarationTemplate: NuxtTemplate = {
|
||||
filename: 'types/app.config.d.ts',
|
||||
getContents ({ app, nuxt }) {
|
||||
const typesDir = join(nuxt.options.buildDir, 'types')
|
||||
const configPaths = app.configs.map(path => relative(typesDir, path).replace(/\b\.\w+$/g, ''))
|
||||
const configPaths = app.configs.map(path => relative(typesDir, path).replace(EXTENSION_RE, ''))
|
||||
|
||||
return `
|
||||
import type { CustomAppConfig } from 'nuxt/schema'
|
||||
|
@ -14,3 +14,7 @@ export function uniqueBy<T, K extends keyof T> (arr: T[], key: K) {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export const QUOTE_RE = /["']/g
|
||||
export const EXTENSION_RE = /\b\.\w+$/g
|
||||
export const SX_RE = /\.[tj]sx$/
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { basename, dirname, extname, normalize } from 'pathe'
|
||||
import { kebabCase, splitByCase } from 'scule'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import { QUOTE_RE } from '.'
|
||||
|
||||
export function getNameFromPath (path: string, relativeTo?: string) {
|
||||
const relativePath = relativeTo
|
||||
@ -9,7 +10,7 @@ export function getNameFromPath (path: string, relativeTo?: string) {
|
||||
const prefixParts = splitByCase(dirname(relativePath))
|
||||
const fileName = basename(relativePath, extname(relativePath))
|
||||
const segments = resolveComponentNameSegments(fileName.toLowerCase() === 'index' ? '' : fileName, prefixParts).filter(Boolean)
|
||||
return kebabCase(segments).replace(/["']/g, '')
|
||||
return kebabCase(segments).replace(QUOTE_RE, '')
|
||||
}
|
||||
|
||||
export function hasSuffix (path: string, suffix: string) {
|
||||
|
@ -54,6 +54,8 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
|
||||
|
||||
await nuxt.callHook('imports:context', ctx)
|
||||
|
||||
const isNuxtV4 = nuxt.options.future?.compatibilityVersion === 4
|
||||
|
||||
// composables/ dirs from all layers
|
||||
let composablesDirs: string[] = []
|
||||
if (options.scan) {
|
||||
@ -64,6 +66,12 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
|
||||
}
|
||||
composablesDirs.push(resolve(layer.config.srcDir, 'composables'))
|
||||
composablesDirs.push(resolve(layer.config.srcDir, 'utils'))
|
||||
|
||||
if (isNuxtV4) {
|
||||
composablesDirs.push(resolve(layer.config.rootDir, 'shared', 'utils'))
|
||||
composablesDirs.push(resolve(layer.config.rootDir, 'shared', 'types'))
|
||||
}
|
||||
|
||||
for (const dir of (layer.config.imports?.dirs ?? [])) {
|
||||
if (!dir) {
|
||||
continue
|
||||
|
@ -109,6 +109,10 @@ const granularAppPresets: InlinePreset[] = [
|
||||
imports: ['useRouteAnnouncer'],
|
||||
from: '#app/composables/route-announcer',
|
||||
},
|
||||
{
|
||||
imports: ['useRuntimeHook'],
|
||||
from: '#app/composables/runtime-hook',
|
||||
},
|
||||
]
|
||||
|
||||
export const scriptsStubsPreset = {
|
||||
@ -216,9 +220,6 @@ const vuePreset = defineUnimportPreset({
|
||||
'hasInjectionContext',
|
||||
'nextTick',
|
||||
'provide',
|
||||
'defineModel',
|
||||
'defineOptions',
|
||||
'defineSlots',
|
||||
'mergeModels',
|
||||
'toValue',
|
||||
'useModel',
|
||||
|
@ -503,7 +503,7 @@ export default defineNuxtModule({
|
||||
const { routes, imports } = normalizeRoutes(app.pages, new Set(), {
|
||||
serverComponentRuntime,
|
||||
clientComponentRuntime,
|
||||
overrideMeta: nuxt.options.experimental.scanPageMeta,
|
||||
overrideMeta: !!nuxt.options.experimental.scanPageMeta,
|
||||
})
|
||||
return [...imports, `export default ${routes}`].join('\n')
|
||||
},
|
||||
|
@ -176,8 +176,10 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions) => createUnplugin
|
||||
|
||||
// https://github.com/vuejs/vue-loader/pull/1911
|
||||
// https://github.com/vitejs/vite/issues/8473
|
||||
const QUERY_START_RE = /^\?/
|
||||
const MACRO_RE = /¯o=true/
|
||||
function rewriteQuery (id: string) {
|
||||
return id.replace(/\?.+$/, r => '?macro=true&' + r.replace(/^\?/, '').replace(/¯o=true/, ''))
|
||||
return id.replace(/\?.+$/, r => '?macro=true&' + r.replace(QUERY_START_RE, '').replace(MACRO_RE, ''))
|
||||
}
|
||||
|
||||
function parseMacroQuery (id: string) {
|
||||
@ -189,6 +191,7 @@ function parseMacroQuery (id: string) {
|
||||
return query
|
||||
}
|
||||
|
||||
const QUOTED_SPECIFIER_RE = /(["']).*\1/
|
||||
function getQuotedSpecifier (id: string) {
|
||||
return id.match(/(["']).*\1/)?.[0]
|
||||
return id.match(QUOTED_SPECIFIER_RE)?.[0]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from 'vue-router'
|
||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRaw, RouteRecordRedirectOption } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { NitroRouteConfig } from 'nitro/types'
|
||||
import { useNuxtApp } from '#app/nuxt'
|
||||
@ -37,6 +37,11 @@ export interface PageMeta {
|
||||
name?: string
|
||||
/** You may define a path matcher, if you have a more complex pattern than can be expressed with the file name. */
|
||||
path?: string
|
||||
/**
|
||||
* Allows accessing the route `params` as props passed to the page component.
|
||||
* @see https://router.vuejs.org/guide/essentials/passing-props
|
||||
*/
|
||||
props?: RouteRecordRaw['props']
|
||||
/** Set to `false` to avoid scrolling to top on page navigations */
|
||||
scrollToTop?: boolean | ((to: RouteLocationNormalizedLoaded, from: RouteLocationNormalizedLoaded) => boolean)
|
||||
}
|
||||
|
@ -5,11 +5,14 @@ type InstanceOf<T> = T extends new (...args: any[]) => infer R ? R : never
|
||||
type RouterViewSlot = Exclude<InstanceOf<typeof RouterView>['$slots']['default'], undefined>
|
||||
export type RouterViewSlotProps = Parameters<RouterViewSlot>[0]
|
||||
|
||||
const ROUTE_KEY_PARENTHESES_RE = /(:\w+)\([^)]+\)/g
|
||||
const ROUTE_KEY_SYMBOLS_RE = /(:\w+)[?+*]/g
|
||||
const ROUTE_KEY_NORMAL_RE = /:\w+/g
|
||||
const interpolatePath = (route: RouteLocationNormalizedLoaded, match: RouteLocationMatched) => {
|
||||
return match.path
|
||||
.replace(/(:\w+)\([^)]+\)/g, '$1')
|
||||
.replace(/(:\w+)[?+*]/g, '$1')
|
||||
.replace(/:\w+/g, r => route.params[r.slice(1)]?.toString() || '')
|
||||
.replace(ROUTE_KEY_PARENTHESES_RE, '$1')
|
||||
.replace(ROUTE_KEY_SYMBOLS_RE, '$1')
|
||||
.replace(ROUTE_KEY_NORMAL_RE, r => route.params[r.slice(1)]?.toString() || '')
|
||||
}
|
||||
|
||||
export const generateRouteKey = (routeProps: RouterViewSlotProps, override?: string | ((route: RouteLocationNormalizedLoaded) => string)) => {
|
||||
|
@ -64,18 +64,25 @@ export async function resolvePagesRoutes (): Promise<NuxtPage[]> {
|
||||
})
|
||||
|
||||
const pages = uniqueBy(allRoutes, 'path')
|
||||
|
||||
const shouldAugment = nuxt.options.experimental.scanPageMeta || nuxt.options.experimental.typedPages
|
||||
|
||||
if (shouldAugment) {
|
||||
if (shouldAugment === false) {
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
return pages
|
||||
}
|
||||
|
||||
if (shouldAugment === 'after-resolve') {
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
await augmentPages(pages, nuxt.vfs)
|
||||
} else {
|
||||
const augmentedPages = await augmentPages(pages, nuxt.vfs)
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
await augmentPages(pages, nuxt.vfs, augmentedPages)
|
||||
augmentedPages.clear()
|
||||
} else {
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
}
|
||||
|
||||
await nuxt.callHook('pages:resolved', pages)
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
@ -83,6 +90,7 @@ type GenerateRoutesFromFilesOptions = {
|
||||
shouldUseServerComponents?: boolean
|
||||
}
|
||||
|
||||
const INDEX_PAGE_RE = /\/index$/
|
||||
export function generateRoutesFromFiles (files: ScannedFile[], options: GenerateRoutesFromFilesOptions = {}): NuxtPage[] {
|
||||
const routes: NuxtPage[] = []
|
||||
|
||||
@ -128,7 +136,7 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
|
||||
route.name += (route.name && '/') + segmentName
|
||||
|
||||
// ex: parent.vue + parent/child.vue
|
||||
const path = withLeadingSlash(joinURL(route.path, getRoutePath(tokens).replace(/\/index$/, '/')))
|
||||
const path = withLeadingSlash(joinURL(route.path, getRoutePath(tokens).replace(INDEX_PAGE_RE, '/')))
|
||||
const child = parent.find(parentRoute => parentRoute.name === route.name && parentRoute.path === path)
|
||||
|
||||
if (child && child.children) {
|
||||
@ -183,7 +191,7 @@ export function extractScriptContent (html: string) {
|
||||
}
|
||||
|
||||
const PAGE_META_RE = /definePageMeta\([\s\S]*?\)/
|
||||
const extractionKeys = ['name', 'path', 'alias', 'redirect'] as const
|
||||
const extractionKeys = ['name', 'path', 'props', 'alias', 'redirect'] as const
|
||||
const DYNAMIC_META_KEY = '__nuxt_dynamic_meta_key' as const
|
||||
|
||||
const pageContentsCache: Record<string, string> = {}
|
||||
@ -265,7 +273,7 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
|
||||
continue
|
||||
}
|
||||
|
||||
if (property.value.type !== 'Literal' || typeof property.value.value !== 'string') {
|
||||
if (property.value.type !== 'Literal' || (typeof property.value.value !== 'string' && typeof property.value.value !== 'boolean')) {
|
||||
console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not a string literal or array of string literals (reading \`${absolutePath}\`).`)
|
||||
dynamicProperties.add(key)
|
||||
continue
|
||||
@ -300,6 +308,7 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
|
||||
return extractedMeta
|
||||
}
|
||||
|
||||
const COLON_RE = /:/g
|
||||
function getRoutePath (tokens: SegmentToken[]): string {
|
||||
return tokens.reduce((path, token) => {
|
||||
return (
|
||||
@ -312,7 +321,7 @@ function getRoutePath (tokens: SegmentToken[]): string {
|
||||
? `:${token.value}(.*)*`
|
||||
: token.type === SegmentTokenType.group
|
||||
? ''
|
||||
: encodePath(token.value).replace(/:/g, '\\:'))
|
||||
: encodePath(token.value).replace(COLON_RE, '\\:'))
|
||||
)
|
||||
}, '/')
|
||||
}
|
||||
@ -432,13 +441,14 @@ function findRouteByName (name: string, routes: NuxtPage[]): NuxtPage | undefine
|
||||
return findRouteByName(name, routes)
|
||||
}
|
||||
|
||||
const NESTED_PAGE_RE = /\//g
|
||||
function prepareRoutes (routes: NuxtPage[], parent?: NuxtPage, names = new Set<string>()) {
|
||||
for (const route of routes) {
|
||||
// Remove -index
|
||||
if (route.name) {
|
||||
route.name = route.name
|
||||
.replace(/\/index$/, '')
|
||||
.replace(/\//g, '-')
|
||||
.replace(INDEX_PAGE_RE, '')
|
||||
.replace(NESTED_PAGE_RE, '-')
|
||||
|
||||
if (names.has(route.name)) {
|
||||
const existingRoute = findRouteByName(route.name, routes)
|
||||
@ -497,13 +507,14 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
|
||||
|
||||
const route: NormalizedRoute = {
|
||||
path: serializeRouteValue(page.path),
|
||||
props: serializeRouteValue(page.props),
|
||||
name: serializeRouteValue(page.name),
|
||||
meta: serializeRouteValue(metaFiltered, skipMeta),
|
||||
alias: serializeRouteValue(toArray(page.alias), skipAlias),
|
||||
redirect: serializeRouteValue(page.redirect),
|
||||
}
|
||||
|
||||
for (const key of ['path', 'name', 'meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) {
|
||||
for (const key of ['path', 'props', 'name', 'meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) {
|
||||
if (route[key] === undefined) {
|
||||
delete route[key]
|
||||
}
|
||||
@ -532,6 +543,7 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
|
||||
const metaRoute: NormalizedRoute = {
|
||||
name: `${metaImportName}?.name ?? ${route.name}`,
|
||||
path: `${metaImportName}?.path ?? ${route.path}`,
|
||||
props: `${metaImportName}?.props ?? ${route.props ?? false}`,
|
||||
meta: `${metaImportName} || {}`,
|
||||
alias: `${metaImportName}?.alias || []`,
|
||||
redirect: `${metaImportName}?.redirect`,
|
||||
@ -575,7 +587,7 @@ async function createClientPage(loader) {
|
||||
}
|
||||
|
||||
// set to extracted value or delete if none extracted
|
||||
for (const key of ['meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) {
|
||||
for (const key of ['meta', 'alias', 'redirect', 'props'] satisfies NormalizedRouteKeys) {
|
||||
if (markedDynamic.has(key)) { continue }
|
||||
|
||||
if (route[key] == null) {
|
||||
@ -600,6 +612,7 @@ async function createClientPage(loader) {
|
||||
}
|
||||
}
|
||||
|
||||
const PATH_TO_NITRO_GLOB_RE = /\/[^:/]*:\w.*$/
|
||||
export function pathToNitroGlob (path: string) {
|
||||
if (!path) {
|
||||
return null
|
||||
@ -609,7 +622,7 @@ export function pathToNitroGlob (path: string) {
|
||||
return null
|
||||
}
|
||||
|
||||
return path.replace(/\/[^:/]*:\w.*$/, '/**')
|
||||
return path.replace(PATH_TO_NITRO_GLOB_RE, '/**')
|
||||
}
|
||||
|
||||
export function resolveRoutePaths (page: NuxtPage, parent = '/'): string[] {
|
||||
|
@ -6,6 +6,7 @@
|
||||
"meta": "{ ...(mockMeta || {}), ...{"someMetaData":true} }",
|
||||
"name": "mockMeta?.name ?? "pushed-route"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -24,6 +25,18 @@
|
||||
"meta": "{ ...(mockMeta || {}), ...{"test":1} }",
|
||||
"name": "mockMeta?.name ?? "page-with-meta"",
|
||||
"path": "mockMeta?.path ?? "/page-with-meta"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
"route.meta props generate by file": [
|
||||
{
|
||||
"alias": "mockMeta?.alias || []",
|
||||
"component": "() => import("pages/page-with-props.vue")",
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "page-with-props"",
|
||||
"path": "mockMeta?.path ?? "/page-with-props"",
|
||||
"props": "mockMeta?.props ?? true",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -34,6 +47,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "test:name"",
|
||||
"path": "mockMeta?.path ?? "/test\\:name"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -50,6 +64,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "param-index"",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -58,6 +73,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "param-index-sibling"",
|
||||
"path": "mockMeta?.path ?? "sibling"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -65,6 +81,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -73,6 +90,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "param-sibling"",
|
||||
"path": "mockMeta?.path ?? "sibling"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -80,6 +98,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? "/param"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -91,6 +110,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "wrapper-expose-other"",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -99,6 +119,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "wrapper-expose-other-sibling"",
|
||||
"path": "mockMeta?.path ?? "sibling"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -106,6 +127,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? "/wrapper-expose/other"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -116,6 +138,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "home"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": ""/"",
|
||||
},
|
||||
],
|
||||
@ -126,6 +149,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "slug"",
|
||||
"path": "mockMeta?.path ?? "/:slug(.*)*"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -134,6 +158,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -144,6 +169,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -152,6 +178,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "slug"",
|
||||
"path": "mockMeta?.path ?? "/:slug()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -163,6 +190,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "foo"",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -170,6 +198,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? "/:foo?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -178,6 +207,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "optional-opt"",
|
||||
"path": "mockMeta?.path ?? "/optional/:opt?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -186,6 +216,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "optional-prefix-opt"",
|
||||
"path": "mockMeta?.path ?? "/optional/prefix-:opt?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -194,6 +225,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "optional-opt-postfix"",
|
||||
"path": "mockMeta?.path ?? "/optional/:opt?-postfix"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -202,6 +234,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "optional-prefix-opt-postfix"",
|
||||
"path": "mockMeta?.path ?? "/optional/prefix-:opt?-postfix"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -210,6 +243,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "bar"",
|
||||
"path": "mockMeta?.path ?? "/:bar()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -218,6 +252,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "nonopt-slug"",
|
||||
"path": "mockMeta?.path ?? "/nonopt/:slug()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -226,6 +261,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "opt-slug"",
|
||||
"path": "mockMeta?.path ?? "/opt/:slug?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -234,6 +270,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "sub-route-slug"",
|
||||
"path": "mockMeta?.path ?? "/:sub?/route-:slug()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -244,6 +281,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "stories"",
|
||||
"path": "mockMeta?.path ?? "/:stories(.*)*"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -252,6 +290,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "stories-id"",
|
||||
"path": "mockMeta?.path ?? "/stories/:id()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -262,6 +301,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "stories-id"",
|
||||
"path": "mockMeta?.path ?? "/stories/:id()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -270,6 +310,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "stories"",
|
||||
"path": "mockMeta?.path ?? "/:stories(.*)*"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -280,6 +321,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "kebab-case"",
|
||||
"path": "mockMeta?.path ?? "/kebab-case"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -290,6 +332,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "snake_case"",
|
||||
"path": "mockMeta?.path ?? "/snake_case"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -300,6 +343,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -308,6 +352,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent"",
|
||||
"path": "mockMeta?.path ?? "/parent"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -316,6 +361,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent-child"",
|
||||
"path": "mockMeta?.path ?? "/parent/child"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -329,6 +375,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent-child"",
|
||||
"path": "mockMeta?.path ?? "child"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -336,6 +383,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent"",
|
||||
"path": "mockMeta?.path ?? "/parent"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -346,6 +394,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -357,6 +406,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "about"",
|
||||
"path": "mockMeta?.path ?? """,
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -364,6 +414,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? undefined",
|
||||
"path": "mockMeta?.path ?? "/about"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -377,6 +428,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index-index-all"",
|
||||
"path": "mockMeta?.path ?? "all"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -384,6 +436,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -394,6 +447,7 @@
|
||||
"meta": "{ ...(mockMeta || {}), ...{"test":1} }",
|
||||
"name": "mockMeta?.name ?? "page-with-meta"",
|
||||
"path": "mockMeta?.path ?? "/page-with-meta"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -404,6 +458,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent-child"",
|
||||
"path": "mockMeta?.path ?? "/parent/:child()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -412,6 +467,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "parent-child"",
|
||||
"path": "mockMeta?.path ?? "/parent-:child()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -422,6 +478,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "foo"",
|
||||
"path": "mockMeta?.path ?? "/:foo?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -430,6 +487,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "foo"",
|
||||
"path": "mockMeta?.path ?? "/:foo()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -440,6 +498,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "a1_1a"",
|
||||
"path": "mockMeta?.path ?? "/:a1_1a()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -448,6 +507,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "b2.2b"",
|
||||
"path": "mockMeta?.path ?? "/:b2.2b()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -456,6 +516,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "b2_2b"",
|
||||
"path": "mockMeta?.path ?? "/:b2()_:2b()"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -464,6 +525,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "c33c"",
|
||||
"path": "mockMeta?.path ?? "/:c33c?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
{
|
||||
@ -472,6 +534,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "d44d"",
|
||||
"path": "mockMeta?.path ?? "/:d44d?"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -482,6 +545,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "home"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
@ -492,6 +556,7 @@
|
||||
"meta": "mockMeta || {}",
|
||||
"name": "mockMeta?.name ?? "index"",
|
||||
"path": "mockMeta?.path ?? "/"",
|
||||
"props": "mockMeta?.props ?? false",
|
||||
"redirect": "mockMeta?.redirect",
|
||||
},
|
||||
],
|
||||
|
@ -24,6 +24,14 @@
|
||||
"path": ""/page-with-meta"",
|
||||
},
|
||||
],
|
||||
"route.meta props generate by file": [
|
||||
{
|
||||
"component": "() => import("pages/page-with-props.vue")",
|
||||
"name": ""page-with-props"",
|
||||
"path": ""/page-with-props"",
|
||||
"props": "true",
|
||||
},
|
||||
],
|
||||
"should allow pages with `:` in their path": [
|
||||
{
|
||||
"component": "() => import("pages/test:name.vue")",
|
||||
|
@ -86,7 +86,10 @@ const excludedVueHelpers = [
|
||||
// Already globally registered
|
||||
'defineEmits',
|
||||
'defineExpose',
|
||||
'defineModel',
|
||||
'defineOptions',
|
||||
'defineProps',
|
||||
'defineSlots',
|
||||
'withDefaults',
|
||||
'stop',
|
||||
//
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { normalize } from 'pathe'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ImpoundPlugin } from 'impound'
|
||||
import { nuxtImportProtections } from '../src/core/plugins/import-protection'
|
||||
import { createImportProtectionPatterns } from '../src/core/plugins/import-protection'
|
||||
import type { NuxtOptions } from '../schema'
|
||||
|
||||
const testsToTriggerOn = [
|
||||
@ -28,7 +28,7 @@ const testsToTriggerOn = [
|
||||
|
||||
describe('import protection', () => {
|
||||
it.each(testsToTriggerOn)('should protect %s', async (id, importer, isProtected) => {
|
||||
const result = await transformWithImportProtection(id, importer)
|
||||
const result = await transformWithImportProtection(id, importer, 'nuxt-app')
|
||||
if (!isProtected) {
|
||||
expect(result).toBeNull()
|
||||
} else {
|
||||
@ -38,16 +38,16 @@ describe('import protection', () => {
|
||||
})
|
||||
})
|
||||
|
||||
const transformWithImportProtection = (id: string, importer: string) => {
|
||||
const transformWithImportProtection = (id: string, importer: string, context: 'nitro-app' | 'nuxt-app' | 'shared') => {
|
||||
const plugin = ImpoundPlugin.rollup({
|
||||
cwd: '/root',
|
||||
patterns: nuxtImportProtections({
|
||||
patterns: createImportProtectionPatterns({
|
||||
options: {
|
||||
modules: ['some-nuxt-module'],
|
||||
srcDir: '/root/src/',
|
||||
serverDir: '/root/src/server',
|
||||
} satisfies Partial<NuxtOptions> as NuxtOptions,
|
||||
}),
|
||||
}, { context }),
|
||||
})
|
||||
|
||||
return (plugin as any).resolveId.call({ error: () => {} }, id, importer)
|
||||
|
@ -211,6 +211,7 @@ describe('normalizeRoutes', () => {
|
||||
{
|
||||
name: indexN6pT4Un8hYMeta?.name ?? undefined,
|
||||
path: indexN6pT4Un8hYMeta?.path ?? "/",
|
||||
props: indexN6pT4Un8hYMeta?.props ?? false,
|
||||
meta: { ...(indexN6pT4Un8hYMeta || {}), ...{"layout":"test","foo":"bar"} },
|
||||
alias: indexN6pT4Un8hYMeta?.alias || [],
|
||||
redirect: indexN6pT4Un8hYMeta?.redirect,
|
||||
|
@ -601,6 +601,30 @@ describe('pages:generateRoutesFromFiles', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'route.meta props generate by file',
|
||||
files: [
|
||||
{
|
||||
path: `${pagesDir}/page-with-props.vue`,
|
||||
template: `
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
props: true
|
||||
})
|
||||
</script>
|
||||
`,
|
||||
},
|
||||
],
|
||||
output: [
|
||||
{
|
||||
name: 'page-with-props',
|
||||
path: '/page-with-props',
|
||||
file: `${pagesDir}/page-with-props.vue`,
|
||||
children: [],
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'should handle route groups',
|
||||
files: [
|
||||
|
@ -31,7 +31,7 @@
|
||||
"dependencies": {
|
||||
"@nuxt/friendly-errors-webpack-plugin": "^2.6.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@rspack/core": "^1.0.10",
|
||||
"@rspack/core": "^1.0.14",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
@ -45,11 +45,11 @@
|
||||
"globby": "^14.0.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"hash-sum": "^2.0.0",
|
||||
"jiti": "^2.3.3",
|
||||
"jiti": "^2.4.0",
|
||||
"knitwork": "^1.1.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"magic-string": "^0.30.12",
|
||||
"memfs": "^4.13.0",
|
||||
"memfs": "^4.14.0",
|
||||
"mlly": "^1.7.2",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
@ -64,7 +64,7 @@
|
||||
"time-fix-plugin": "^2.0.7",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^1.14.1",
|
||||
"unplugin": "^1.15.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-bundle-renderer": "^2.1.1",
|
||||
"vue-loader": "^17.4.2",
|
||||
@ -81,9 +81,9 @@
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
"rollup": "4.24.0",
|
||||
"rollup": "4.24.4",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vue": "3.5.11"
|
||||
"vue": "3.5.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.4"
|
||||
|
@ -39,26 +39,26 @@
|
||||
"@types/file-loader": "5.0.4",
|
||||
"@types/pug": "2.0.10",
|
||||
"@types/sass-loader": "8.0.9",
|
||||
"@unhead/schema": "1.11.7",
|
||||
"@unhead/schema": "1.11.11",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "4.0.1",
|
||||
"@vue/compiler-core": "3.5.11",
|
||||
"@vue/compiler-sfc": "3.5.11",
|
||||
"@vue/language-core": "2.1.6",
|
||||
"@vue/compiler-core": "3.5.12",
|
||||
"@vue/compiler-sfc": "3.5.12",
|
||||
"@vue/language-core": "2.1.10",
|
||||
"esbuild-loader": "4.2.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"ignore": "6.0.2",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"ofetch": "1.4.1",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"unctx": "2.3.1",
|
||||
"unenv": "1.10.0",
|
||||
"vite": "5.4.8",
|
||||
"vue": "3.5.11",
|
||||
"vite": "5.4.10",
|
||||
"vue": "3.5.12",
|
||||
"vue-bundle-renderer": "2.1.1",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue-router": "4.4.5",
|
||||
"webpack": "5.95.0",
|
||||
"webpack": "5.96.1",
|
||||
"webpack-dev-middleware": "7.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -355,6 +355,11 @@ export default defineUntypedSchema({
|
||||
*/
|
||||
plugins: 'plugins',
|
||||
|
||||
/**
|
||||
* The shared directory. This directory is shared between the app and the server.
|
||||
*/
|
||||
shared: 'shared',
|
||||
|
||||
/**
|
||||
* The directory containing your static files, which will be directly accessible via the Nuxt server
|
||||
* and copied across into your `dist` folder when your app is generated.
|
||||
@ -424,12 +429,13 @@ export default defineUntypedSchema({
|
||||
*/
|
||||
alias: {
|
||||
$resolve: async (val: Record<string, string>, get): Promise<Record<string, string>> => {
|
||||
const [srcDir, rootDir, assetsDir, publicDir, buildDir] = await Promise.all([get('srcDir'), get('rootDir'), get('dir.assets'), get('dir.public'), get('buildDir')]) as [string, string, string, string, string]
|
||||
const [srcDir, rootDir, assetsDir, publicDir, buildDir, sharedDir] = await Promise.all([get('srcDir'), get('rootDir'), get('dir.assets'), get('dir.public'), get('buildDir'), get('dir.shared')]) as [string, string, string, string, string, string]
|
||||
return {
|
||||
'~': srcDir,
|
||||
'@': srcDir,
|
||||
'~~': rootDir,
|
||||
'@@': rootDir,
|
||||
'#shared': resolve(rootDir, sharedDir),
|
||||
[basename(assetsDir)]: resolve(srcDir, assetsDir),
|
||||
[basename(publicDir)]: resolve(srcDir, publicDir),
|
||||
'#build': buildDir,
|
||||
|
@ -116,13 +116,16 @@ export default defineUntypedSchema({
|
||||
* Emit `app:chunkError` hook when there is an error loading vite/webpack
|
||||
* chunks.
|
||||
*
|
||||
* By default, Nuxt will also perform a hard reload of the new route
|
||||
* when a chunk fails to load when navigating to a new route.
|
||||
* By default, Nuxt will also perform a reload of the new route
|
||||
* when a chunk fails to load when navigating to a new route (`automatic`).
|
||||
*
|
||||
* Setting `automatic-immediate` will lead Nuxt to perform a reload of the current route
|
||||
* right when a chunk fails to load (instead of waiting for navigation).
|
||||
*
|
||||
* You can disable automatic handling by setting this to `false`, or handle
|
||||
* chunk errors manually by setting it to `manual`.
|
||||
* @see [Nuxt PR #19038](https://github.com/nuxt/nuxt/pull/19038)
|
||||
* @type {false | 'manual' | 'automatic'}
|
||||
* @type {false | 'manual' | 'automatic' | 'automatic-immediate'}
|
||||
*/
|
||||
emitRouteChunkError: {
|
||||
$resolve: (val) => {
|
||||
@ -297,8 +300,13 @@ export default defineUntypedSchema({
|
||||
* This only works with static or strings/arrays rather than variables or conditional assignment.
|
||||
*
|
||||
* @see [Nuxt Issues #24770](https://github.com/nuxt/nuxt/issues/24770)
|
||||
* @type {boolean | 'after-resolve'}
|
||||
*/
|
||||
scanPageMeta: true,
|
||||
scanPageMeta: {
|
||||
async $resolve (val, get) {
|
||||
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion === 4 ? 'after-resolve' : true)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Automatically share payload _data_ between pages that are prerendered. This can result in a significant
|
||||
|
@ -48,7 +48,7 @@ export default defineUntypedSchema({
|
||||
* Each handler accepts the following options:
|
||||
*
|
||||
* - handler: The path to the file defining the handler.
|
||||
* - route: The route under which the handler is available. This follows the conventions of [rou3](https://github.com/unjs/rou3.)
|
||||
* - route: The route under which the handler is available. This follows the conventions of [rou3](https://github.com/unjs/rou3).
|
||||
* - method: The HTTP method of requests that should be handled.
|
||||
* - middleware: Specifies whether it is a middleware handler.
|
||||
* - lazy: Specifies whether to use lazy loading to import the handler.
|
||||
|
@ -68,6 +68,7 @@ export type NuxtConfigLayer = ResolvedConfig<NuxtConfig & {
|
||||
rootDir: ConfigSchema['rootDir']
|
||||
}> & {
|
||||
cwd: string
|
||||
configFile: string
|
||||
}
|
||||
|
||||
export interface NuxtBuilder {
|
||||
|
@ -8,7 +8,7 @@ import type { Import, InlinePreset, Unimport } from 'unimport'
|
||||
import type { Compiler, Configuration, Stats } from 'webpack'
|
||||
import type { Nitro, NitroConfig } from 'nitro/types'
|
||||
import type { Schema, SchemaDefinition } from 'untyped'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import type { RouteLocationRaw, RouteRecordRaw } from 'vue-router'
|
||||
import type { VueCompilerOptions } from '@vue/language-core'
|
||||
import type { NuxtCompatibility, NuxtCompatibilityIssues, ViteConfig } from '..'
|
||||
import type { Component, ComponentsOptions } from './components'
|
||||
@ -28,6 +28,7 @@ export type VueTSConfig = 0 extends 1 & VueCompilerOptions ? TSConfig : TSConfig
|
||||
export type NuxtPage = {
|
||||
name?: string
|
||||
path: string
|
||||
props?: RouteRecordRaw['props']
|
||||
file?: string
|
||||
meta?: Record<string, any>
|
||||
alias?: string[] | string
|
||||
@ -183,12 +184,19 @@ export interface NuxtHooks {
|
||||
'builder:watch': (event: WatchEvent, path: string) => HookResult
|
||||
|
||||
/**
|
||||
* Called after pages routes are resolved.
|
||||
* @param pages Array containing resolved pages
|
||||
* Called after page routes are scanned from the file system.
|
||||
* @param pages Array containing scanned pages
|
||||
* @returns Promise
|
||||
*/
|
||||
'pages:extend': (pages: NuxtPage[]) => HookResult
|
||||
|
||||
/**
|
||||
* Called after page routes have been augmented with scanned metadata.
|
||||
* @param pages Array containing resolved pages
|
||||
* @returns Promise
|
||||
*/
|
||||
'pages:resolved': (pages: NuxtPage[]) => HookResult
|
||||
|
||||
/**
|
||||
* Called when resolving `app/router.options` files. It allows modifying the detected router options files
|
||||
* and adding new ones.
|
||||
|
@ -3,8 +3,7 @@ import { readFileSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs'
|
||||
import { copyFile } from 'node:fs/promises'
|
||||
import { basename, dirname, join } from 'pathe'
|
||||
import type { Plugin } from 'vite'
|
||||
// @ts-expect-error https://github.com/GoogleChromeLabs/critters/pull/151
|
||||
import Critters from 'critters'
|
||||
import Beasties from 'beasties'
|
||||
import { genObjectFromRawEntries } from 'knitwork'
|
||||
import htmlnano from 'htmlnano'
|
||||
import { glob } from 'tinyglobby'
|
||||
@ -25,7 +24,7 @@ export const RenderPlugin = () => {
|
||||
},
|
||||
enforce: 'post',
|
||||
async writeBundle () {
|
||||
const critters = new Critters({ path: outputDir })
|
||||
const critters = new Beasties({ path: outputDir })
|
||||
const htmlFiles = await glob(['templates/**/*.html'], {
|
||||
cwd: outputDir,
|
||||
absolute: true,
|
||||
|
@ -18,18 +18,18 @@
|
||||
"test": "pnpm lint && pnpm build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unocss/reset": "0.63.4",
|
||||
"critters": "0.0.25",
|
||||
"html-validate": "8.24.1",
|
||||
"@unocss/reset": "0.64.0",
|
||||
"beasties": "0.1.0",
|
||||
"html-validate": "8.24.2",
|
||||
"htmlnano": "2.1.1",
|
||||
"jiti": "2.3.3",
|
||||
"jiti": "2.4.0",
|
||||
"knitwork": "1.1.0",
|
||||
"pathe": "1.1.2",
|
||||
"prettier": "3.3.3",
|
||||
"scule": "1.3.0",
|
||||
"tinyexec": "0.3.0",
|
||||
"tinyglobby": "0.2.9",
|
||||
"unocss": "0.63.4",
|
||||
"vite": "5.4.8"
|
||||
"tinyexec": "0.3.1",
|
||||
"tinyglobby": "0.2.10",
|
||||
"unocss": "0.64.0",
|
||||
"vite": "5.4.10"
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { exec } from 'tinyexec'
|
||||
import { format } from 'prettier'
|
||||
import { createJiti } from 'jiti'
|
||||
// @ts-expect-error types not valid for bundler resolution
|
||||
import { HtmlValidate } from 'html-validate'
|
||||
|
||||
const distDir = fileURLToPath(new URL('../node_modules/.temp/dist/templates', import.meta.url))
|
||||
|
@ -27,9 +27,9 @@
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@types/clear": "0.1.4",
|
||||
"@types/estree": "1.0.6",
|
||||
"rollup": "4.24.0",
|
||||
"rollup": "4.24.4",
|
||||
"unbuild": "3.0.0-rc.11",
|
||||
"vue": "3.5.11"
|
||||
"vue": "3.5.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "workspace:*",
|
||||
@ -47,7 +47,7 @@
|
||||
"externality": "^1.0.2",
|
||||
"get-port-please": "^3.1.2",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"jiti": "^2.3.3",
|
||||
"jiti": "^2.4.0",
|
||||
"knitwork": "^1.1.0",
|
||||
"magic-string": "^0.30.12",
|
||||
"mlly": "^1.7.2",
|
||||
@ -61,9 +61,9 @@
|
||||
"strip-literal": "^2.1.0",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^1.14.1",
|
||||
"vite": "^5.4.8",
|
||||
"vite-node": "^2.1.2",
|
||||
"unplugin": "^1.15.0",
|
||||
"vite": "^5.4.10",
|
||||
"vite-node": "^2.1.4",
|
||||
"vite-plugin-checker": "^0.8.0",
|
||||
"vue-bundle-renderer": "^2.1.1"
|
||||
},
|
||||
|
@ -9,13 +9,12 @@ function sortPlugins ({ plugins, order }: NuxtOptions['postcss']): string[] {
|
||||
}
|
||||
|
||||
export async function resolveCSSOptions (nuxt: Nuxt): Promise<ViteConfig['css']> {
|
||||
const css: ViteConfig['css'] & { postcss: NonNullable<Exclude<NonNullable<ViteConfig['css']>['postcss'], string>> } = {
|
||||
const css: ViteConfig['css'] & { postcss: NonNullable<Exclude<NonNullable<ViteConfig['css']>['postcss'], string>> & { plugins: Plugin[] } } = {
|
||||
postcss: {
|
||||
plugins: [],
|
||||
},
|
||||
}
|
||||
|
||||
css.postcss.plugins = []
|
||||
const postcssOptions = nuxt.options.postcss
|
||||
|
||||
const jiti = createJiti(nuxt.options.rootDir, { alias: nuxt.options.alias })
|
||||
|
@ -20,6 +20,7 @@ interface ComposableKeysOptions {
|
||||
const stringTypes: Array<string | undefined> = ['Literal', 'TemplateLiteral']
|
||||
const NUXT_LIB_RE = /node_modules\/(?:nuxt|nuxt3|nuxt-nightly)\//
|
||||
const SUPPORTED_EXT_RE = /\.(?:m?[jt]sx?|vue)/
|
||||
const SCRIPT_RE = /(?<=<script[^>]*>)[\s\S]*?(?=<\/script>)/i
|
||||
|
||||
export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptions) => {
|
||||
const composableMeta: Record<string, any> = {}
|
||||
@ -43,7 +44,7 @@ export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptio
|
||||
},
|
||||
transform (code, id) {
|
||||
if (!KEYED_FUNCTIONS_RE.test(code)) { return }
|
||||
const { 0: script = code, index: codeIndex = 0 } = code.match(/(?<=<script[^>]*>)[\s\S]*?(?=<\/script>)/i) || { index: 0, 0: code }
|
||||
const { 0: script = code, index: codeIndex = 0 } = code.match(SCRIPT_RE) || { index: 0, 0: code }
|
||||
const s = new MagicString(code)
|
||||
// https://github.com/unjs/unplugin/issues/90
|
||||
let imports: Set<string> | undefined
|
||||
|
@ -10,6 +10,7 @@ import { isCSSRequest } from 'vite'
|
||||
const PREFIX = 'virtual:public?'
|
||||
const CSS_URL_RE = /url\((\/[^)]+)\)/g
|
||||
const CSS_URL_SINGLE_RE = /url\(\/[^)]+\)/
|
||||
const RENDER_CHUNK_RE = /(?<= = )['"`]/
|
||||
|
||||
interface VitePublicDirsPluginOptions {
|
||||
dev?: boolean
|
||||
@ -70,7 +71,7 @@ export const VitePublicDirsPlugin = createUnplugin((options: VitePublicDirsPlugi
|
||||
if (!chunk.facadeModuleId?.includes('?inline&used')) { return }
|
||||
|
||||
const s = new MagicString(code)
|
||||
const q = code.match(/(?<= = )['"`]/)?.[0] || '"'
|
||||
const q = code.match(RENDER_CHUNK_RE)?.[0] || '"'
|
||||
for (const [full, url] of code.matchAll(CSS_URL_RE)) {
|
||||
if (url && resolveFromPublicAssets(url)) {
|
||||
s.replace(full, `url(${q} + publicAssetsURL(${q}${url}${q}) + ${q})`)
|
||||
@ -108,13 +109,14 @@ export const VitePublicDirsPlugin = createUnplugin((options: VitePublicDirsPlugi
|
||||
]
|
||||
})
|
||||
|
||||
const PUBLIC_ASSETS_RE = /[?#].*$/
|
||||
export function useResolveFromPublicAssets () {
|
||||
const nitro = useNitro()
|
||||
|
||||
function resolveFromPublicAssets (id: string) {
|
||||
for (const dir of nitro.options.publicAssets) {
|
||||
if (!id.startsWith(withTrailingSlash(dir.baseURL || '/'))) { continue }
|
||||
const path = id.replace(/[?#].*$/, '').replace(withTrailingSlash(dir.baseURL || '/'), withTrailingSlash(dir.dir))
|
||||
const path = id.replace(PUBLIC_ASSETS_RE, '').replace(withTrailingSlash(dir.baseURL || '/'), withTrailingSlash(dir.dir))
|
||||
if (existsSync(path)) {
|
||||
return id
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { joinURL, withTrailingSlash, withoutLeadingSlash } from 'ufo'
|
||||
import type { ViteConfig } from '@nuxt/schema'
|
||||
import defu from 'defu'
|
||||
import type { Nitro } from 'nitro/types'
|
||||
import escapeStringRegexp from 'escape-string-regexp'
|
||||
import type { ViteBuildContext } from './vite'
|
||||
import { createViteLogger } from './utils/logger'
|
||||
import { initViteNodeServer } from './vite-node'
|
||||
@ -80,7 +81,13 @@ export async function buildServer (ctx: ViteBuildContext) {
|
||||
ssr: true,
|
||||
rollupOptions: {
|
||||
input: { server: entry },
|
||||
external: ['nitro/runtime', '#internal/nuxt/paths', '#internal/nuxt/app-config'],
|
||||
external: [
|
||||
'nitro/runtime',
|
||||
'#internal/nuxt/paths',
|
||||
'#internal/nuxt/app-config',
|
||||
'#shared',
|
||||
new RegExp('^' + escapeStringRegexp(withTrailingSlash(resolve(ctx.nuxt.options.rootDir, ctx.nuxt.options.dir.shared)))),
|
||||
],
|
||||
output: {
|
||||
entryFileNames: '[name].mjs',
|
||||
format: 'module',
|
||||
|
@ -1,9 +1,13 @@
|
||||
import type { ExternalsOptions } from 'externality'
|
||||
import { ExternalsDefaults, isExternal } from 'externality'
|
||||
import type { ViteDevServer } from 'vite'
|
||||
import escapeStringRegexp from 'escape-string-regexp'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import type { Nuxt } from 'nuxt/schema'
|
||||
import { resolve } from 'pathe'
|
||||
import { toArray } from '.'
|
||||
|
||||
export function createIsExternal (viteServer: ViteDevServer, rootDir: string, modulesDirs?: string[]) {
|
||||
export function createIsExternal (viteServer: ViteDevServer, nuxt: Nuxt) {
|
||||
const externalOpts: ExternalsOptions = {
|
||||
inline: [
|
||||
/virtual:/,
|
||||
@ -16,15 +20,17 @@ export function createIsExternal (viteServer: ViteDevServer, rootDir: string, mo
|
||||
),
|
||||
],
|
||||
external: [
|
||||
'#shared',
|
||||
new RegExp('^' + escapeStringRegexp(withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared)))),
|
||||
...(viteServer.config.ssr.external as string[]) || [],
|
||||
/node_modules/,
|
||||
],
|
||||
resolve: {
|
||||
modules: modulesDirs,
|
||||
modules: nuxt.options.modulesDir,
|
||||
type: 'module',
|
||||
extensions: ['.ts', '.js', '.json', '.vue', '.mjs', '.jsx', '.tsx', '.wasm'],
|
||||
},
|
||||
}
|
||||
|
||||
return (id: string) => isExternal(id, rootDir, externalOpts)
|
||||
return (id: string) => isExternal(id, nuxt.options.rootDir, externalOpts)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type * as vite from 'vite'
|
||||
import { logger } from '@nuxt/kit'
|
||||
import { colorize } from 'consola/utils'
|
||||
import { hasTTY, isCI } from 'std-env'
|
||||
import clear from 'clear'
|
||||
import type { NuxtOptions } from '@nuxt/schema'
|
||||
@ -22,6 +23,7 @@ const logLevelMapReverse: Record<NonNullable<vite.UserConfig['logLevel']>, numbe
|
||||
info: 3,
|
||||
}
|
||||
|
||||
const RUNTIME_RESOLVE_REF_RE = /^([^ ]+) referenced in/m
|
||||
export function createViteLogger (config: vite.InlineConfig): vite.Logger {
|
||||
const loggedErrors = new WeakSet<any>()
|
||||
const canClearScreen = hasTTY && !isCI && config.clearScreen
|
||||
@ -36,7 +38,7 @@ export function createViteLogger (config: vite.InlineConfig): vite.Logger {
|
||||
if (msg.startsWith('Sourcemap') && msg.includes('node_modules')) { return }
|
||||
// Hide warnings about externals produced by https://github.com/vitejs/vite/blob/v5.2.11/packages/vite/src/node/plugins/css.ts#L350-L355
|
||||
if (msg.includes('didn\'t resolve at build time, it will remain unchanged to be resolved at runtime')) {
|
||||
const id = msg.trim().match(/^([^ ]+) referenced in/m)?.[1]
|
||||
const id = msg.trim().match(RUNTIME_RESOLVE_REF_RE)?.[1]
|
||||
if (id && resolveFromPublicAssets(id)) { return }
|
||||
}
|
||||
}
|
||||
@ -61,8 +63,7 @@ export function createViteLogger (config: vite.InlineConfig): vite.Logger {
|
||||
|
||||
const prevLevel = logger.level
|
||||
logger.level = logLevelMapReverse[config.logLevel || 'info']
|
||||
// TODO: colorize counter after https://github.com/unjs/consola/pull/166
|
||||
logger[type](msg + (sameAsLast ? ` (x${duplicateCount + 1})` : ''))
|
||||
logger[type](msg + (sameAsLast ? colorize('dim', ` (x${duplicateCount + 1})`) : ''))
|
||||
logger.level = prevLevel
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user