mirror of
https://github.com/nuxt/nuxt.git
synced 2024-12-01 18:07:22 +00:00
Merge branch 'nuxt:main' into main
This commit is contained in:
commit
7501792b91
2
.github/workflows/autofix-docs.yml
vendored
2
.github/workflows/autofix-docs.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/autofix.yml
vendored
2
.github/workflows/autofix.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/changelog.yml
vendored
2
.github/workflows/changelog.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
2
.github/workflows/check-links.yml
vendored
2
.github/workflows/check-links.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
restore-keys: cache-lychee-
|
restore-keys: cache-lychee-
|
||||||
|
|
||||||
# check links with Lychee
|
# check links with Lychee
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- name: Lychee link checker
|
- name: Lychee link checker
|
||||||
uses: lycheeverse/lychee-action@25a231001d1723960a301b7d4c82884dc7ef857d # for v1.8.0
|
uses: lycheeverse/lychee-action@25a231001d1723960a301b7d4c82884dc7ef857d # for v1.8.0
|
||||||
|
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
- build
|
- build
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -83,7 +83,7 @@ jobs:
|
|||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8
|
uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
|
||||||
with:
|
with:
|
||||||
languages: javascript
|
languages: javascript
|
||||||
queries: +security-and-quality
|
queries: +security-and-quality
|
||||||
@ -95,7 +95,7 @@ jobs:
|
|||||||
path: packages
|
path: packages
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8
|
uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
|
||||||
with:
|
with:
|
||||||
category: "/language:javascript"
|
category: "/language:javascript"
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ jobs:
|
|||||||
module: ["bundler", "node"]
|
module: ["bundler", "node"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -142,7 +142,7 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -166,7 +166,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- build
|
- build
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -218,7 +218,7 @@ jobs:
|
|||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -248,7 +248,7 @@ jobs:
|
|||||||
TEST_PAYLOAD: ${{ matrix.payload }}
|
TEST_PAYLOAD: ${{ matrix.payload }}
|
||||||
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }}
|
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }}
|
||||||
|
|
||||||
- uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1
|
- uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
||||||
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
|
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
@ -270,7 +270,7 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
@ -309,7 +309,7 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@ -17,6 +17,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3
|
uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3
|
||||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/introspect.yml
vendored
2
.github/workflows/introspect.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
# From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
|
# From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
|
||||||
- name: Check workflow files
|
- name: Check workflow files
|
||||||
run: |
|
run: |
|
||||||
|
2
.github/workflows/release-pr.yml
vendored
2
.github/workflows/release-pr.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.issue.pull_request.head.sha }}
|
ref: ${{ github.event.issue.pull_request.head.sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
2
.github/workflows/reproduire.yml
vendored
2
.github/workflows/reproduire.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
reproduire:
|
reproduire:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
|
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
|
||||||
with:
|
with:
|
||||||
label: needs reproduction
|
label: needs reproduction
|
||||||
|
4
.github/workflows/scorecards.yml
vendored
4
.github/workflows/scorecards.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8
|
uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
|
||||||
if: github.repository == 'nuxt/nuxt' && success()
|
if: github.repository == 'nuxt/nuxt' && success()
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
@ -23,7 +23,7 @@ To use the latest Nuxt build and test features before their release, read about
|
|||||||
|
|
||||||
Nuxt 4 is planned to be released **on or before June 14** (though obviously this is dependent on having enough time after Nitro's major release to be properly tested in the community, so be aware that this is not an exact date).
|
Nuxt 4 is planned to be released **on or before June 14** (though obviously this is dependent on having enough time after Nitro's major release to be properly tested in the community, so be aware that this is not an exact date).
|
||||||
|
|
||||||
Until then, it is possible to test many of Nuxt 4's breaking changes on the nightly release channel.
|
Until then, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12 or via the nightly release channel.
|
||||||
|
|
||||||
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=r4wFKlcJK6c" target="_blank"}
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=r4wFKlcJK6c" target="_blank"}
|
||||||
Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking changes already.
|
Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking changes already.
|
||||||
|
@ -40,7 +40,7 @@ There is also a `future` namespace for early opting-in to new features that will
|
|||||||
### compatibilityVersion
|
### compatibilityVersion
|
||||||
|
|
||||||
::important
|
::important
|
||||||
This configuration option is available in Nuxt v3.12+ or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
This configuration option is available in Nuxt v3.12+.
|
||||||
::
|
::
|
||||||
|
|
||||||
This enables early access to Nuxt features or flags.
|
This enables early access to Nuxt features or flags.
|
||||||
|
@ -11,7 +11,7 @@ links:
|
|||||||
---
|
---
|
||||||
|
|
||||||
::important
|
::important
|
||||||
This component will be available in Nuxt v3.12 or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
This component is available in Nuxt v3.12+.
|
||||||
::
|
::
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -10,7 +10,7 @@ links:
|
|||||||
---
|
---
|
||||||
|
|
||||||
::important
|
::important
|
||||||
This composable will be available in Nuxt v3.12+ or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
This composable is available in Nuxt v3.12+.
|
||||||
::
|
::
|
||||||
|
|
||||||
`onPrehydrate` is a composable lifecycle hook that allows you to run a callback on the client immediately before
|
`onPrehydrate` is a composable lifecycle hook that allows you to run a callback on the client immediately before
|
||||||
|
@ -11,7 +11,7 @@ links:
|
|||||||
---
|
---
|
||||||
|
|
||||||
::important
|
::important
|
||||||
This composable will be available in Nuxt v3.12 or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
This composable is available in Nuxt v3.12+.
|
||||||
::
|
::
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
@ -98,6 +98,10 @@ export default defineNuxtComponent({
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::warning
|
||||||
|
Possible breaking change: `head` receives the nuxt app but cannot access the component instance. If the code in your `head` tries to access the data object through `this` or `this.$data`, you will need to migrate to the `useHead` composable.
|
||||||
|
::
|
||||||
|
|
||||||
## Title Template
|
## Title Template
|
||||||
|
|
||||||
If you want to use a function (for full control), then this cannot be set in your nuxt.config, and it is recommended instead to set it within your `/layouts` directory.
|
If you want to use a function (for full control), then this cannot be set in your nuxt.config, and it is recommended instead to set it within your `/layouts` directory.
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
"magic-string": "^0.30.10",
|
"magic-string": "^0.30.10",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"rollup": "^4.18.0",
|
"rollup": "^4.18.0",
|
||||||
"vite": "5.2.13",
|
"vite": "5.3.0",
|
||||||
"vue": "3.4.27"
|
"vue": "3.4.27"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -56,7 +56,7 @@
|
|||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
"@types/node": "20.14.2",
|
"@types/node": "20.14.2",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@unhead/schema": "1.9.12",
|
"@unhead/schema": "1.9.13",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
"@vitest/coverage-v8": "1.6.0",
|
"@vitest/coverage-v8": "1.6.0",
|
||||||
"@vue/test-utils": "2.4.6",
|
"@vue/test-utils": "2.4.6",
|
||||||
@ -66,7 +66,7 @@
|
|||||||
"devalue": "5.0.0",
|
"devalue": "5.0.0",
|
||||||
"eslint": "9.4.0",
|
"eslint": "9.4.0",
|
||||||
"eslint-plugin-no-only-tests": "3.1.0",
|
"eslint-plugin-no-only-tests": "3.1.0",
|
||||||
"eslint-plugin-perfectionist": "2.10.0",
|
"eslint-plugin-perfectionist": "2.11.0",
|
||||||
"eslint-typegen": "0.2.4",
|
"eslint-typegen": "0.2.4",
|
||||||
"execa": "9.2.0",
|
"execa": "9.2.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
@ -76,7 +76,7 @@
|
|||||||
"jiti": "1.21.6",
|
"jiti": "1.21.6",
|
||||||
"markdownlint-cli": "0.41.0",
|
"markdownlint-cli": "0.41.0",
|
||||||
"nitropack": "2.9.6",
|
"nitropack": "2.9.6",
|
||||||
"nuxi": "3.11.1",
|
"nuxi": "3.12.0",
|
||||||
"nuxt": "workspace:*",
|
"nuxt": "workspace:*",
|
||||||
"nuxt-content-twoslash": "0.0.10",
|
"nuxt-content-twoslash": "0.0.10",
|
||||||
"ofetch": "1.3.4",
|
"ofetch": "1.3.4",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxt/kit",
|
"name": "@nuxt/kit",
|
||||||
"version": "3.11.2",
|
"version": "3.12.1",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||||
@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"c12": "^1.10.0",
|
"c12": "^1.11.1",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"destr": "^2.0.3",
|
"destr": "^2.0.3",
|
||||||
@ -54,9 +54,9 @@
|
|||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"nitropack": "2.9.6",
|
"nitropack": "2.9.6",
|
||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"vite": "5.2.13",
|
"vite": "5.3.0",
|
||||||
"vitest": "1.6.0",
|
"vitest": "1.6.0",
|
||||||
"webpack": "5.91.0"
|
"webpack": "5.92.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
|
@ -61,7 +61,7 @@ function getRequireCacheItem (id: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getModulePaths (paths?: string[] | string) {
|
export function getNodeModulesPaths (paths?: string[] | string) {
|
||||||
return ([] as Array<string | undefined>).concat(
|
return ([] as Array<string | undefined>).concat(
|
||||||
global.__NUXT_PREPATHS__,
|
global.__NUXT_PREPATHS__,
|
||||||
paths || [],
|
paths || [],
|
||||||
@ -73,7 +73,7 @@ export function getModulePaths (paths?: string[] | string) {
|
|||||||
/** @deprecated Do not use CJS utils */
|
/** @deprecated Do not use CJS utils */
|
||||||
export function resolveModule (id: string, opts: ResolveModuleOptions = {}) {
|
export function resolveModule (id: string, opts: ResolveModuleOptions = {}) {
|
||||||
return normalize(_require.resolve(id, {
|
return normalize(_require.resolve(id, {
|
||||||
paths: getModulePaths(opts.paths),
|
paths: getNodeModulesPaths(opts.paths),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +32,11 @@ const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"\{(.+)\
|
|||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
const importSources = (sources: string | string[], { lazy = false } = {}) => {
|
const importSources = (sources: string | string[], { lazy = false } = {}) => {
|
||||||
return toArray(sources).map((src) => {
|
return toArray(sources).map((src) => {
|
||||||
|
const safeVariableName = genSafeVariableName(src)
|
||||||
if (lazy) {
|
if (lazy) {
|
||||||
return `const ${genSafeVariableName(src)} = ${genDynamicImport(src, { comment: `webpackChunkName: ${JSON.stringify(src)}` })}`
|
return `const ${safeVariableName} = ${genDynamicImport(src, { comment: `webpackChunkName: ${JSON.stringify(src)}` })}`
|
||||||
}
|
}
|
||||||
return genImport(src, genSafeVariableName(src))
|
return genImport(src, safeVariableName)
|
||||||
}).join('\n')
|
}).join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,10 +7,17 @@ import { NuxtConfigSchema } from '@nuxt/schema'
|
|||||||
import { globby } from 'globby'
|
import { globby } from 'globby'
|
||||||
import defu from 'defu'
|
import defu from 'defu'
|
||||||
|
|
||||||
export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}
|
export interface LoadNuxtConfigOptions extends Omit<LoadConfigOptions<NuxtConfig>, 'overrides'> {
|
||||||
|
overrides?: Exclude<LoadConfigOptions<NuxtConfig>['overrides'], Promise<any> | Function>
|
||||||
|
}
|
||||||
|
|
||||||
const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'dir']
|
const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'dir']
|
||||||
const layerSchema = Object.fromEntries(Object.entries(NuxtConfigSchema).filter(([key]) => layerSchemaKeys.includes(key)))
|
const layerSchema = Object.create(null)
|
||||||
|
for (const key of layerSchemaKeys) {
|
||||||
|
if (key in NuxtConfigSchema) {
|
||||||
|
layerSchema[key] = NuxtConfigSchema[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
|
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
|
||||||
// Automatically detect and import layers from `~~/layers/` directory
|
// Automatically detect and import layers from `~~/layers/` directory
|
||||||
@ -40,10 +47,15 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
|
|||||||
nuxtConfig._nuxtConfigFiles = [configFile]
|
nuxtConfig._nuxtConfigFiles = [configFile]
|
||||||
|
|
||||||
const _layers: ConfigLayer<NuxtConfig, ConfigLayerMeta>[] = []
|
const _layers: ConfigLayer<NuxtConfig, ConfigLayerMeta>[] = []
|
||||||
|
const processedLayers = new Set<string>()
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
// Resolve `rootDir` & `srcDir` of layers
|
// Resolve `rootDir` & `srcDir` of layers
|
||||||
layer.config = layer.config || {}
|
layer.config = layer.config || {}
|
||||||
layer.config.rootDir = layer.config.rootDir ?? layer.cwd
|
layer.config.rootDir = layer.config.rootDir ?? layer.cwd!
|
||||||
|
|
||||||
|
// Only process/resolve layers once
|
||||||
|
if (processedLayers.has(layer.config.rootDir)) { continue }
|
||||||
|
processedLayers.add(layer.config.rootDir)
|
||||||
|
|
||||||
// Normalise layer directories
|
// Normalise layer directories
|
||||||
layer.config = await applyDefaults(layerSchema, layer.config as NuxtConfig & Record<string, JSValue>) as unknown as NuxtConfig
|
layer.config = await applyDefaults(layerSchema, layer.config as NuxtConfig & Record<string, JSValue>) as unknown as NuxtConfig
|
||||||
|
@ -51,11 +51,10 @@ export async function getNuxtModuleVersion (module: string | NuxtModule, nuxt: N
|
|||||||
// need a name from here
|
// need a name from here
|
||||||
if (!moduleMeta.name) { return false }
|
if (!moduleMeta.name) { return false }
|
||||||
// maybe the version got attached within the installed module instance?
|
// maybe the version got attached within the installed module instance?
|
||||||
const version = nuxt.options._installedModules
|
for (const m of nuxt.options._installedModules) {
|
||||||
// @ts-expect-error _installedModules is not typed
|
if (m.meta.name === moduleMeta.name && m.meta.version) {
|
||||||
.filter(m => m.meta.name === moduleMeta.name).map(m => m.meta.version)?.[0]
|
return m.meta.version
|
||||||
if (version) {
|
}
|
||||||
return version
|
|
||||||
}
|
}
|
||||||
// it's possible that the module will be installed, it just hasn't been done yet, preemptively load the instance
|
// it's possible that the module will be installed, it just hasn't been done yet, preemptively load the instance
|
||||||
if (hasNuxtModule(moduleMeta.name)) {
|
if (hasNuxtModule(moduleMeta.name)) {
|
||||||
|
@ -51,11 +51,12 @@ export function addRouteMiddleware (input: NuxtMiddleware | NuxtMiddleware[], op
|
|||||||
for (const middleware of middlewares) {
|
for (const middleware of middlewares) {
|
||||||
const find = app.middleware.findIndex(item => item.name === middleware.name)
|
const find = app.middleware.findIndex(item => item.name === middleware.name)
|
||||||
if (find >= 0) {
|
if (find >= 0) {
|
||||||
if (app.middleware[find].path === middleware.path) { continue }
|
const foundPath = app.middleware[find].path
|
||||||
|
if (foundPath === middleware.path) { continue }
|
||||||
if (options.override === true) {
|
if (options.override === true) {
|
||||||
app.middleware[find] = { ...middleware }
|
app.middleware[find] = { ...middleware }
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`'${middleware.name}' middleware already exists at '${app.middleware[find].path}'. You can set \`override: true\` to replace it.`)
|
logger.warn(`'${middleware.name}' middleware already exists at '${foundPath}'. You can set \`override: true\` to replace it.`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
app.middleware.push({ ...middleware })
|
app.middleware.push({ ...middleware })
|
||||||
|
@ -168,8 +168,8 @@ export function createResolver (base: string | URL): Resolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveNuxtModule (base: string, paths: string[]) {
|
export async function resolveNuxtModule (base: string, paths: string[]): Promise<string[]> {
|
||||||
const resolved = []
|
const resolved: string[] = []
|
||||||
const resolver = createResolver(base)
|
const resolver = createResolver(base)
|
||||||
|
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
@ -209,6 +209,12 @@ function existsInVFS (path: string, nuxt = tryUseNuxt()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveFiles (path: string, pattern: string | string[], opts: { followSymbolicLinks?: boolean } = {}) {
|
export async function resolveFiles (path: string, pattern: string | string[], opts: { followSymbolicLinks?: boolean } = {}) {
|
||||||
const files = await globby(pattern, { cwd: path, followSymbolicLinks: opts.followSymbolicLinks ?? true })
|
const files: string[] = []
|
||||||
return files.map(p => resolve(path, p)).filter(p => !isIgnored(p)).sort()
|
for (const file of await globby(pattern, { cwd: path, followSymbolicLinks: opts.followSymbolicLinks ?? true })) {
|
||||||
|
const p = resolve(path, file)
|
||||||
|
if (!isIgnored(p)) {
|
||||||
|
files.push(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files.sort()
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import { readPackageJSON } from 'pkg-types'
|
|||||||
import { tryResolveModule } from './internal/esm'
|
import { tryResolveModule } from './internal/esm'
|
||||||
import { getDirectory } from './module/install'
|
import { getDirectory } from './module/install'
|
||||||
import { tryUseNuxt, useNuxt } from './context'
|
import { tryUseNuxt, useNuxt } from './context'
|
||||||
import { getModulePaths } from './internal/cjs'
|
import { getNodeModulesPaths } from './internal/cjs'
|
||||||
import { resolveNuxtModule } from './resolve'
|
import { resolveNuxtModule } from './resolve'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,18 +113,55 @@ export async function updateTemplates (options?: { filter?: (template: ResolvedN
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function _generateTypes (nuxt: Nuxt) {
|
export async function _generateTypes (nuxt: Nuxt) {
|
||||||
const nodeModulePaths = getModulePaths(nuxt.options.modulesDir)
|
|
||||||
|
|
||||||
const rootDirWithSlash = withTrailingSlash(nuxt.options.rootDir)
|
const rootDirWithSlash = withTrailingSlash(nuxt.options.rootDir)
|
||||||
|
const relativeRootDir = relativeWithDot(nuxt.options.buildDir, nuxt.options.rootDir)
|
||||||
|
|
||||||
const modulePaths = await resolveNuxtModule(rootDirWithSlash,
|
const include = new Set<string>([
|
||||||
nuxt.options._installedModules
|
'./nuxt.d.ts',
|
||||||
.filter(m => m.entryPath)
|
join(relativeRootDir, '.config/nuxt.*'),
|
||||||
.map(m => getDirectory(m.entryPath)),
|
join(relativeRootDir, '**/*'),
|
||||||
)
|
])
|
||||||
|
|
||||||
|
if (nuxt.options.srcDir !== nuxt.options.rootDir) {
|
||||||
|
include.add(join(relative(nuxt.options.buildDir, nuxt.options.srcDir), '**/*'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nuxt.options.typescript.includeWorkspace && nuxt.options.workspaceDir !== nuxt.options.rootDir) {
|
||||||
|
include.add(join(relative(nuxt.options.buildDir, nuxt.options.workspaceDir), '**/*'))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const layer of nuxt.options._layers) {
|
||||||
|
const srcOrCwd = layer.config.srcDir ?? layer.cwd
|
||||||
|
if (!srcOrCwd.startsWith(rootDirWithSlash) || srcOrCwd.includes('node_modules')) {
|
||||||
|
include.add(join(relative(nuxt.options.buildDir, srcOrCwd), '**/*'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exclude = new Set<string>([
|
||||||
|
// nitro generate output: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/core/nitro.ts#L186
|
||||||
|
relativeWithDot(nuxt.options.buildDir, resolve(nuxt.options.rootDir, 'dist')),
|
||||||
|
])
|
||||||
|
|
||||||
|
for (const dir of nuxt.options.modulesDir) {
|
||||||
|
exclude.add(relativeWithDot(nuxt.options.buildDir, dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleEntryPaths: string[] = []
|
||||||
|
for (const m of nuxt.options._installedModules) {
|
||||||
|
if (m.entryPath) {
|
||||||
|
moduleEntryPaths.push(getDirectory(m.entryPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const modulePaths = await resolveNuxtModule(rootDirWithSlash, moduleEntryPaths)
|
||||||
|
|
||||||
|
for (const path of modulePaths) {
|
||||||
|
const relative = relativeWithDot(nuxt.options.buildDir, path)
|
||||||
|
include.add(join(relative, 'runtime'))
|
||||||
|
exclude.add(join(relative, 'runtime/server'))
|
||||||
|
}
|
||||||
|
|
||||||
const isV4 = nuxt.options.future?.compatibilityVersion === 4
|
const isV4 = nuxt.options.future?.compatibilityVersion === 4
|
||||||
|
|
||||||
const hasTypescriptVersionWithModulePreserve = await readPackageJSON('typescript', { url: nuxt.options.modulesDir })
|
const hasTypescriptVersionWithModulePreserve = await readPackageJSON('typescript', { url: nuxt.options.modulesDir })
|
||||||
.then(r => r?.version && gte(r.version, '5.4.0'))
|
.then(r => r?.version && gte(r.version, '5.4.0'))
|
||||||
.catch(() => isV4)
|
.catch(() => isV4)
|
||||||
@ -168,23 +205,8 @@ export async function _generateTypes (nuxt: Nuxt) {
|
|||||||
noImplicitThis: true, /* enabled with `strict` */
|
noImplicitThis: true, /* enabled with `strict` */
|
||||||
allowSyntheticDefaultImports: true,
|
allowSyntheticDefaultImports: true,
|
||||||
},
|
},
|
||||||
include: [
|
include: [...include],
|
||||||
'./nuxt.d.ts',
|
exclude: [...exclude],
|
||||||
join(relativeWithDot(nuxt.options.buildDir, nuxt.options.rootDir), '.config/nuxt.*'),
|
|
||||||
join(relativeWithDot(nuxt.options.buildDir, nuxt.options.rootDir), '**/*'),
|
|
||||||
...nuxt.options.srcDir !== nuxt.options.rootDir ? [join(relative(nuxt.options.buildDir, nuxt.options.srcDir), '**/*')] : [],
|
|
||||||
...nuxt.options._layers.map(layer => layer.config.srcDir ?? layer.cwd)
|
|
||||||
.filter(srcOrCwd => !srcOrCwd.startsWith(rootDirWithSlash) || srcOrCwd.includes('node_modules'))
|
|
||||||
.map(srcOrCwd => join(relative(nuxt.options.buildDir, srcOrCwd), '**/*')),
|
|
||||||
...nuxt.options.typescript.includeWorkspace && nuxt.options.workspaceDir !== nuxt.options.rootDir ? [join(relative(nuxt.options.buildDir, nuxt.options.workspaceDir), '**/*')] : [],
|
|
||||||
...modulePaths.map(m => join(relativeWithDot(nuxt.options.buildDir, m), 'runtime')),
|
|
||||||
],
|
|
||||||
exclude: [
|
|
||||||
...nuxt.options.modulesDir.map(m => relativeWithDot(nuxt.options.buildDir, m)),
|
|
||||||
...modulePaths.map(m => join(relativeWithDot(nuxt.options.buildDir, m), 'runtime/server')),
|
|
||||||
// nitro generate output: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/core/nitro.ts#L186
|
|
||||||
relativeWithDot(nuxt.options.buildDir, resolve(nuxt.options.rootDir, 'dist')),
|
|
||||||
],
|
|
||||||
} satisfies TSConfig)
|
} satisfies TSConfig)
|
||||||
|
|
||||||
const aliases: Record<string, string> = {
|
const aliases: Record<string, string> = {
|
||||||
@ -195,7 +217,9 @@ export async function _generateTypes (nuxt: Nuxt) {
|
|||||||
// Exclude bridge alias types to support Volar
|
// Exclude bridge alias types to support Volar
|
||||||
const excludedAlias = [/^@vue\/.*$/]
|
const excludedAlias = [/^@vue\/.*$/]
|
||||||
|
|
||||||
const basePath = tsConfig.compilerOptions!.baseUrl ? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl) : nuxt.options.buildDir
|
const basePath = tsConfig.compilerOptions!.baseUrl
|
||||||
|
? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl)
|
||||||
|
: nuxt.options.buildDir
|
||||||
|
|
||||||
tsConfig.compilerOptions = tsConfig.compilerOptions || {}
|
tsConfig.compilerOptions = tsConfig.compilerOptions || {}
|
||||||
tsConfig.include = tsConfig.include || []
|
tsConfig.include = tsConfig.include || []
|
||||||
@ -237,12 +261,13 @@ export async function _generateTypes (nuxt: Nuxt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const references: TSReference[] = await Promise.all([
|
const references: TSReference[] = []
|
||||||
...nuxt.options.modules,
|
await Promise.all([...nuxt.options.modules, ...nuxt.options._modules].map(async (id) => {
|
||||||
...nuxt.options._modules,
|
if (typeof id !== 'string') { return }
|
||||||
]
|
|
||||||
.filter(f => typeof f === 'string')
|
const pkg = await readPackageJSON(id, { url: getNodeModulesPaths(nuxt.options.modulesDir) }).catch(() => null)
|
||||||
.map(async id => ({ types: (await readPackageJSON(id, { url: nodeModulePaths }).catch(() => null))?.name || id })))
|
references.push(({ types: pkg?.name || id }))
|
||||||
|
}))
|
||||||
|
|
||||||
const declarations: string[] = []
|
const declarations: string[] = []
|
||||||
|
|
||||||
@ -302,7 +327,11 @@ export async function writeTypes (nuxt: Nuxt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderAttrs (obj: Record<string, string>) {
|
function renderAttrs (obj: Record<string, string>) {
|
||||||
return Object.entries(obj).map(e => renderAttr(e[0], e[1])).join(' ')
|
const attrs: string[] = []
|
||||||
|
for (const key in obj) {
|
||||||
|
attrs.push(renderAttr(key, obj[key]))
|
||||||
|
}
|
||||||
|
return attrs.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAttr (key: string, value: string) {
|
function renderAttr (key: string, value: string) {
|
||||||
|
@ -53,12 +53,12 @@ describe('tsConfig generation', () => {
|
|||||||
}))
|
}))
|
||||||
expect(tsConfig.exclude).toMatchInlineSnapshot(`
|
expect(tsConfig.exclude).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
|
"../dist",
|
||||||
"../modules/test/node_modules",
|
"../modules/test/node_modules",
|
||||||
"../modules/node_modules",
|
"../modules/node_modules",
|
||||||
"../node_modules/@some/module/node_modules",
|
"../node_modules/@some/module/node_modules",
|
||||||
"../node_modules",
|
"../node_modules",
|
||||||
"../../node_modules",
|
"../../node_modules",
|
||||||
"../dist",
|
|
||||||
]
|
]
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nuxt",
|
"name": "nuxt",
|
||||||
"version": "3.11.2",
|
"version": "3.12.1",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||||
@ -65,12 +65,12 @@
|
|||||||
"@nuxt/schema": "workspace:*",
|
"@nuxt/schema": "workspace:*",
|
||||||
"@nuxt/telemetry": "^2.5.4",
|
"@nuxt/telemetry": "^2.5.4",
|
||||||
"@nuxt/vite-builder": "workspace:*",
|
"@nuxt/vite-builder": "workspace:*",
|
||||||
"@unhead/dom": "^1.9.12",
|
"@unhead/dom": "^1.9.13",
|
||||||
"@unhead/ssr": "^1.9.12",
|
"@unhead/ssr": "^1.9.13",
|
||||||
"@unhead/vue": "^1.9.12",
|
"@unhead/vue": "^1.9.13",
|
||||||
"@vue/shared": "^3.4.27",
|
"@vue/shared": "^3.4.27",
|
||||||
"acorn": "8.11.3",
|
"acorn": "8.11.3",
|
||||||
"c12": "^1.10.0",
|
"c12": "^1.11.1",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"cookie-es": "^1.1.0",
|
"cookie-es": "^1.1.0",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
@ -90,7 +90,7 @@
|
|||||||
"magic-string": "^0.30.10",
|
"magic-string": "^0.30.10",
|
||||||
"mlly": "^1.7.1",
|
"mlly": "^1.7.1",
|
||||||
"nitropack": "^2.9.6",
|
"nitropack": "^2.9.6",
|
||||||
"nuxi": "^3.11.1",
|
"nuxi": "^3.12.0",
|
||||||
"nypm": "^0.3.8",
|
"nypm": "^0.3.8",
|
||||||
"ofetch": "^1.3.4",
|
"ofetch": "^1.3.4",
|
||||||
"ohash": "^1.1.3",
|
"ohash": "^1.1.3",
|
||||||
@ -118,6 +118,7 @@
|
|||||||
"vue-router": "^4.3.3"
|
"vue-router": "^4.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@nuxt/scripts": "0.5.1",
|
||||||
"@nuxt/ui-templates": "1.3.4",
|
"@nuxt/ui-templates": "1.3.4",
|
||||||
"@parcel/watcher": "2.4.1",
|
"@parcel/watcher": "2.4.1",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.5",
|
||||||
@ -125,7 +126,7 @@
|
|||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
"@vue/compiler-sfc": "3.4.27",
|
"@vue/compiler-sfc": "3.4.27",
|
||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"vite": "5.2.13",
|
"vite": "5.3.0",
|
||||||
"vitest": "1.6.0"
|
"vitest": "1.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Component, PropType } from 'vue'
|
import type { Component, PropType, VNode } from 'vue'
|
||||||
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref, toRaw, watch, withMemo } from 'vue'
|
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref, toRaw, watch, withMemo } from 'vue'
|
||||||
import { debounce } from 'perfect-debounce'
|
import { debounce } from 'perfect-debounce'
|
||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
@ -29,7 +29,7 @@ const getId = import.meta.client ? () => (id++).toString() : randomUUID
|
|||||||
const components = import.meta.client ? new Map<string, Component>() : undefined
|
const components = import.meta.client ? new Map<string, Component>() : undefined
|
||||||
|
|
||||||
async function loadComponents (source = appBaseURL, paths: NuxtIslandResponse['components']) {
|
async function loadComponents (source = appBaseURL, paths: NuxtIslandResponse['components']) {
|
||||||
const promises = []
|
const promises: Array<Promise<void>> = []
|
||||||
|
|
||||||
for (const component in paths) {
|
for (const component in paths) {
|
||||||
if (!(components!.has(component))) {
|
if (!(components!.has(component))) {
|
||||||
@ -259,7 +259,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
// should away be triggered ONE tick after re-rendering the static node
|
// should away be triggered ONE tick after re-rendering the static node
|
||||||
withMemo([teleportKey.value], () => {
|
withMemo([teleportKey.value], () => {
|
||||||
const teleports = []
|
const teleports: Array<VNode> = []
|
||||||
// this is used to force trigger Teleport when vue makes the diff between old and new node
|
// this is used to force trigger Teleport when vue makes the diff between old and new node
|
||||||
const isKeyOdd = teleportKey.value === 0 || !!(teleportKey.value && !(teleportKey.value % 2))
|
const isKeyOdd = teleportKey.value === 0 || !!(teleportKey.value && !(teleportKey.value % 2))
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import type {
|
|||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref, resolveComponent } from 'vue'
|
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref, resolveComponent } from 'vue'
|
||||||
import type { RouteLocation, RouteLocationRaw, Router, RouterLink, RouterLinkProps, useLink } from '#vue-router'
|
import type { RouteLocation, RouteLocationRaw, Router, RouterLink, RouterLinkProps, useLink } from '#vue-router'
|
||||||
import { hasProtocol, joinURL, parseQuery, withTrailingSlash, withoutTrailingSlash } from 'ufo'
|
import { hasProtocol, joinURL, parseQuery, withQuery, withTrailingSlash, withoutTrailingSlash } from 'ufo'
|
||||||
import { preloadRouteComponents } from '../composables/preload'
|
import { preloadRouteComponents } from '../composables/preload'
|
||||||
import { onNuxtReady } from '../composables/ready'
|
import { onNuxtReady } from '../composables/ready'
|
||||||
import { navigateTo, useRouter } from '../composables/router'
|
import { navigateTo, useRouter } from '../composables/router'
|
||||||
@ -70,8 +70,8 @@ export interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
|
|||||||
* @see https://nuxt.com/docs/api/components/nuxt-link
|
* @see https://nuxt.com/docs/api/components/nuxt-link
|
||||||
*/
|
*/
|
||||||
export interface NuxtLinkOptions extends
|
export interface NuxtLinkOptions extends
|
||||||
Pick<RouterLinkProps, 'activeClass' | 'exactActiveClass'>,
|
Partial<Pick<RouterLinkProps, 'activeClass' | 'exactActiveClass'>>,
|
||||||
Pick<NuxtLinkProps, 'prefetchedClass'> {
|
Partial<Pick<NuxtLinkProps, 'prefetchedClass'>> {
|
||||||
/**
|
/**
|
||||||
* The name of the component.
|
* The name of the component.
|
||||||
* @default "NuxtLink"
|
* @default "NuxtLink"
|
||||||
@ -124,34 +124,17 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
// Resolving `to` value from `to` and `href` props
|
const hasTarget = computed(() => !!props.target && props.target !== '_self')
|
||||||
const to: ComputedRef<string | RouteLocationRaw> = computed(() => {
|
|
||||||
checkPropConflicts(props, 'to', 'href')
|
|
||||||
const path = props.to || props.href || '' // Defaults to empty string (won't render any `href` attribute)
|
|
||||||
return resolveTrailingSlashBehavior(path, router.resolve)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Lazily check whether to.value has a protocol
|
// Lazily check whether to.value has a protocol
|
||||||
const isAbsoluteUrl = computed(() => typeof to.value === 'string' && hasProtocol(to.value, { acceptRelative: true }))
|
const isAbsoluteUrl = computed(() => {
|
||||||
|
const path = props.to || props.href || ''
|
||||||
// Resolves `to` value if it's a route location object
|
return typeof path === 'string' && hasProtocol(path, { acceptRelative: true })
|
||||||
const href = computed(() => (typeof to.value === 'object'
|
})
|
||||||
? router.resolve(to.value)?.href ?? null
|
|
||||||
: (to.value && !props.external && !isAbsoluteUrl.value)
|
|
||||||
? resolveTrailingSlashBehavior(joinURL(config.app.baseURL, to.value), router.resolve) as string
|
|
||||||
: to.value
|
|
||||||
))
|
|
||||||
|
|
||||||
const builtinRouterLink = resolveComponent('RouterLink') as string | typeof RouterLink
|
const builtinRouterLink = resolveComponent('RouterLink') as string | typeof RouterLink
|
||||||
const useBuiltinLink = builtinRouterLink && typeof builtinRouterLink !== 'string' ? builtinRouterLink.useLink : undefined
|
const useBuiltinLink = builtinRouterLink && typeof builtinRouterLink !== 'string' ? builtinRouterLink.useLink : undefined
|
||||||
|
|
||||||
const link = useBuiltinLink?.({
|
|
||||||
...props,
|
|
||||||
to: to.value,
|
|
||||||
})
|
|
||||||
|
|
||||||
const hasTarget = computed(() => props.target && props.target !== '_self')
|
|
||||||
|
|
||||||
// Resolving link type
|
// Resolving link type
|
||||||
const isExternal = computed<boolean>(() => {
|
const isExternal = computed<boolean>(() => {
|
||||||
// External prop is explicitly set
|
// External prop is explicitly set
|
||||||
@ -159,17 +142,40 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// When `target` prop is set, link is external
|
const path = props.to || props.href || ''
|
||||||
if (hasTarget.value) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// When `to` is a route object then it's an internal link
|
// When `to` is a route object then it's an internal link
|
||||||
if (typeof to.value === 'object') {
|
if (typeof path === 'object') {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return to.value === '' || isAbsoluteUrl.value
|
return path === '' || isAbsoluteUrl.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// Resolving `to` value from `to` and `href` props
|
||||||
|
const to: ComputedRef<RouteLocationRaw> = computed(() => {
|
||||||
|
checkPropConflicts(props, 'to', 'href')
|
||||||
|
const path = props.to || props.href || '' // Defaults to empty string (won't render any `href` attribute)
|
||||||
|
if (isExternal.value) { return path }
|
||||||
|
return resolveTrailingSlashBehavior(path, router.resolve)
|
||||||
|
})
|
||||||
|
|
||||||
|
const link = isExternal.value ? undefined : useBuiltinLink?.({ ...props, to })
|
||||||
|
|
||||||
|
// Resolves `to` value if it's a route location object
|
||||||
|
const href = computed(() => {
|
||||||
|
if (!to.value || isAbsoluteUrl.value) { return to.value as string }
|
||||||
|
|
||||||
|
if (isExternal.value) {
|
||||||
|
const path = typeof to.value === 'object' ? resolveRouteObject(to.value) : to.value
|
||||||
|
return resolveTrailingSlashBehavior(path, router.resolve /* will not be called */) as string
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof to.value === 'object') {
|
||||||
|
return router.resolve(to.value)?.href ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveTrailingSlashBehavior(joinURL(config.app.baseURL, to.value), router.resolve /* will not be called */)
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -183,10 +189,10 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
isExactActive: link?.isExactActive ?? computed(() => to.value === router.currentRoute.value.path),
|
isExactActive: link?.isExactActive ?? computed(() => to.value === router.currentRoute.value.path),
|
||||||
route: link?.route ?? computed(() => router.resolve(to.value)),
|
route: link?.route ?? computed(() => router.resolve(to.value)),
|
||||||
async navigate () {
|
async navigate () {
|
||||||
await navigateTo(href.value, { replace: props.replace, external: props.external })
|
await navigateTo(href.value, { replace: props.replace, external: isExternal.value || hasTarget.value })
|
||||||
},
|
},
|
||||||
} satisfies ReturnType<typeof useLink> & {
|
} satisfies ReturnType<typeof useLink> & {
|
||||||
to: ComputedRef<string | RouteLocationRaw>
|
to: ComputedRef<RouteLocationRaw>
|
||||||
hasTarget: ComputedRef<boolean | null | undefined>
|
hasTarget: ComputedRef<boolean | null | undefined>
|
||||||
isAbsoluteUrl: ComputedRef<boolean>
|
isAbsoluteUrl: ComputedRef<boolean>
|
||||||
isExternal: ComputedRef<boolean>
|
isExternal: ComputedRef<boolean>
|
||||||
@ -307,10 +313,12 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
unobserve?.()
|
unobserve?.()
|
||||||
unobserve = null
|
unobserve = null
|
||||||
|
|
||||||
const path = typeof to.value === 'string' ? to.value : router.resolve(to.value).fullPath
|
const path = typeof to.value === 'string'
|
||||||
|
? to.value
|
||||||
|
: isExternal.value ? resolveRouteObject(to.value) : router.resolve(to.value).fullPath
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
nuxtApp.hooks.callHook('link:prefetch', path).catch(() => {}),
|
nuxtApp.hooks.callHook('link:prefetch', path).catch(() => {}),
|
||||||
!isExternal.value && preloadRouteComponents(to.value as string, router).catch(() => {}),
|
!isExternal.value && !hasTarget.value && preloadRouteComponents(to.value as string, router).catch(() => {}),
|
||||||
])
|
])
|
||||||
prefetched.value = true
|
prefetched.value = true
|
||||||
})
|
})
|
||||||
@ -336,7 +344,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (!isExternal.value) {
|
if (!isExternal.value && !hasTarget.value) {
|
||||||
const routerLinkProps: RouterLinkProps & VNodeProps & AllowedComponentProps & AnchorHTMLAttributes = {
|
const routerLinkProps: RouterLinkProps & VNodeProps & AllowedComponentProps & AnchorHTMLAttributes = {
|
||||||
ref: elRef,
|
ref: elRef,
|
||||||
to: to.value,
|
to: to.value,
|
||||||
@ -408,7 +416,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
|
|||||||
},
|
},
|
||||||
rel,
|
rel,
|
||||||
target,
|
target,
|
||||||
isExternal: isExternal.value,
|
isExternal: isExternal.value || hasTarget.value,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isExactActive: false,
|
isExactActive: false,
|
||||||
})
|
})
|
||||||
@ -487,3 +495,7 @@ function isSlowConnection () {
|
|||||||
if (cn && (cn.saveData || /2g/.test(cn.effectiveType))) { return true }
|
if (cn && (cn.saveData || /2g/.test(cn.effectiveType))) { return true }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveRouteObject (to: Exclude<RouteLocationRaw, string>) {
|
||||||
|
return withQuery(to.path || '', to.query || {}) + (to.hash ? '#' + to.hash : '')
|
||||||
|
}
|
||||||
|
@ -86,7 +86,7 @@ export function vforToArray (source: any): any[] {
|
|||||||
if (import.meta.dev && !Number.isInteger(source)) {
|
if (import.meta.dev && !Number.isInteger(source)) {
|
||||||
console.warn(`The v-for range expect an integer value but got ${source}.`)
|
console.warn(`The v-for range expect an integer value but got ${source}.`)
|
||||||
}
|
}
|
||||||
const array = []
|
const array: number[] = []
|
||||||
for (let i = 0; i < source; i++) {
|
for (let i = 0; i < source; i++) {
|
||||||
array[i] = i
|
array[i] = i
|
||||||
}
|
}
|
||||||
|
@ -37,3 +37,4 @@ export { reloadNuxtApp } from './chunk'
|
|||||||
export { useRequestURL } from './url'
|
export { useRequestURL } from './url'
|
||||||
export { usePreviewMode } from './preview'
|
export { usePreviewMode } from './preview'
|
||||||
export { useId } from './id'
|
export { useId } from './id'
|
||||||
|
export { useRouteAnnouncer } from './route-announcer'
|
||||||
|
@ -3,6 +3,7 @@ import { parse } from 'devalue'
|
|||||||
import { useHead } from '@unhead/vue'
|
import { useHead } from '@unhead/vue'
|
||||||
import { getCurrentInstance, onServerPrefetch } from 'vue'
|
import { getCurrentInstance, onServerPrefetch } from 'vue'
|
||||||
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
||||||
|
import type { NuxtPayload } from '../nuxt'
|
||||||
|
|
||||||
import { useRoute } from './router'
|
import { useRoute } from './router'
|
||||||
import { getAppManifest, getRouteRules } from './manifest'
|
import { getAppManifest, getRouteRules } from './manifest'
|
||||||
@ -95,11 +96,12 @@ export async function isPrerendered (url = useRoute().path) {
|
|||||||
return !!rules.prerender && !rules.redirect
|
return !!rules.prerender && !rules.redirect
|
||||||
}
|
}
|
||||||
|
|
||||||
let payloadCache: any = null
|
let payloadCache: NuxtPayload | null = null
|
||||||
|
|
||||||
/** @since 3.4.0 */
|
/** @since 3.4.0 */
|
||||||
export async function getNuxtClientPayload () {
|
export async function getNuxtClientPayload () {
|
||||||
if (import.meta.server) {
|
if (import.meta.server) {
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
if (payloadCache) {
|
if (payloadCache) {
|
||||||
return payloadCache
|
return payloadCache
|
||||||
@ -107,7 +109,7 @@ export async function getNuxtClientPayload () {
|
|||||||
|
|
||||||
const el = document.getElementById('__NUXT_DATA__')
|
const el = document.getElementById('__NUXT_DATA__')
|
||||||
if (!el) {
|
if (!el) {
|
||||||
return {}
|
return {} as Partial<NuxtPayload>
|
||||||
}
|
}
|
||||||
|
|
||||||
const inlineData = await parsePayload(el.textContent || '')
|
const inlineData = await parsePayload(el.textContent || '')
|
||||||
|
@ -7,8 +7,8 @@ export const onNuxtReady = (callback: () => any) => {
|
|||||||
|
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
if (nuxtApp.isHydrating) {
|
if (nuxtApp.isHydrating) {
|
||||||
nuxtApp.hooks.hookOnce('app:suspense:resolve', () => { requestIdleCallback(callback) })
|
nuxtApp.hooks.hookOnce('app:suspense:resolve', () => { requestIdleCallback(() => callback()) })
|
||||||
} else {
|
} else {
|
||||||
requestIdleCallback(callback)
|
requestIdleCallback(() => callback())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ export interface NuxtSSRContext extends SSRContext {
|
|||||||
/** whether we are rendering an SSR error */
|
/** whether we are rendering an SSR error */
|
||||||
error?: boolean
|
error?: boolean
|
||||||
nuxt: _NuxtApp
|
nuxt: _NuxtApp
|
||||||
payload: NuxtPayload
|
payload: Partial<NuxtPayload>
|
||||||
head: VueHeadClient<MergeHead>
|
head: VueHeadClient<MergeHead>
|
||||||
/** This is used solely to render runtime config with SPA renderer. */
|
/** This is used solely to render runtime config with SPA renderer. */
|
||||||
config?: Pick<RuntimeConfig, 'public' | 'app'>
|
config?: Pick<RuntimeConfig, 'public' | 'app'>
|
||||||
@ -558,6 +558,7 @@ export function defineAppConfig<C extends AppConfigInput> (config: C): C {
|
|||||||
/**
|
/**
|
||||||
* Configure error getter on runtime secret property access that doesn't exist on the client side
|
* Configure error getter on runtime secret property access that doesn't exist on the client side
|
||||||
*/
|
*/
|
||||||
|
const loggedKeys = new Set<string>()
|
||||||
function wrappedConfig (runtimeConfig: Record<string, unknown>) {
|
function wrappedConfig (runtimeConfig: Record<string, unknown>) {
|
||||||
if (!import.meta.dev || import.meta.server) { return runtimeConfig }
|
if (!import.meta.dev || import.meta.server) { return runtimeConfig }
|
||||||
const keys = Object.keys(runtimeConfig).map(key => `\`${key}\``)
|
const keys = Object.keys(runtimeConfig).map(key => `\`${key}\``)
|
||||||
@ -565,7 +566,10 @@ function wrappedConfig (runtimeConfig: Record<string, unknown>) {
|
|||||||
return new Proxy(runtimeConfig, {
|
return new Proxy(runtimeConfig, {
|
||||||
get (target, p, receiver) {
|
get (target, p, receiver) {
|
||||||
if (typeof p === 'string' && p !== 'public' && !(p in target) && !p.startsWith('__v') /* vue check for reactivity, e.g. `__v_isRef` */) {
|
if (typeof p === 'string' && p !== 'public' && !(p in target) && !p.startsWith('__v') /* vue check for reactivity, e.g. `__v_isRef` */) {
|
||||||
console.warn(`[nuxt] Could not access \`${p}\`. The only available runtime config keys on the client side are ${keys.join(', ')} and ${lastKey}. See \`https://nuxt.com/docs/guide/going-further/runtime-config\` for more information.`)
|
if (!loggedKeys.has(p)) {
|
||||||
|
loggedKeys.add(p)
|
||||||
|
console.warn(`[nuxt] Could not access \`${p}\`. The only available runtime config keys on the client side are ${keys.join(', ')} and ${lastKey}. See https://nuxt.com/docs/guide/going-further/runtime-config for more information.`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Reflect.get(target, p, receiver)
|
return Reflect.get(target, p, receiver)
|
||||||
},
|
},
|
||||||
|
@ -136,6 +136,7 @@ function createGranularWatcher () {
|
|||||||
console.timeEnd('[nuxt] builder:chokidar:watch')
|
console.timeEnd('[nuxt] builder:chokidar:watch')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
nuxt.hook('close', () => watcher?.close())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
const modules = await resolveNuxtModule(rootDirWithSlash,
|
const modules = await resolveNuxtModule(rootDirWithSlash,
|
||||||
nuxt.options._installedModules
|
nuxt.options._installedModules
|
||||||
.filter(m => m.entryPath)
|
.filter(m => m.entryPath)
|
||||||
.map(m => m.entryPath),
|
.map(m => m.entryPath!),
|
||||||
)
|
)
|
||||||
|
|
||||||
const nitroConfig: NitroConfig = defu(nuxt.options.nitro, {
|
const nitroConfig: NitroConfig = defu(nuxt.options.nitro, {
|
||||||
|
@ -4,7 +4,7 @@ import ignore from 'ignore'
|
|||||||
import type { LoadNuxtOptions } from '@nuxt/kit'
|
import type { LoadNuxtOptions } from '@nuxt/kit'
|
||||||
import { addBuildPlugin, addComponent, addPlugin, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit'
|
import { addBuildPlugin, addComponent, addPlugin, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit'
|
||||||
import { resolvePath as _resolvePath } from 'mlly'
|
import { resolvePath as _resolvePath } from 'mlly'
|
||||||
import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions, RuntimeConfig } from 'nuxt/schema'
|
import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema'
|
||||||
import type { PackageJson } from 'pkg-types'
|
import type { PackageJson } from 'pkg-types'
|
||||||
import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
|
import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
|
||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
@ -49,7 +49,10 @@ export function createNuxt (options: NuxtOptions): Nuxt {
|
|||||||
addHooks: hooks.addHooks,
|
addHooks: hooks.addHooks,
|
||||||
hook: hooks.hook,
|
hook: hooks.hook,
|
||||||
ready: () => initNuxt(nuxt),
|
ready: () => initNuxt(nuxt),
|
||||||
close: () => Promise.resolve(hooks.callHook('close', nuxt)),
|
close: async () => {
|
||||||
|
await hooks.callHook('close', nuxt)
|
||||||
|
hooks.removeAllHooks()
|
||||||
|
},
|
||||||
vfs: {},
|
vfs: {},
|
||||||
apps: {},
|
apps: {},
|
||||||
}
|
}
|
||||||
@ -125,6 +128,7 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
// Add nuxt types
|
// Add nuxt types
|
||||||
nuxt.hook('prepare:types', (opts) => {
|
nuxt.hook('prepare:types', (opts) => {
|
||||||
opts.references.push({ types: 'nuxt' })
|
opts.references.push({ types: 'nuxt' })
|
||||||
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/app-defaults.d.ts') })
|
||||||
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/plugins.d.ts') })
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/plugins.d.ts') })
|
||||||
// Add vue shim
|
// Add vue shim
|
||||||
if (nuxt.options.typescript.shim) {
|
if (nuxt.options.typescript.shim) {
|
||||||
@ -641,8 +645,22 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
|||||||
options._modules.push('@nuxt/telemetry')
|
options._modules.push('@nuxt/telemetry')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we share runtime config between Nuxt and Nitro
|
// Ensure we share key config between Nuxt and Nitro
|
||||||
options.runtimeConfig = options.nitro.runtimeConfig as RuntimeConfig
|
createPortalProperties(options.nitro.runtimeConfig, options, ['nitro.runtimeConfig', 'runtimeConfig'])
|
||||||
|
createPortalProperties(options.nitro.routeRules, options, ['nitro.routeRules', 'routeRules'])
|
||||||
|
|
||||||
|
// prevent replacement of options.nitro
|
||||||
|
const nitroOptions = options.nitro
|
||||||
|
Object.defineProperties(options, {
|
||||||
|
nitro: {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: true,
|
||||||
|
get: () => nitroOptions,
|
||||||
|
set (value) {
|
||||||
|
Object.assign(nitroOptions, value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const nuxt = createNuxt(options)
|
const nuxt = createNuxt(options)
|
||||||
|
|
||||||
@ -680,7 +698,7 @@ const RESTART_RE = /^(?:app|error|app\.config)\.(?:js|ts|mjs|jsx|tsx|vue)$/i
|
|||||||
function deduplicateArray<T = unknown> (maybeArray: T): T {
|
function deduplicateArray<T = unknown> (maybeArray: T): T {
|
||||||
if (!Array.isArray(maybeArray)) { return maybeArray }
|
if (!Array.isArray(maybeArray)) { return maybeArray }
|
||||||
|
|
||||||
const fresh = []
|
const fresh: any[] = []
|
||||||
const hashes = new Set<string>()
|
const hashes = new Set<string>()
|
||||||
for (const item of maybeArray) {
|
for (const item of maybeArray) {
|
||||||
const _hash = hash(item)
|
const _hash = hash(item)
|
||||||
@ -691,3 +709,31 @@ function deduplicateArray<T = unknown> (maybeArray: T): T {
|
|||||||
}
|
}
|
||||||
return fresh as T
|
return fresh as T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createPortalProperties (sourceValue: any, options: NuxtOptions, paths: string[]) {
|
||||||
|
let sharedValue = sourceValue
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
const segments = path.split('.')
|
||||||
|
const key = segments.pop()!
|
||||||
|
let parent: Record<string, any> = options
|
||||||
|
|
||||||
|
while (segments.length) {
|
||||||
|
const key = segments.shift()!
|
||||||
|
parent = parent[key] || (parent[key] = {})
|
||||||
|
}
|
||||||
|
|
||||||
|
delete parent[key]
|
||||||
|
|
||||||
|
Object.defineProperties(parent, {
|
||||||
|
[key]: {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: true,
|
||||||
|
get: () => sharedValue,
|
||||||
|
set (value) {
|
||||||
|
sharedValue = value
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -165,11 +165,7 @@ const getSPARenderer = lazyCachedFunction(async () => {
|
|||||||
const config = useRuntimeConfig(ssrContext.event)
|
const config = useRuntimeConfig(ssrContext.event)
|
||||||
ssrContext.modules = ssrContext.modules || new Set<string>()
|
ssrContext.modules = ssrContext.modules || new Set<string>()
|
||||||
ssrContext!.payload = {
|
ssrContext!.payload = {
|
||||||
_errors: {},
|
|
||||||
serverRendered: false,
|
serverRendered: false,
|
||||||
data: {},
|
|
||||||
state: {},
|
|
||||||
once: new Set<string>(),
|
|
||||||
}
|
}
|
||||||
ssrContext.config = {
|
ssrContext.config = {
|
||||||
public: config.public,
|
public: config.public,
|
||||||
@ -404,7 +400,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
// 2. Styles
|
// 2. Styles
|
||||||
head.push({ style: inlinedStyles })
|
head.push({ style: inlinedStyles })
|
||||||
if (!isRenderingIsland || import.meta.dev) {
|
if (!isRenderingIsland || import.meta.dev) {
|
||||||
const link = []
|
const link: Link[] = []
|
||||||
for (const style in styles) {
|
for (const style in styles) {
|
||||||
const resource = styles[style]
|
const resource = styles[style]
|
||||||
// Do not add links to resources that are inlined (vite v5+)
|
// Do not add links to resources that are inlined (vite v5+)
|
||||||
|
@ -7,7 +7,7 @@ import escapeRE from 'escape-string-regexp'
|
|||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { camelCase } from 'scule'
|
import { camelCase } from 'scule'
|
||||||
import { filename } from 'pathe/utils'
|
import { filename } from 'pathe/utils'
|
||||||
import type { NuxtTemplate } from 'nuxt/schema'
|
import type { NuxtTemplate, NuxtTypeTemplate } from 'nuxt/schema'
|
||||||
|
|
||||||
import { annotatePlugins, checkForCircularDependencies } from './app'
|
import { annotatePlugins, checkForCircularDependencies } from './app'
|
||||||
|
|
||||||
@ -96,6 +96,20 @@ export const serverPluginTemplate: NuxtTemplate = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const appDefaults: NuxtTypeTemplate = {
|
||||||
|
filename: 'types/app-defaults.d.ts',
|
||||||
|
getContents: (ctx) => {
|
||||||
|
const isV4 = ctx.nuxt.options.future.compatibilityVersion === 4
|
||||||
|
return `
|
||||||
|
declare module '#app/defaults' {
|
||||||
|
type DefaultAsyncDataErrorValue = ${isV4 ? 'undefined' : 'null'}
|
||||||
|
type DefaultAsyncDataValue = ${isV4 ? 'undefined' : 'null'}
|
||||||
|
type DefaultErrorValue = ${isV4 ? 'undefined' : 'null'}
|
||||||
|
type DedupeOption = ${isV4 ? '\'cancel\' | \'defer\'' : 'boolean | \'cancel\' | \'defer\''}
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export const pluginsDeclaration: NuxtTemplate = {
|
export const pluginsDeclaration: NuxtTemplate = {
|
||||||
filename: 'types/plugins.d.ts',
|
filename: 'types/plugins.d.ts',
|
||||||
getContents: async (ctx) => {
|
getContents: async (ctx) => {
|
||||||
@ -112,8 +126,6 @@ export const pluginsDeclaration: NuxtTemplate = {
|
|||||||
|
|
||||||
const pluginsName = (await annotatePlugins(ctx.nuxt, ctx.app.plugins)).filter(p => p.name).map(p => `'${p.name}'`)
|
const pluginsName = (await annotatePlugins(ctx.nuxt, ctx.app.plugins)).filter(p => p.name).map(p => `'${p.name}'`)
|
||||||
|
|
||||||
const isV4 = ctx.nuxt.options.future.compatibilityVersion === 4
|
|
||||||
|
|
||||||
return `// Generated by Nuxt'
|
return `// Generated by Nuxt'
|
||||||
import type { Plugin } from '#app'
|
import type { Plugin } from '#app'
|
||||||
|
|
||||||
@ -132,13 +144,6 @@ declare module '#app' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '#app/defaults' {
|
|
||||||
type DefaultAsyncDataErrorValue = ${isV4 ? 'undefined' : 'null'}
|
|
||||||
type DefaultAsyncDataValue = ${isV4 ? 'undefined' : 'null'}
|
|
||||||
type DefaultErrorValue = ${isV4 ? 'undefined' : 'null'}
|
|
||||||
type DedupeOption = ${isV4 ? '\'cancel\' | \'defer\'' : 'boolean | \'cancel\' | \'defer\''}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
interface ComponentCustomProperties extends NuxtAppInjections { }
|
interface ComponentCustomProperties extends NuxtAppInjections { }
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,10 @@ const granularAppPresets: InlinePreset[] = [
|
|||||||
imports: ['useId'],
|
imports: ['useId'],
|
||||||
from: '#app/composables/id',
|
from: '#app/composables/id',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
imports: ['useRouteAnnouncer'],
|
||||||
|
from: '#app/composables/route-announcer',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const scriptsStubsPreset = {
|
export const scriptsStubsPreset = {
|
||||||
@ -119,19 +123,21 @@ export const scriptsStubsPreset = {
|
|||||||
'useScript',
|
'useScript',
|
||||||
'useScriptGoogleAnalytics',
|
'useScriptGoogleAnalytics',
|
||||||
'useScriptPlausibleAnalytics',
|
'useScriptPlausibleAnalytics',
|
||||||
|
'useScriptClarity',
|
||||||
'useScriptCloudflareWebAnalytics',
|
'useScriptCloudflareWebAnalytics',
|
||||||
'useScriptFathomAnalytics',
|
'useScriptFathomAnalytics',
|
||||||
'useScriptMatomoAnalytics',
|
'useScriptMatomoAnalytics',
|
||||||
'useScriptGoogleTagManager',
|
'useScriptGoogleTagManager',
|
||||||
|
'useScriptGoogleAdsense',
|
||||||
'useScriptSegment',
|
'useScriptSegment',
|
||||||
'useScriptFacebookPixel',
|
'useScriptMetaPixel',
|
||||||
'useScriptXPixel',
|
'useScriptXPixel',
|
||||||
'useScriptIntercom',
|
'useScriptIntercom',
|
||||||
'useScriptHotjar',
|
'useScriptHotjar',
|
||||||
'useScriptStripe',
|
'useScriptStripe',
|
||||||
'useScriptLemonSqueezy',
|
'useScriptLemonSqueezy',
|
||||||
'useScriptVimeoPlayer',
|
'useScriptVimeoPlayer',
|
||||||
'useScriptYouTubeIframe',
|
'useScriptYouTubePlayer',
|
||||||
'useScriptGoogleMaps',
|
'useScriptGoogleMaps',
|
||||||
'useScriptNpm',
|
'useScriptNpm',
|
||||||
],
|
],
|
||||||
|
@ -275,6 +275,20 @@ export default defineNuxtModule({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO: inject routes in `200.html` in next nitro upgrade (2.9.7+) via https://github.com/unjs/nitro/pull/2517
|
||||||
|
if (!nuxt.options.dev && !nuxt.options._prepare) {
|
||||||
|
nuxt.hook('app:templatesGenerated', (app) => {
|
||||||
|
const nitro = useNitro()
|
||||||
|
if (nitro.options.prerender.crawlLinks) {
|
||||||
|
for (const page of app.pages!) {
|
||||||
|
if (page.path && !page.path.includes(':')) {
|
||||||
|
nitro.options.prerender.routes.push(page.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
nuxt.hook('imports:extend', (imports) => {
|
nuxt.hook('imports:extend', (imports) => {
|
||||||
imports.push(
|
imports.push(
|
||||||
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },
|
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },
|
||||||
|
@ -18,11 +18,12 @@ export async function extractRouteRules (code: string): Promise<NitroRouteConfig
|
|||||||
}
|
}
|
||||||
if (!ROUTE_RULE_RE.test(code)) { return null }
|
if (!ROUTE_RULE_RE.test(code)) { return null }
|
||||||
|
|
||||||
code = extractScriptContent(code) || code
|
const script = extractScriptContent(code)
|
||||||
|
code = script?.code || code
|
||||||
|
|
||||||
let rule: NitroRouteConfig | null = null
|
let rule: NitroRouteConfig | null = null
|
||||||
|
|
||||||
const js = await transform(code, { loader: 'ts' })
|
const js = await transform(code, { loader: script?.loader || 'ts' })
|
||||||
walk(parse(js.code, {
|
walk(parse(js.code, {
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
|
@ -9,6 +9,7 @@ import { filename } from 'pathe/utils'
|
|||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { transform } from 'esbuild'
|
import { transform } from 'esbuild'
|
||||||
import { parse } from 'acorn'
|
import { parse } from 'acorn'
|
||||||
|
import { walk } from 'estree-walker'
|
||||||
import type { CallExpression, ExpressionStatement, ObjectExpression, Program, Property } from 'estree'
|
import type { CallExpression, ExpressionStatement, ObjectExpression, Program, Property } from 'estree'
|
||||||
import type { NuxtPage } from 'nuxt/schema'
|
import type { NuxtPage } from 'nuxt/schema'
|
||||||
|
|
||||||
@ -139,11 +140,12 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
|
|||||||
return prepareRoutes(routes)
|
return prepareRoutes(routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, augmentedPages = new Set<NuxtPage>()) {
|
export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, augmentedPages = new Set<string>()) {
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
if (!augmentedPages.has(route) && route.file) {
|
if (route.file && !augmentedPages.has(route.file)) {
|
||||||
const fileContent = route.file in vfs ? vfs[route.file] : fs.readFileSync(await resolvePath(route.file), 'utf-8')
|
const fileContent = route.file in vfs ? vfs[route.file] : fs.readFileSync(await resolvePath(route.file), 'utf-8')
|
||||||
Object.assign(route, await getRouteMeta(fileContent, route.file))
|
Object.assign(route, await getRouteMeta(fileContent, route.file))
|
||||||
|
augmentedPages.add(route.file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.children && route.children.length > 0) {
|
if (route.children && route.children.length > 0) {
|
||||||
@ -153,12 +155,15 @@ export async function augmentPages (routes: NuxtPage[], vfs: Record<string, stri
|
|||||||
return augmentedPages
|
return augmentedPages
|
||||||
}
|
}
|
||||||
|
|
||||||
const SFC_SCRIPT_RE = /<script[^>]*>([\s\S]*?)<\/script[^>]*>/i
|
const SFC_SCRIPT_RE = /<script(?<attrs>[^>]*)>(?<content>[\s\S]*?)<\/script[^>]*>/i
|
||||||
export function extractScriptContent (html: string) {
|
export function extractScriptContent (html: string) {
|
||||||
const match = html.match(SFC_SCRIPT_RE)
|
const groups = html.match(SFC_SCRIPT_RE)?.groups || {}
|
||||||
|
|
||||||
if (match && match[1]) {
|
if (groups.content) {
|
||||||
return match[1].trim()
|
return {
|
||||||
|
loader: groups.attrs.includes('tsx') ? 'tsx' : 'ts',
|
||||||
|
code: groups.content.trim(),
|
||||||
|
} as const
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@ -169,7 +174,7 @@ const DYNAMIC_META_KEY = '__nuxt_dynamic_meta_key' as const
|
|||||||
|
|
||||||
const pageContentsCache: Record<string, string> = {}
|
const pageContentsCache: Record<string, string> = {}
|
||||||
const metaCache: Record<string, Partial<Record<keyof NuxtPage, any>>> = {}
|
const metaCache: Record<string, Partial<Record<keyof NuxtPage, any>>> = {}
|
||||||
async function getRouteMeta (contents: string, absolutePath: string): Promise<Partial<Record<keyof NuxtPage, any>>> {
|
export async function getRouteMeta (contents: string, absolutePath: string): Promise<Partial<Record<keyof NuxtPage, any>>> {
|
||||||
// set/update pageContentsCache, invalidate metaCache on cache mismatch
|
// set/update pageContentsCache, invalidate metaCache on cache mismatch
|
||||||
if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) {
|
if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) {
|
||||||
pageContentsCache[absolutePath] = contents
|
pageContentsCache[absolutePath] = contents
|
||||||
@ -184,81 +189,88 @@ async function getRouteMeta (contents: string, absolutePath: string): Promise<Pa
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PAGE_META_RE.test(script)) {
|
if (!PAGE_META_RE.test(script.code)) {
|
||||||
metaCache[absolutePath] = {}
|
metaCache[absolutePath] = {}
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const js = await transform(script, { loader: 'ts' })
|
const js = await transform(script.code, { loader: script.loader })
|
||||||
const ast = parse(js.code, {
|
const ast = parse(js.code, {
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
ranges: true,
|
ranges: true,
|
||||||
}) as unknown as Program
|
}) as unknown as Program
|
||||||
const pageMetaAST = ast.body.find(node => node.type === 'ExpressionStatement' && node.expression.type === 'CallExpression' && node.expression.callee.type === 'Identifier' && node.expression.callee.name === 'definePageMeta')
|
|
||||||
if (!pageMetaAST) {
|
|
||||||
metaCache[absolutePath] = {}
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageMetaArgument = ((pageMetaAST as ExpressionStatement).expression as CallExpression).arguments[0] as ObjectExpression
|
|
||||||
const extractedMeta = {} as Partial<Record<keyof NuxtPage, any>>
|
const extractedMeta = {} as Partial<Record<keyof NuxtPage, any>>
|
||||||
const extractionKeys = ['name', 'path', 'alias', 'redirect'] as const
|
const extractionKeys = ['name', 'path', 'alias', 'redirect'] as const
|
||||||
const dynamicProperties = new Set<keyof NuxtPage>()
|
const dynamicProperties = new Set<keyof NuxtPage>()
|
||||||
|
|
||||||
for (const key of extractionKeys) {
|
let foundMeta = false
|
||||||
const property = pageMetaArgument.properties.find(property => property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === key) as Property
|
|
||||||
if (!property) { continue }
|
|
||||||
|
|
||||||
if (property.value.type === 'ObjectExpression') {
|
walk(ast, {
|
||||||
const valueString = js.code.slice(property.value.range![0], property.value.range![1])
|
enter (node) {
|
||||||
try {
|
if (foundMeta) { return }
|
||||||
extractedMeta[key] = JSON.parse(runInNewContext(`JSON.stringify(${valueString})`, {}))
|
|
||||||
} catch {
|
|
||||||
console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not JSON-serializable (reading \`${absolutePath}\`).`)
|
|
||||||
dynamicProperties.add(key)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (property.value.type === 'ArrayExpression') {
|
if (node.type !== 'ExpressionStatement' || node.expression.type !== 'CallExpression' || node.expression.callee.type !== 'Identifier' || node.expression.callee.name !== 'definePageMeta') { return }
|
||||||
const values = []
|
|
||||||
for (const element of property.value.elements) {
|
foundMeta = true
|
||||||
if (!element) {
|
const pageMetaArgument = ((node as ExpressionStatement).expression as CallExpression).arguments[0] as ObjectExpression
|
||||||
|
|
||||||
|
for (const key of extractionKeys) {
|
||||||
|
const property = pageMetaArgument.properties.find(property => property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === key) as Property
|
||||||
|
if (!property) { continue }
|
||||||
|
|
||||||
|
if (property.value.type === 'ObjectExpression') {
|
||||||
|
const valueString = js.code.slice(property.value.range![0], property.value.range![1])
|
||||||
|
try {
|
||||||
|
extractedMeta[key] = JSON.parse(runInNewContext(`JSON.stringify(${valueString})`, {}))
|
||||||
|
} catch {
|
||||||
|
console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not JSON-serializable (reading \`${absolutePath}\`).`)
|
||||||
|
dynamicProperties.add(key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.value.type === 'ArrayExpression') {
|
||||||
|
const values: string[] = []
|
||||||
|
for (const element of property.value.elements) {
|
||||||
|
if (!element) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (element.type !== 'Literal' || typeof element.value !== 'string') {
|
||||||
|
console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not an array of string literals (reading \`${absolutePath}\`).`)
|
||||||
|
dynamicProperties.add(key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
values.push(element.value)
|
||||||
|
}
|
||||||
|
extractedMeta[key] = values
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (element.type !== 'Literal' || typeof element.value !== 'string') {
|
|
||||||
console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not an array of string literals (reading \`${absolutePath}\`).`)
|
if (property.value.type !== 'Literal' || typeof property.value.value !== 'string') {
|
||||||
|
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)
|
dynamicProperties.add(key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
values.push(element.value)
|
extractedMeta[key] = property.value.value
|
||||||
}
|
}
|
||||||
extractedMeta[key] = values
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (property.value.type !== 'Literal' || typeof property.value.value !== 'string') {
|
const extraneousMetaKeys = pageMetaArgument.properties
|
||||||
console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not a string literal or array of string literals (reading \`${absolutePath}\`).`)
|
.filter(property => property.type === 'Property' && property.key.type === 'Identifier' && !(extractionKeys as unknown as string[]).includes(property.key.name))
|
||||||
dynamicProperties.add(key)
|
// @ts-expect-error inferred types have been filtered out
|
||||||
continue
|
.map(property => property.key.name)
|
||||||
}
|
|
||||||
extractedMeta[key] = property.value.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const extraneousMetaKeys = pageMetaArgument.properties
|
if (extraneousMetaKeys.length) {
|
||||||
.filter(property => property.type === 'Property' && property.key.type === 'Identifier' && !(extractionKeys as unknown as string[]).includes(property.key.name))
|
dynamicProperties.add('meta')
|
||||||
// @ts-expect-error inferred types have been filtered out
|
}
|
||||||
.map(property => property.key.name)
|
|
||||||
|
|
||||||
if (extraneousMetaKeys.length) {
|
if (dynamicProperties.size) {
|
||||||
dynamicProperties.add('meta')
|
extractedMeta.meta ??= {}
|
||||||
}
|
extractedMeta.meta[DYNAMIC_META_KEY] = dynamicProperties
|
||||||
|
}
|
||||||
if (dynamicProperties.size) {
|
},
|
||||||
extractedMeta.meta ??= {}
|
})
|
||||||
extractedMeta.meta[DYNAMIC_META_KEY] = dynamicProperties
|
|
||||||
}
|
|
||||||
|
|
||||||
metaCache[absolutePath] = extractedMeta
|
metaCache[absolutePath] = extractedMeta
|
||||||
return extractedMeta
|
return extractedMeta
|
||||||
@ -501,19 +513,20 @@ async function createClientPage(loader) {
|
|||||||
}`)
|
}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.children != null) {
|
if (route.children) {
|
||||||
metaRoute.children = route.children
|
metaRoute.children = route.children
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overrideMeta) {
|
if (route.meta) {
|
||||||
metaRoute.name = `${metaImportName}?.name`
|
metaRoute.meta = `{ ...(${metaImportName} || {}), ...${route.meta} }`
|
||||||
metaRoute.path = `${metaImportName}?.path ?? ''`
|
}
|
||||||
|
|
||||||
|
if (overrideMeta) {
|
||||||
// skip and retain fallback if marked dynamic
|
// skip and retain fallback if marked dynamic
|
||||||
// set to extracted value or fallback if none extracted
|
// set to extracted value or fallback if none extracted
|
||||||
for (const key of ['name', 'path'] satisfies NormalizedRouteKeys) {
|
for (const key of ['name', 'path'] satisfies NormalizedRouteKeys) {
|
||||||
if (markedDynamic.has(key)) { continue }
|
if (markedDynamic.has(key)) { continue }
|
||||||
metaRoute[key] = route[key] ?? metaRoute[key]
|
metaRoute[key] = route[key] ?? `${metaImportName}?.${key}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// set to extracted value or delete if none extracted
|
// set to extracted value or delete if none extracted
|
||||||
@ -528,10 +541,6 @@ async function createClientPage(loader) {
|
|||||||
metaRoute[key] = route[key]
|
metaRoute[key] = route[key]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (route.meta != null) {
|
|
||||||
metaRoute.meta = `{ ...(${metaImportName} || {}), ...${route.meta} }`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.alias != null) {
|
if (route.alias != null) {
|
||||||
metaRoute.alias = `${route.alias}.concat(${metaImportName}?.alias || [])`
|
metaRoute.alias = `${route.alias}.concat(${metaImportName}?.alias || [])`
|
||||||
}
|
}
|
||||||
|
@ -303,7 +303,7 @@
|
|||||||
"alias": "mockMeta?.alias || []",
|
"alias": "mockMeta?.alias || []",
|
||||||
"component": "() => import("pages/index.vue").then(m => m.default || m)",
|
"component": "() => import("pages/index.vue").then(m => m.default || m)",
|
||||||
"meta": "mockMeta || {}",
|
"meta": "mockMeta || {}",
|
||||||
"name": "mockMeta?.name",
|
"name": "mockMeta?.name ?? "index"",
|
||||||
"path": ""/"",
|
"path": ""/"",
|
||||||
"redirect": "mockMeta?.redirect",
|
"redirect": "mockMeta?.redirect",
|
||||||
},
|
},
|
||||||
|
@ -6,8 +6,9 @@ import * as VueFunctions from 'vue'
|
|||||||
import type { Import } from 'unimport'
|
import type { Import } from 'unimport'
|
||||||
import { createUnimport } from 'unimport'
|
import { createUnimport } from 'unimport'
|
||||||
import type { Plugin } from 'vite'
|
import type { Plugin } from 'vite'
|
||||||
|
import { registry as scriptRegistry } from '@nuxt/scripts/registry'
|
||||||
import { TransformPlugin } from '../src/imports/transform'
|
import { TransformPlugin } from '../src/imports/transform'
|
||||||
import { defaultPresets } from '../src/imports/presets'
|
import { defaultPresets, scriptsStubsPreset } from '../src/imports/presets'
|
||||||
|
|
||||||
describe('imports:transform', () => {
|
describe('imports:transform', () => {
|
||||||
const imports: Import[] = [
|
const imports: Import[] = [
|
||||||
@ -193,3 +194,24 @@ describe('imports:vue', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('imports:nuxt/scripts', () => {
|
||||||
|
const scripts = scriptRegistry().map(s => s.import?.name).filter(Boolean)
|
||||||
|
const globalScripts = new Set([
|
||||||
|
'useScript',
|
||||||
|
'useAnalyticsPageEvent',
|
||||||
|
'useElementScriptTrigger',
|
||||||
|
'useConsentScriptTrigger',
|
||||||
|
// registered separately
|
||||||
|
'useScriptGoogleTagManager',
|
||||||
|
'useScriptGoogleAnalytics',
|
||||||
|
])
|
||||||
|
it.each(scriptsStubsPreset.imports)(`should register %s from @nuxt/scripts`, (name) => {
|
||||||
|
if (globalScripts.has(name)) { return }
|
||||||
|
|
||||||
|
expect(scripts).toContain(name)
|
||||||
|
})
|
||||||
|
it.each(scripts)(`should register %s from @nuxt/scripts`, (name) => {
|
||||||
|
expect(scriptsStubsPreset.imports).toContain(name)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
import type { RouteLocation, RouteLocationRaw } from 'vue-router'
|
import type { RouteLocation } from 'vue-router'
|
||||||
import type { NuxtLinkOptions, NuxtLinkProps } from '../src/app/components/nuxt-link'
|
import type { NuxtLinkOptions, NuxtLinkProps } from '../src/app/components/nuxt-link'
|
||||||
import { defineNuxtLink } from '../src/app/components/nuxt-link'
|
import { defineNuxtLink } from '../src/app/components/nuxt-link'
|
||||||
import { useRuntimeConfig } from '../src/app/nuxt'
|
import { useRuntimeConfig } from '../src/app/nuxt'
|
||||||
@ -99,7 +99,11 @@ describe('nuxt-link:isExternal', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('returns `false` when `to` is a route location object', () => {
|
it('returns `false` when `to` is a route location object', () => {
|
||||||
expect(nuxtLink({ to: { to: '/to' } as RouteLocationRaw }).type).toBe(INTERNAL)
|
expect(nuxtLink({ to: { path: '/to' } }).type).toBe(INTERNAL)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns `true` when `to` has a `target`', () => {
|
||||||
|
expect(nuxtLink({ to: { path: '/to' }, target: '_blank' }).type).toBe(EXTERNAL)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('honors `external` prop', () => {
|
it('honors `external` prop', () => {
|
||||||
@ -122,7 +126,12 @@ describe('nuxt-link:propsOrAttributes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('resolves route location object', () => {
|
it('resolves route location object', () => {
|
||||||
expect(nuxtLink({ to: { to: '/to' } as RouteLocationRaw, external: true }).props.href).toBe('/to')
|
expect(nuxtLink({ to: { path: '/to' }, external: true }).props.href).toBe('/to')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('applies trailing slash behaviour', () => {
|
||||||
|
expect(nuxtLink({ to: { path: '/to' }, external: true }, { trailingSlash: 'append' }).props.href).toBe('/to/')
|
||||||
|
expect(nuxtLink({ to: '/to', external: true }, { trailingSlash: 'append' }).props.href).toBe('/to/')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -167,6 +176,8 @@ describe('nuxt-link:propsOrAttributes', () => {
|
|||||||
}, () => {
|
}, () => {
|
||||||
expect(nuxtLink({ to: 'http://nuxtjs.org/app/about', target: '_blank' }).props.href).toBe('http://nuxtjs.org/app/about')
|
expect(nuxtLink({ to: 'http://nuxtjs.org/app/about', target: '_blank' }).props.href).toBe('http://nuxtjs.org/app/about')
|
||||||
expect(nuxtLink({ to: '//nuxtjs.org/app/about', target: '_blank' }).props.href).toBe('//nuxtjs.org/app/about')
|
expect(nuxtLink({ to: '//nuxtjs.org/app/about', target: '_blank' }).props.href).toBe('//nuxtjs.org/app/about')
|
||||||
|
expect(nuxtLink({ to: { path: '/' }, external: true }).props.href).toBe('/')
|
||||||
|
expect(nuxtLink({ to: '/', external: true }).props.href).toBe('/')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -209,7 +220,7 @@ describe('nuxt-link:propsOrAttributes', () => {
|
|||||||
describe('to', () => {
|
describe('to', () => {
|
||||||
it('forwards `to` prop', () => {
|
it('forwards `to` prop', () => {
|
||||||
expect(nuxtLink({ to: '/to' }).props.to).toBe('/to')
|
expect(nuxtLink({ to: '/to' }).props.to).toBe('/to')
|
||||||
expect(nuxtLink({ to: { to: '/to' } as RouteLocationRaw }).props.to).toEqual({ to: '/to' })
|
expect(nuxtLink({ to: { path: '/to' } }).props.to).toEqual({ path: '/to' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
148
packages/nuxt/test/page-metadata.test.ts
Normal file
148
packages/nuxt/test/page-metadata.test.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { getRouteMeta, normalizeRoutes } from '../src/pages/utils'
|
||||||
|
import type { NuxtPage } from '../schema'
|
||||||
|
|
||||||
|
const filePath = '/app/pages/index.vue'
|
||||||
|
|
||||||
|
describe('page metadata', () => {
|
||||||
|
it('should not extract metadata from empty files', async () => {
|
||||||
|
expect(await getRouteMeta('', filePath)).toEqual({})
|
||||||
|
expect(await getRouteMeta('<template><div>Hi</div></template>', filePath)).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should use and invalidate cache', async () => {
|
||||||
|
const fileContents = `<script setup>definePageMeta({ foo: 'bar' })</script>`
|
||||||
|
const meta = await getRouteMeta(fileContents, filePath)
|
||||||
|
expect(meta === await getRouteMeta(fileContents, filePath)).toBeTruthy()
|
||||||
|
expect(meta === await getRouteMeta(fileContents, '/app/pages/other.vue')).toBeFalsy()
|
||||||
|
expect(meta === await getRouteMeta('<template><div>Hi</div></template>' + fileContents, filePath)).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should extract serialisable metadata', async () => {
|
||||||
|
const meta = await getRouteMeta(`
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
name: 'some-custom-name',
|
||||||
|
path: '/some-custom-path',
|
||||||
|
validate: () => true,
|
||||||
|
middleware: [
|
||||||
|
function () {},
|
||||||
|
],
|
||||||
|
otherValue: {
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
`, filePath)
|
||||||
|
|
||||||
|
expect(meta).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"__nuxt_dynamic_meta_key": Set {
|
||||||
|
"meta",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"name": "some-custom-name",
|
||||||
|
"path": "/some-custom-path",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should extract serialisable metadata in options api', async () => {
|
||||||
|
const meta = await getRouteMeta(`
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
definePageMeta({
|
||||||
|
name: 'some-custom-name',
|
||||||
|
path: '/some-custom-path',
|
||||||
|
middleware: (from, to) => console.warn('middleware'),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
`, filePath)
|
||||||
|
|
||||||
|
expect(meta).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"__nuxt_dynamic_meta_key": Set {
|
||||||
|
"meta",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"name": "some-custom-name",
|
||||||
|
"path": "/some-custom-path",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('normalizeRoutes', () => {
|
||||||
|
it('should produce valid route objects when used with extracted meta', async () => {
|
||||||
|
const page: NuxtPage = { path: '/', file: filePath }
|
||||||
|
Object.assign(page, await getRouteMeta(`
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
name: 'some-custom-name',
|
||||||
|
path: ref('/some-custom-path'), /* dynamic */
|
||||||
|
validate: () => true,
|
||||||
|
redirect: '/',
|
||||||
|
middleware: [
|
||||||
|
function () {},
|
||||||
|
],
|
||||||
|
otherValue: {
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
`, filePath))
|
||||||
|
|
||||||
|
page.meta ||= {}
|
||||||
|
page.meta.layout = 'test'
|
||||||
|
page.meta.foo = 'bar'
|
||||||
|
|
||||||
|
const { routes, imports } = normalizeRoutes([page], new Set(), true)
|
||||||
|
expect({ routes, imports }).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"imports": Set {
|
||||||
|
"import { default as indexN6pT4Un8hYMeta } from "/app/pages/index.vue?macro=true";",
|
||||||
|
},
|
||||||
|
"routes": "[
|
||||||
|
{
|
||||||
|
name: "some-custom-name",
|
||||||
|
path: indexN6pT4Un8hYMeta?.path ?? "/",
|
||||||
|
meta: { ...(indexN6pT4Un8hYMeta || {}), ...{"layout":"test","foo":"bar"} },
|
||||||
|
redirect: "/",
|
||||||
|
component: () => import("/app/pages/index.vue").then(m => m.default || m)
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should produce valid route objects when used without extracted meta', () => {
|
||||||
|
const page: NuxtPage = { path: '/', file: filePath }
|
||||||
|
page.meta ||= {}
|
||||||
|
page.meta.layout = 'test'
|
||||||
|
page.meta.foo = 'bar'
|
||||||
|
|
||||||
|
const { routes, imports } = normalizeRoutes([page], new Set())
|
||||||
|
expect({ routes, imports }).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"imports": Set {
|
||||||
|
"import { default as indexN6pT4Un8hYMeta } from "/app/pages/index.vue?macro=true";",
|
||||||
|
},
|
||||||
|
"routes": "[
|
||||||
|
{
|
||||||
|
name: indexN6pT4Un8hYMeta?.name ?? undefined,
|
||||||
|
path: indexN6pT4Un8hYMeta?.path ?? "/",
|
||||||
|
meta: { ...(indexN6pT4Un8hYMeta || {}), ...{"layout":"test","foo":"bar"} },
|
||||||
|
alias: indexN6pT4Un8hYMeta?.alias || [],
|
||||||
|
redirect: indexN6pT4Un8hYMeta?.redirect,
|
||||||
|
component: () => import("/app/pages/index.vue").then(m => m.default || m)
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxt/schema",
|
"name": "@nuxt/schema",
|
||||||
"version": "3.11.2",
|
"version": "3.12.1",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||||
@ -39,13 +39,13 @@
|
|||||||
"@types/file-loader": "5.0.4",
|
"@types/file-loader": "5.0.4",
|
||||||
"@types/pug": "2.0.10",
|
"@types/pug": "2.0.10",
|
||||||
"@types/sass-loader": "8.0.8",
|
"@types/sass-loader": "8.0.8",
|
||||||
"@unhead/schema": "1.9.12",
|
"@unhead/schema": "1.9.13",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
"@vitejs/plugin-vue-jsx": "4.0.0",
|
"@vitejs/plugin-vue-jsx": "4.0.0",
|
||||||
"@vue/compiler-core": "3.4.27",
|
"@vue/compiler-core": "3.4.27",
|
||||||
"@vue/compiler-sfc": "3.4.27",
|
"@vue/compiler-sfc": "3.4.27",
|
||||||
"@vue/language-core": "2.0.21",
|
"@vue/language-core": "2.0.21",
|
||||||
"c12": "1.10.0",
|
"c12": "1.11.1",
|
||||||
"esbuild-loader": "4.1.0",
|
"esbuild-loader": "4.1.0",
|
||||||
"h3": "1.11.1",
|
"h3": "1.11.1",
|
||||||
"ignore": "5.3.1",
|
"ignore": "5.3.1",
|
||||||
@ -54,16 +54,16 @@
|
|||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"unctx": "2.3.1",
|
"unctx": "2.3.1",
|
||||||
"unenv": "1.9.0",
|
"unenv": "1.9.0",
|
||||||
"vite": "5.2.13",
|
"vite": "5.3.0",
|
||||||
"vue": "3.4.27",
|
"vue": "3.4.27",
|
||||||
"vue-bundle-renderer": "2.1.0",
|
"vue-bundle-renderer": "2.1.0",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
"vue-router": "4.3.3",
|
"vue-router": "4.3.3",
|
||||||
"webpack": "5.91.0",
|
"webpack": "5.92.0",
|
||||||
"webpack-dev-middleware": "7.2.1"
|
"webpack-dev-middleware": "7.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"compatx": "^0.1.3",
|
"compatx": "^0.1.8",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"hookable": "^5.5.3",
|
"hookable": "^5.5.3",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { existsSync } from 'node:fs'
|
import { existsSync } from 'node:fs'
|
||||||
|
import { readdir } from 'node:fs/promises'
|
||||||
import { defineUntypedSchema } from 'untyped'
|
import { defineUntypedSchema } from 'untyped'
|
||||||
import { join, relative, resolve } from 'pathe'
|
import { join, relative, resolve } from 'pathe'
|
||||||
import { isDebug, isDevelopment, isTest } from 'std-env'
|
import { isDebug, isDevelopment, isTest } from 'std-env'
|
||||||
@ -28,7 +29,7 @@ export default defineUntypedSchema({
|
|||||||
*
|
*
|
||||||
* We plan to improve the tooling around this feature in the future.
|
* We plan to improve the tooling around this feature in the future.
|
||||||
*
|
*
|
||||||
* @type {typeof import('compatx').DateString | Record<string, typeof import('compatx').DateString>}
|
* @type {typeof import('compatx').CompatibilityDateSpec}
|
||||||
*/
|
*/
|
||||||
compatibilityDate: undefined,
|
compatibilityDate: undefined,
|
||||||
|
|
||||||
@ -117,7 +118,16 @@ export default defineUntypedSchema({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const srcDir = resolve(rootDir, 'app')
|
const srcDir = resolve(rootDir, 'app')
|
||||||
if (!existsSync(srcDir)) {
|
const srcDirFiles = new Set<string>()
|
||||||
|
if (existsSync(srcDir)) {
|
||||||
|
const files = await readdir(srcDir).catch(() => [])
|
||||||
|
for (const file of files) {
|
||||||
|
if (file !== 'spa-loading-template.html' && !file.startsWith('router.options')) {
|
||||||
|
srcDirFiles.add(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (srcDirFiles.size === 0) {
|
||||||
for (const file of ['app.vue', 'App.vue']) {
|
for (const file of ['app.vue', 'App.vue']) {
|
||||||
if (existsSync(resolve(rootDir, file))) {
|
if (existsSync(resolve(rootDir, file))) {
|
||||||
return rootDir
|
return rootDir
|
||||||
|
@ -23,7 +23,10 @@ export default defineUntypedSchema({
|
|||||||
_nuxtConfigFiles: [],
|
_nuxtConfigFiles: [],
|
||||||
/** @private */
|
/** @private */
|
||||||
appDir: '',
|
appDir: '',
|
||||||
/** @private */
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array<{ meta: ModuleMeta; timings?: Record<string, number | undefined>; entryPath?: string }>}
|
||||||
|
*/
|
||||||
_installedModules: [],
|
_installedModules: [],
|
||||||
/** @private */
|
/** @private */
|
||||||
_modules: [],
|
_modules: [],
|
||||||
|
@ -141,7 +141,7 @@ export interface AppConfigInput extends CustomAppConfig {
|
|||||||
server?: never
|
server?: never
|
||||||
}
|
}
|
||||||
|
|
||||||
type Serializable<T> = T extends Function ? never : T extends Promise<infer U> ? Serializable<U> : T extends Record<string, any> ? { [K in keyof T]: Serializable<T[K]> } : T
|
type Serializable<T> = T extends Function ? never : T extends Promise<infer U> ? Serializable<U> : T extends string & {} ? T : T extends Record<string, any> ? { [K in keyof T]: Serializable<T[K]> } : T
|
||||||
|
|
||||||
export interface NuxtAppConfig {
|
export interface NuxtAppConfig {
|
||||||
head: Serializable<AppHeadMetaObject>
|
head: Serializable<AppHeadMetaObject>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/html-minifier": "4.0.5",
|
"@types/html-minifier": "4.0.5",
|
||||||
"@types/lodash-es": "4.17.12",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@unocss/reset": "0.60.4",
|
"@unocss/reset": "0.61.0",
|
||||||
"critters": "0.0.22",
|
"critters": "0.0.22",
|
||||||
"execa": "9.2.0",
|
"execa": "9.2.0",
|
||||||
"globby": "14.0.1",
|
"globby": "14.0.1",
|
||||||
@ -30,9 +30,9 @@
|
|||||||
"knitwork": "1.1.0",
|
"knitwork": "1.1.0",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"pathe": "1.1.2",
|
"pathe": "1.1.2",
|
||||||
"prettier": "3.3.1",
|
"prettier": "3.3.2",
|
||||||
"scule": "1.3.0",
|
"scule": "1.3.0",
|
||||||
"unocss": "0.60.4",
|
"unocss": "0.61.0",
|
||||||
"vite": "5.2.13"
|
"vite": "5.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxt/vite-builder",
|
"name": "@nuxt/vite-builder",
|
||||||
"version": "3.11.2",
|
"version": "3.12.1",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||||
@ -28,6 +28,7 @@
|
|||||||
"@types/clear": "0.1.4",
|
"@types/clear": "0.1.4",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.5",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
|
"rollup": "4.18.0",
|
||||||
"unbuild": "latest",
|
"unbuild": "latest",
|
||||||
"vue": "3.4.27"
|
"vue": "3.4.27"
|
||||||
},
|
},
|
||||||
@ -62,7 +63,7 @@
|
|||||||
"ufo": "^1.5.3",
|
"ufo": "^1.5.3",
|
||||||
"unenv": "^1.9.0",
|
"unenv": "^1.9.0",
|
||||||
"unplugin": "^1.10.1",
|
"unplugin": "^1.10.1",
|
||||||
"vite": "^5.2.13",
|
"vite": "^5.3.0",
|
||||||
"vite-node": "^1.6.0",
|
"vite-node": "^1.6.0",
|
||||||
"vite-plugin-checker": "^0.6.4",
|
"vite-plugin-checker": "^0.6.4",
|
||||||
"vue-bundle-renderer": "^2.1.0"
|
"vue-bundle-renderer": "^2.1.0"
|
||||||
|
@ -163,10 +163,11 @@ export async function buildClient (ctx: ViteBuildContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We want to respect users' own rollup output options
|
// We want to respect users' own rollup output options
|
||||||
|
const fileNames = withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[hash].js'))
|
||||||
clientConfig.build!.rollupOptions = defu(clientConfig.build!.rollupOptions!, {
|
clientConfig.build!.rollupOptions = defu(clientConfig.build!.rollupOptions!, {
|
||||||
output: {
|
output: {
|
||||||
chunkFileNames: ctx.nuxt.options.dev ? undefined : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[hash].js')),
|
chunkFileNames: ctx.nuxt.options.dev ? undefined : fileNames,
|
||||||
entryFileNames: ctx.nuxt.options.dev ? 'entry.js' : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[hash].js')),
|
entryFileNames: ctx.nuxt.options.dev ? 'entry.js' : fileNames,
|
||||||
} satisfies NonNullable<BuildOptions['rollupOptions']>['output'],
|
} satisfies NonNullable<BuildOptions['rollupOptions']>['output'],
|
||||||
}) as any
|
}) as any
|
||||||
|
|
||||||
@ -228,7 +229,13 @@ export async function buildClient (ctx: ViteBuildContext) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const viteMiddleware = defineEventHandler(async (event) => {
|
const viteMiddleware = defineEventHandler(async (event) => {
|
||||||
const viteRoutes = viteServer.middlewares.stack.map(m => m.route).filter(r => r.length > 1)
|
const viteRoutes: string[] = []
|
||||||
|
for (const viteRoute of viteServer.middlewares.stack) {
|
||||||
|
const m = viteRoute.route
|
||||||
|
if (m.length > 1) {
|
||||||
|
viteRoutes.push(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!event.path.startsWith(clientConfig.base!) && !viteRoutes.some(route => event.path.startsWith(route))) {
|
if (!event.path.startsWith(clientConfig.base!) && !viteRoutes.some(route => event.path.startsWith(route))) {
|
||||||
// @ts-expect-error _skip_transform is a private property
|
// @ts-expect-error _skip_transform is a private property
|
||||||
event.node.req._skip_transform = true
|
event.node.req._skip_transform = true
|
||||||
|
@ -3,6 +3,8 @@ import type { Nuxt } from '@nuxt/schema'
|
|||||||
import type { InlineConfig as ViteConfig } from 'vite'
|
import type { InlineConfig as ViteConfig } from 'vite'
|
||||||
import { distDir } from './dirs'
|
import { distDir } from './dirs'
|
||||||
|
|
||||||
|
const lastPlugins = ['autoprefixer', 'cssnano']
|
||||||
|
|
||||||
export function resolveCSSOptions (nuxt: Nuxt): ViteConfig['css'] {
|
export function resolveCSSOptions (nuxt: Nuxt): 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>> } = {
|
||||||
postcss: {
|
postcss: {
|
||||||
@ -10,19 +12,22 @@ export function resolveCSSOptions (nuxt: Nuxt): ViteConfig['css'] {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastPlugins = ['autoprefixer', 'cssnano']
|
css.postcss.plugins = []
|
||||||
css.postcss.plugins = Object.entries(nuxt.options.postcss.plugins)
|
|
||||||
|
const plugins = Object.entries(nuxt.options.postcss.plugins)
|
||||||
.sort((a, b) => lastPlugins.indexOf(a[0]) - lastPlugins.indexOf(b[0]))
|
.sort((a, b) => lastPlugins.indexOf(a[0]) - lastPlugins.indexOf(b[0]))
|
||||||
.filter(([, opts]) => opts)
|
|
||||||
.map(([name, opts]) => {
|
for (const [name, opts] of plugins) {
|
||||||
|
if (opts) {
|
||||||
const plugin = requireModule(name, {
|
const plugin = requireModule(name, {
|
||||||
paths: [
|
paths: [
|
||||||
...nuxt.options.modulesDir,
|
...nuxt.options.modulesDir,
|
||||||
distDir,
|
distDir,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
return plugin(opts)
|
css.postcss.plugins.push(plugin(opts))
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return css
|
return css
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,13 @@ export async function initViteDevBundler (ctx: ViteBuildContext, onBuild: () =>
|
|||||||
const { code, ids } = await bundleRequest(options, ctx.entry)
|
const { code, ids } = await bundleRequest(options, ctx.entry)
|
||||||
await fse.writeFile(resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs'), code, 'utf-8')
|
await fse.writeFile(resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs'), code, 'utf-8')
|
||||||
// Have CSS in the manifest to prevent FOUC on dev SSR
|
// Have CSS in the manifest to prevent FOUC on dev SSR
|
||||||
await writeManifest(ctx, ids.filter(isCSS).map(i => i.slice(1)))
|
const manifestIds: string[] = []
|
||||||
|
for (const i of ids) {
|
||||||
|
if (isCSS(i)) {
|
||||||
|
manifestIds.push(i.slice(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await writeManifest(ctx, manifestIds)
|
||||||
const time = (Date.now() - start)
|
const time = (Date.now() - start)
|
||||||
logger.success(`Vite server built in ${time}ms`)
|
logger.success(`Vite server built in ${time}ms`)
|
||||||
await onBuild()
|
await onBuild()
|
||||||
|
@ -50,11 +50,14 @@ export async function writeManifest (ctx: ViteBuildContext, css: string[] = [])
|
|||||||
await fse.mkdirp(serverDist)
|
await fse.mkdirp(serverDist)
|
||||||
|
|
||||||
if (ctx.config.build?.cssCodeSplit === false) {
|
if (ctx.config.build?.cssCodeSplit === false) {
|
||||||
const entryCSS = Object.values(clientManifest as Record<string, { file?: string }>).find(val => (val).file?.endsWith('.css'))?.file
|
for (const key in clientManifest as Record<string, { file?: string }>) {
|
||||||
if (entryCSS) {
|
const val = clientManifest[key]
|
||||||
const key = relative(ctx.config.root!, ctx.entry)
|
if (val.file?.endsWith('.css')) {
|
||||||
clientManifest[key].css ||= []
|
const key = relative(ctx.config.root!, ctx.entry)
|
||||||
clientManifest[key].css!.push(entryCSS)
|
clientManifest[key].css ||= []
|
||||||
|
clientManifest[key].css!.push(val.file)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { transform } from 'esbuild'
|
|||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import defu from 'defu'
|
import defu from 'defu'
|
||||||
import type { NuxtOptions } from 'nuxt/schema'
|
import type { NuxtOptions } from 'nuxt/schema'
|
||||||
|
import type { RenderedModule } from 'rollup'
|
||||||
import type { ViteBuildContext } from '../vite'
|
import type { ViteBuildContext } from '../vite'
|
||||||
|
|
||||||
export function analyzePlugin (ctx: ViteBuildContext): Plugin[] {
|
export function analyzePlugin (ctx: ViteBuildContext): Plugin[] {
|
||||||
@ -13,14 +14,18 @@ export function analyzePlugin (ctx: ViteBuildContext): Plugin[] {
|
|||||||
{
|
{
|
||||||
name: 'nuxt:analyze-minify',
|
name: 'nuxt:analyze-minify',
|
||||||
async generateBundle (_opts, outputBundle) {
|
async generateBundle (_opts, outputBundle) {
|
||||||
for (const [_bundleId, bundle] of Object.entries(outputBundle)) {
|
for (const _bundleId in outputBundle) {
|
||||||
|
const bundle = outputBundle[_bundleId]
|
||||||
if (bundle.type !== 'chunk') { continue }
|
if (bundle.type !== 'chunk') { continue }
|
||||||
const originalEntries = Object.entries(bundle.modules)
|
const minifiedModuleEntryPromises: Array<Promise<[string, RenderedModule]>> = []
|
||||||
const minifiedEntries = await Promise.all(originalEntries.map(async ([moduleId, module]) => {
|
for (const moduleId in bundle.modules) {
|
||||||
const { code } = await transform(module.code || '', { minify: true })
|
const module = bundle.modules[moduleId]
|
||||||
return [moduleId, { ...module, code }]
|
minifiedModuleEntryPromises.push(
|
||||||
}))
|
transform(module.code || '', { minify: true })
|
||||||
bundle.modules = Object.fromEntries(minifiedEntries)
|
.then(result => [moduleId, { ...module, code: result.code }]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
bundle.modules = Object.fromEntries(await Promise.all(minifiedModuleEntryPromises))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -23,12 +23,15 @@ const SUPPORTED_EXT_RE = /\.(?:m?[jt]sx?|vue)/
|
|||||||
|
|
||||||
export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptions) => {
|
export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptions) => {
|
||||||
const composableMeta: Record<string, any> = {}
|
const composableMeta: Record<string, any> = {}
|
||||||
|
const composableLengths = new Set<number>()
|
||||||
|
const keyedFunctions = new Set<string>()
|
||||||
for (const { name, ...meta } of options.composables) {
|
for (const { name, ...meta } of options.composables) {
|
||||||
composableMeta[name] = meta
|
composableMeta[name] = meta
|
||||||
|
keyedFunctions.add(name)
|
||||||
|
composableLengths.add(meta.argumentLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxLength = Math.max(...options.composables.map(({ argumentLength }) => argumentLength))
|
const maxLength = Math.max(...composableLengths)
|
||||||
const keyedFunctions = new Set(options.composables.map(({ name }) => name))
|
|
||||||
const KEYED_FUNCTIONS_RE = new RegExp(`\\b(${[...keyedFunctions].map(f => escapeRE(f)).join('|')})\\b`)
|
const KEYED_FUNCTIONS_RE = new RegExp(`\\b(${[...keyedFunctions].map(f => escapeRE(f)).join('|')})\\b`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -25,7 +25,7 @@ export const VitePublicDirsPlugin = createUnplugin((options: { sourcemap?: boole
|
|||||||
resolveId: {
|
resolveId: {
|
||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
handler (id) {
|
handler (id) {
|
||||||
if (id === '/__skip_vite' || !id.startsWith('/') || id.startsWith('/@fs')) { return }
|
if (id === '/__skip_vite' || id[0] !== '/' || id.startsWith('/@fs')) { return }
|
||||||
|
|
||||||
if (resolveFromPublicAssets(id)) {
|
if (resolveFromPublicAssets(id)) {
|
||||||
return PREFIX + encodeURIComponent(id)
|
return PREFIX + encodeURIComponent(id)
|
||||||
|
@ -66,12 +66,12 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
const { files, inBundle } = cssMap[file]
|
const { files, inBundle } = cssMap[file]
|
||||||
// File has been tree-shaken out of build (or there are no styles to inline)
|
// File has been tree-shaken out of build (or there are no styles to inline)
|
||||||
if (!files.length || !inBundle) { continue }
|
if (!files.length || !inBundle) { continue }
|
||||||
|
const fileName = filename(file)
|
||||||
const base = typeof outputOptions.assetFileNames === 'string'
|
const base = typeof outputOptions.assetFileNames === 'string'
|
||||||
? outputOptions.assetFileNames
|
? outputOptions.assetFileNames
|
||||||
: outputOptions.assetFileNames({
|
: outputOptions.assetFileNames({
|
||||||
type: 'asset',
|
type: 'asset',
|
||||||
name: `${filename(file)}-styles.mjs`,
|
name: `${fileName}-styles.mjs`,
|
||||||
source: '',
|
source: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
|
|
||||||
emitted[file] = this.emitFile({
|
emitted[file] = this.emitFile({
|
||||||
type: 'asset',
|
type: 'asset',
|
||||||
name: `${filename(file)}-styles.mjs`,
|
name: `${fileName}-styles.mjs`,
|
||||||
source: [
|
source: [
|
||||||
...files.map((css, i) => `import style_${i} from './${relative(baseDir, this.getFileName(css))}';`),
|
...files.map((css, i) => `import style_${i} from './${relative(baseDir, this.getFileName(css))}';`),
|
||||||
`export default [${files.map((_, i) => `style_${i}`).join(', ')}]`,
|
`export default [${files.map((_, i) => `style_${i}`).join(', ')}]`,
|
||||||
|
@ -105,7 +105,7 @@ export async function buildServer (ctx: ViteBuildContext) {
|
|||||||
|
|
||||||
if (!ctx.nuxt.options.dev) {
|
if (!ctx.nuxt.options.dev) {
|
||||||
const nitroDependencies = await tryResolveModule('nitropack/package.json', ctx.nuxt.options.modulesDir)
|
const nitroDependencies = await tryResolveModule('nitropack/package.json', ctx.nuxt.options.modulesDir)
|
||||||
.then(r => import(r!)).then(r => Object.keys(r.dependencies || {})).catch(() => [])
|
.then(r => import(r!)).then(r => r.dependencies ? Object.keys(r.dependencies) : []).catch(() => [])
|
||||||
if (Array.isArray(serverConfig.ssr!.external)) {
|
if (Array.isArray(serverConfig.ssr!.external)) {
|
||||||
serverConfig.ssr!.external.push(
|
serverConfig.ssr!.external.push(
|
||||||
// explicit dependencies we use in our ssr renderer - these can be inlined (if necessary) in the nitro build
|
// explicit dependencies we use in our ssr renderer - these can be inlined (if necessary) in the nitro build
|
||||||
|
@ -10,7 +10,7 @@ interface Envs {
|
|||||||
|
|
||||||
export function transpile (envs: Envs): Array<string | RegExp> {
|
export function transpile (envs: Envs): Array<string | RegExp> {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
const transpile = []
|
const transpile: Array<string | RegExp> = []
|
||||||
|
|
||||||
for (let pattern of nuxt.options.build.transpile) {
|
for (let pattern of nuxt.options.build.transpile) {
|
||||||
if (typeof pattern === 'function') {
|
if (typeof pattern === 'function') {
|
||||||
|
@ -4,6 +4,7 @@ import { dirname, join, normalize, resolve } from 'pathe'
|
|||||||
import type { Nuxt, NuxtBuilder, ViteConfig } from '@nuxt/schema'
|
import type { Nuxt, NuxtBuilder, ViteConfig } from '@nuxt/schema'
|
||||||
import { addVitePlugin, isIgnored, logger, resolvePath } from '@nuxt/kit'
|
import { addVitePlugin, isIgnored, logger, resolvePath } from '@nuxt/kit'
|
||||||
import replace from '@rollup/plugin-replace'
|
import replace from '@rollup/plugin-replace'
|
||||||
|
import type { RollupReplaceOptions } from '@rollup/plugin-replace'
|
||||||
import { sanitizeFilePath } from 'mlly'
|
import { sanitizeFilePath } from 'mlly'
|
||||||
import { withoutLeadingSlash } from 'ufo'
|
import { withoutLeadingSlash } from 'ufo'
|
||||||
import { filename } from 'pathe/utils'
|
import { filename } from 'pathe/utils'
|
||||||
@ -102,10 +103,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
|||||||
rootDir: nuxt.options.rootDir,
|
rootDir: nuxt.options.rootDir,
|
||||||
composables: nuxt.options.optimization.keyedComposables,
|
composables: nuxt.options.optimization.keyedComposables,
|
||||||
}),
|
}),
|
||||||
replace({
|
replace({ preventAssignment: true, ...globalThisReplacements }),
|
||||||
...Object.fromEntries([';', '(', '{', '}', ' ', '\t', '\n'].map(d => [`${d}global.`, `${d}globalThis.`])),
|
|
||||||
preventAssignment: true,
|
|
||||||
}),
|
|
||||||
virtual(nuxt.vfs),
|
virtual(nuxt.vfs),
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
@ -164,10 +162,16 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
|||||||
await nuxt.callHook('vite:extend', ctx)
|
await nuxt.callHook('vite:extend', ctx)
|
||||||
|
|
||||||
nuxt.hook('vite:extendConfig', (config) => {
|
nuxt.hook('vite:extendConfig', (config) => {
|
||||||
config.plugins!.push(replace({
|
const replaceOptions: RollupReplaceOptions = Object.create(null)
|
||||||
preventAssignment: true,
|
replaceOptions.preventAssignment = true
|
||||||
...Object.fromEntries(Object.entries(config.define!).filter(([key]) => key.startsWith('import.meta.'))),
|
|
||||||
}))
|
for (const key in config.define!) {
|
||||||
|
if (key.startsWith('import.meta.')) {
|
||||||
|
replaceOptions[key] = config.define![key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.plugins!.push(replace(replaceOptions))
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!ctx.nuxt.options.dev) {
|
if (!ctx.nuxt.options.dev) {
|
||||||
@ -224,3 +228,5 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
|||||||
await buildClient(ctx)
|
await buildClient(ctx)
|
||||||
await buildServer(ctx)
|
await buildServer(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const globalThisReplacements = Object.fromEntries([';', '(', '{', '}', ' ', '\t', '\n'].map(d => [`${d}global.`, `${d}globalThis.`]))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxt/webpack-builder",
|
"name": "@nuxt/webpack-builder",
|
||||||
"version": "3.11.2",
|
"version": "3.12.1",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||||
@ -62,7 +62,7 @@
|
|||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"vue-bundle-renderer": "^2.1.0",
|
"vue-bundle-renderer": "^2.1.0",
|
||||||
"vue-loader": "^17.4.2",
|
"vue-loader": "^17.4.2",
|
||||||
"webpack": "^5.91.0",
|
"webpack": "^5.92.0",
|
||||||
"webpack-bundle-analyzer": "^4.10.2",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"webpack-dev-middleware": "^7.2.1",
|
"webpack-dev-middleware": "^7.2.1",
|
||||||
"webpack-hot-middleware": "^2.26.1",
|
"webpack-hot-middleware": "^2.26.1",
|
||||||
|
@ -43,24 +43,19 @@ export default class VueSSRClientPlugin {
|
|||||||
|
|
||||||
const allFiles = new Set<string>()
|
const allFiles = new Set<string>()
|
||||||
const asyncFiles = new Set<string>()
|
const asyncFiles = new Set<string>()
|
||||||
|
|
||||||
for (const asset of stats.assets!) {
|
|
||||||
const file = asset.name
|
|
||||||
if (!isHotUpdate(file)) {
|
|
||||||
allFiles.add(file)
|
|
||||||
if (initialFiles.has(file)) { continue }
|
|
||||||
if (isJS(file) || isCSS(file)) {
|
|
||||||
asyncFiles.add(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const assetsMapping: Record<string, string[]> = {}
|
const assetsMapping: Record<string, string[]> = {}
|
||||||
for (const { name, chunkNames = [] } of stats.assets!) {
|
|
||||||
if (isJS(name) && !isHotUpdate(name)) {
|
for (const { name: file, chunkNames = [] } of stats.assets!) {
|
||||||
|
if (isHotUpdate(file)) { continue }
|
||||||
|
allFiles.add(file)
|
||||||
|
const isFileJS = isJS(file)
|
||||||
|
if (!initialFiles.has(file) && (isFileJS || isCSS(file))) {
|
||||||
|
asyncFiles.add(file)
|
||||||
|
}
|
||||||
|
if (isFileJS) {
|
||||||
const componentHash = hash(chunkNames.join('|'))
|
const componentHash = hash(chunkNames.join('|'))
|
||||||
const map = assetsMapping[componentHash] ||= []
|
const map = assetsMapping[componentHash] ||= []
|
||||||
map.push(name)
|
map.push(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1327
pnpm-lock.yaml
1327
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ for PKG in packages/* ; do
|
|||||||
if [[ $PKG == "packages/test-utils" ]] ; then
|
if [[ $PKG == "packages/test-utils" ]] ; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
if [[ $p == "packages/ui-templates" ]] ; then
|
if [[ $PKG == "packages/ui-templates" ]] ; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
pushd $PKG
|
pushd $PKG
|
||||||
|
@ -13,9 +13,9 @@ async function main () {
|
|||||||
const commits = await getLatestCommits().then(commits => commits.filter(
|
const commits = await getLatestCommits().then(commits => commits.filter(
|
||||||
c => config.types[c.type] && !(c.type === 'chore' && c.scope === 'deps' && !c.isBreaking),
|
c => config.types[c.type] && !(c.type === 'chore' && c.scope === 'deps' && !c.isBreaking),
|
||||||
))
|
))
|
||||||
const bumpType = await determineBumpType()
|
const bumpType = await determineBumpType() || 'patch'
|
||||||
|
|
||||||
const newVersion = inc(workspace.find('nuxt').data.version, bumpType || 'patch')
|
const newVersion = inc(workspace.find('nuxt').data.version, bumpType)
|
||||||
const changelog = await generateMarkDown(commits, config)
|
const changelog = await generateMarkDown(commits, config)
|
||||||
|
|
||||||
// Create and push a branch with bumped versions if it has not already been created
|
// Create and push a branch with bumped versions if it has not already been created
|
||||||
@ -44,7 +44,8 @@ async function main () {
|
|||||||
changelog
|
changelog
|
||||||
.replace(/^## v.*\n/, '')
|
.replace(/^## v.*\n/, '')
|
||||||
.replace(`...${releaseBranch}`, `...v${newVersion}`)
|
.replace(`...${releaseBranch}`, `...v${newVersion}`)
|
||||||
.replace(/### ❤️ Contributors[\s\S]*$/, ''),
|
.replace(/### ❤️ Contributors[\s\S]*$/, '')
|
||||||
|
.replace(/[\n\r]+/g, '\n'),
|
||||||
'### ❤️ Contributors',
|
'### ❤️ Contributors',
|
||||||
contributors.map(c => `- ${c.name} (@${c.username})`).join('\n'),
|
contributors.map(c => `- ${c.name} (@${c.username})`).join('\n'),
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
@ -166,6 +166,21 @@ describe('pages', () => {
|
|||||||
expect(res.headers.get('x-extend')).toEqual('added in pages:extend')
|
expect(res.headers.get('x-extend')).toEqual('added in pages:extend')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('preserves page metadata added in pages:extend hook', async () => {
|
||||||
|
const html = await $fetch<string>('/some-custom-path')
|
||||||
|
expect (html.match(/<pre>([^<]*)<\/pre>/)?.[1]?.trim().replace(/"/g, '"').replace(/>/g, '>')).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"name": "some-custom-name",
|
||||||
|
"path": "/some-custom-path",
|
||||||
|
"validate": "() => true",
|
||||||
|
"middleware": [
|
||||||
|
"() => true"
|
||||||
|
],
|
||||||
|
"otherValue": "{\\"foo\\":\\"bar\\"}"
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
it('validates routes', async () => {
|
it('validates routes', async () => {
|
||||||
const { status, headers } = await fetch('/forbidden')
|
const { status, headers } = await fetch('/forbidden')
|
||||||
expect(status).toEqual(404)
|
expect(status).toEqual(404)
|
||||||
@ -745,6 +760,24 @@ describe('nuxt links', () => {
|
|||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('respects external links in edge cases', async () => {
|
||||||
|
const html = await $fetch<string>('/nuxt-link/custom-external')
|
||||||
|
const hrefs = html.match(/<a[^>]*href="([^"]+)"/g)
|
||||||
|
expect(hrefs).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"<a href="https://thehackernews.com/2024/01/urgent-upgrade-gitlab-critical.html"",
|
||||||
|
"<a href="https://thehackernews.com/2024/01/urgent-upgrade-gitlab-critical.html"",
|
||||||
|
"<a href="/missing-page/"",
|
||||||
|
"<a href="/missing-page/"",
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
const { page, consoleLogs } = await renderPage('/nuxt-link/custom-external')
|
||||||
|
const warnings = consoleLogs.filter(c => c.text.includes('No match found for location'))
|
||||||
|
expect(warnings).toMatchInlineSnapshot(`[]`)
|
||||||
|
await page.close()
|
||||||
|
})
|
||||||
|
|
||||||
it('preserves route state', async () => {
|
it('preserves route state', async () => {
|
||||||
const { page } = await renderPage('/nuxt-link/trailing-slash')
|
const { page } = await renderPage('/nuxt-link/trailing-slash')
|
||||||
|
|
||||||
|
7
test/fixtures/basic-types/nuxt.config.ts
vendored
7
test/fixtures/basic-types/nuxt.config.ts
vendored
@ -21,6 +21,13 @@ export default defineNuxtConfig({
|
|||||||
title: Promise.resolve('Nuxt Fixture'),
|
title: Promise.resolve('Nuxt Fixture'),
|
||||||
// @ts-expect-error Functions are not allowed
|
// @ts-expect-error Functions are not allowed
|
||||||
titleTemplate: title => 'test',
|
titleTemplate: title => 'test',
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
// Allows unknown property
|
||||||
|
property: 'og:thing',
|
||||||
|
content: '1234567890',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
pageTransition: {
|
pageTransition: {
|
||||||
// @ts-expect-error Functions are not allowed
|
// @ts-expect-error Functions are not allowed
|
||||||
|
2
test/fixtures/basic-types/package.json
vendored
2
test/fixtures/basic-types/package.json
vendored
@ -11,7 +11,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ofetch": "latest",
|
"ofetch": "latest",
|
||||||
"unplugin-vue-router": "^0.7.0",
|
"unplugin-vue-router": "^0.7.0",
|
||||||
"vitest": "1.5.3",
|
"vitest": "1.6.0",
|
||||||
"vue": "latest",
|
"vue": "latest",
|
||||||
"vue-router": "latest"
|
"vue-router": "latest"
|
||||||
}
|
}
|
||||||
|
12
test/fixtures/basic-types/types.ts
vendored
12
test/fixtures/basic-types/types.ts
vendored
@ -4,6 +4,7 @@ import type { FetchError } from 'ofetch'
|
|||||||
import type { NavigationFailure, RouteLocationNormalized, RouteLocationRaw, Router, useRouter as vueUseRouter } from '#vue-router'
|
import type { NavigationFailure, RouteLocationNormalized, RouteLocationRaw, Router, useRouter as vueUseRouter } from '#vue-router'
|
||||||
|
|
||||||
import type { AppConfig, RuntimeValue, UpperSnakeCase } from 'nuxt/schema'
|
import type { AppConfig, RuntimeValue, UpperSnakeCase } from 'nuxt/schema'
|
||||||
|
import { defineNuxtModule } from 'nuxt/kit'
|
||||||
import { defineNuxtConfig } from 'nuxt/config'
|
import { defineNuxtConfig } from 'nuxt/config'
|
||||||
import { callWithNuxt, isVue3 } from '#app'
|
import { callWithNuxt, isVue3 } from '#app'
|
||||||
import type { NuxtError } from '#app'
|
import type { NuxtError } from '#app'
|
||||||
@ -242,6 +243,17 @@ describe('modules', () => {
|
|||||||
// @ts-expect-error we want to ensure we throw type error on invalid key
|
// @ts-expect-error we want to ensure we throw type error on invalid key
|
||||||
defineNuxtConfig({ undeclaredKey: { other: false } })
|
defineNuxtConfig({ undeclaredKey: { other: false } })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('preserves options in defineNuxtModule setup without `.with()`', () => {
|
||||||
|
defineNuxtModule<{ foo?: string, baz: number }>({
|
||||||
|
defaults: {
|
||||||
|
baz: 100,
|
||||||
|
},
|
||||||
|
setup: (resolvedOptions) => {
|
||||||
|
expectTypeOf(resolvedOptions).toEqualTypeOf<{ foo?: string, baz: number }>()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('nuxtApp', () => {
|
describe('nuxtApp', () => {
|
||||||
|
@ -16,9 +16,15 @@ export default defineNuxtModule({
|
|||||||
}, {
|
}, {
|
||||||
path: '/big-page-1',
|
path: '/big-page-1',
|
||||||
file: resolver.resolve('./pages/big-page.vue'),
|
file: resolver.resolve('./pages/big-page.vue'),
|
||||||
|
meta: {
|
||||||
|
layout: false,
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
path: '/big-page-2',
|
path: '/big-page-2',
|
||||||
file: resolver.resolve('./pages/big-page.vue'),
|
file: resolver.resolve('./pages/big-page.vue'),
|
||||||
|
meta: {
|
||||||
|
layout: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
21
test/fixtures/basic/nuxt.config.ts
vendored
21
test/fixtures/basic/nuxt.config.ts
vendored
@ -1,5 +1,6 @@
|
|||||||
import { addBuildPlugin, addComponent } from 'nuxt/kit'
|
import { addBuildPlugin, addComponent } from 'nuxt/kit'
|
||||||
import type { NuxtPage } from 'nuxt/schema'
|
import type { NuxtPage } from 'nuxt/schema'
|
||||||
|
import { defu } from 'defu'
|
||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import { withoutLeadingSlash } from 'ufo'
|
import { withoutLeadingSlash } from 'ufo'
|
||||||
|
|
||||||
@ -88,10 +89,17 @@ export default defineNuxtConfig({
|
|||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
public: {
|
public: {
|
||||||
needsFallback: undefined,
|
needsFallback: undefined,
|
||||||
testConfig: 123,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modules: [
|
modules: [
|
||||||
|
function (_options, nuxt) {
|
||||||
|
// ensure setting `runtimeConfig` also sets `nitro.runtimeConfig`
|
||||||
|
nuxt.options.runtimeConfig = defu(nuxt.options.runtimeConfig, {
|
||||||
|
public: {
|
||||||
|
testConfig: 123,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
function (_options, nuxt) {
|
function (_options, nuxt) {
|
||||||
nuxt.hook('modules:done', () => {
|
nuxt.hook('modules:done', () => {
|
||||||
// @ts-expect-error not valid nuxt option
|
// @ts-expect-error not valid nuxt option
|
||||||
@ -151,6 +159,17 @@ export default defineNuxtConfig({
|
|||||||
internalParent!.children = newPages
|
internalParent!.children = newPages
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
function (_options, nuxt) {
|
||||||
|
// to check that page metadata is preserved
|
||||||
|
nuxt.hook('pages:extend', (pages) => {
|
||||||
|
const customName = pages.find(page => page.name === 'some-custom-name')
|
||||||
|
if (!customName) { throw new Error('Page with custom name not found') }
|
||||||
|
if (customName.path !== '/some-custom-path') { throw new Error('Page path not extracted') }
|
||||||
|
|
||||||
|
customName.meta ||= {}
|
||||||
|
customName.meta.someProp = true
|
||||||
|
})
|
||||||
|
},
|
||||||
// To test falsy module values
|
// To test falsy module values
|
||||||
undefined,
|
undefined,
|
||||||
],
|
],
|
||||||
|
36
test/fixtures/basic/pages/meta.vue
vendored
Normal file
36
test/fixtures/basic/pages/meta.vue
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
name: 'some-custom-name',
|
||||||
|
path: '/some-custom-path',
|
||||||
|
validate: () => true,
|
||||||
|
middleware: [() => true],
|
||||||
|
otherValue: {
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const serialisedMeta: Record<string, string> = {}
|
||||||
|
const meta = useRoute().meta
|
||||||
|
for (const key in meta) {
|
||||||
|
if (Array.isArray(meta[key])) {
|
||||||
|
serialisedMeta[key] = meta[key].map((fn: Function) => fn.toString())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (typeof meta[key] === 'string') {
|
||||||
|
serialisedMeta[key] = meta[key]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (typeof meta[key] === 'object') {
|
||||||
|
serialisedMeta[key] = JSON.stringify(meta[key])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (typeof meta[key] === 'function') {
|
||||||
|
serialisedMeta[key] = meta[key].toString()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<pre>{{ serialisedMeta }}</pre>
|
||||||
|
</template>
|
53
test/fixtures/basic/pages/nuxt-link/custom-external.vue
vendored
Normal file
53
test/fixtures/basic/pages/nuxt-link/custom-external.vue
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const MyLink = defineNuxtLink({
|
||||||
|
componentName: 'MyLink',
|
||||||
|
trailingSlash: 'append',
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<MyLink to="https://thehackernews.com/2024/01/urgent-upgrade-gitlab-critical.html">
|
||||||
|
Trailing slashes should not be applied to implicit external links
|
||||||
|
</MyLink>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<NuxtLink
|
||||||
|
:to="{ path: 'https://thehackernews.com/2024/01/urgent-upgrade-gitlab-critical.html' }"
|
||||||
|
external
|
||||||
|
>
|
||||||
|
External links within route objects should be respected and not have trailing slashes applied
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MyLink
|
||||||
|
:to="{ path: '/missing-page' }"
|
||||||
|
external
|
||||||
|
>
|
||||||
|
External links within route objects should be respected and have trailing slashes applied
|
||||||
|
</MyLink>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MyLink
|
||||||
|
to="/missing-page"
|
||||||
|
external
|
||||||
|
>
|
||||||
|
External links should be respected and have trailing slashes applied
|
||||||
|
</MyLink>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<NuxtLink
|
||||||
|
custom
|
||||||
|
to="https://google.com"
|
||||||
|
external
|
||||||
|
>
|
||||||
|
<template #default="{ navigate }">
|
||||||
|
<button @click="navigate()">
|
||||||
|
Using navigate() with external link should work
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
9
test/fixtures/basic/pages/tsx-page-meta.vue
vendored
Normal file
9
test/fixtures/basic/pages/tsx-page-meta.vue
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<PageContent />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="tsx">
|
||||||
|
definePageMeta({})
|
||||||
|
defineRouteRules({})
|
||||||
|
const PageContent = () => (<div>Home Page</div>)
|
||||||
|
</script>
|
@ -53,6 +53,7 @@ describe('app config', () => {
|
|||||||
describe('composables', () => {
|
describe('composables', () => {
|
||||||
it('are all tested', () => {
|
it('are all tested', () => {
|
||||||
const testedComposables: string[] = [
|
const testedComposables: string[] = [
|
||||||
|
'useRouteAnnouncer',
|
||||||
'clearNuxtData',
|
'clearNuxtData',
|
||||||
'refreshNuxtData',
|
'refreshNuxtData',
|
||||||
'useAsyncData',
|
'useAsyncData',
|
||||||
|
Loading…
Reference in New Issue
Block a user