mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 01:15:58 +00:00
Merge branch 'main' into patch-21
This commit is contained in:
commit
8b21493917
@ -1,4 +1,4 @@
|
||||
FROM node:lts@sha256:0e910f435308c36ea60b4cfd7b80208044d77a074d16b768a81901ce938a62dc
|
||||
FROM node:lts@sha256:99981c3d1aac0d98cd9f03f74b92dddf30f30ffb0b34e6df8bd96283f62f12c6
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -fy libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 && \
|
||||
|
4
.github/workflows/autofix-docs.yml
vendored
4
.github/workflows/autofix-docs.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@ -33,4 +33,4 @@ jobs:
|
||||
- name: Lint (docs)
|
||||
run: pnpm lint:docs:fix
|
||||
|
||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
||||
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||
|
6
.github/workflows/autofix.yml
vendored
6
.github/workflows/autofix.yml
vendored
@ -17,14 +17,14 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Check engine ranges, peer dependency ranges and installed versions
|
||||
run: pnpm installed-check -d --fix
|
||||
run: pnpm installed-check --no-include-workspace-root --ignore-dev --workspace-ignore='test/**,playground' --fix
|
||||
|
||||
- name: Build (stub)
|
||||
run: pnpm dev:prepare
|
||||
@ -55,4 +55,4 @@ jobs:
|
||||
- name: Lint (code)
|
||||
run: pnpm lint:fix
|
||||
|
||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
||||
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||
|
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
2
.github/workflows/changelog.yml
vendored
2
.github/workflows/changelog.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
47
.github/workflows/ci.yml
vendored
47
.github/workflows/ci.yml
vendored
@ -41,7 +41,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@ -56,11 +56,8 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Check types
|
||||
run: pnpm test:attw
|
||||
|
||||
- name: Cache dist
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
retention-days: 3
|
||||
name: dist
|
||||
@ -81,7 +78,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
||||
uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||
with:
|
||||
config: |
|
||||
paths:
|
||||
@ -98,7 +95,7 @@ jobs:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
||||
uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
|
||||
@ -118,7 +115,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@ -149,7 +146,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@ -161,6 +158,9 @@ jobs:
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
- name: Check built types
|
||||
run: pnpm test:attw
|
||||
|
||||
test-unit:
|
||||
# autofix workflow will be triggered instead for PRs
|
||||
if: github.event_name == 'push'
|
||||
@ -173,7 +173,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@ -260,20 +260,18 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
build-release:
|
||||
release-nightly:
|
||||
concurrency:
|
||||
group: release
|
||||
permissions:
|
||||
id-token: write
|
||||
if: |
|
||||
github.event_name == 'push' &&
|
||||
github.repository == 'nuxt/nuxt' &&
|
||||
github.repository_owner == 'nuxt' &&
|
||||
!contains(github.event.head_commit.message, '[skip-release]') &&
|
||||
!startsWith(github.event.head_commit.message, 'docs')
|
||||
needs:
|
||||
- lint
|
||||
- build
|
||||
- test-fixtures
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
@ -284,7 +282,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@ -299,22 +297,15 @@ jobs:
|
||||
- name: Release Edge
|
||||
run: ./scripts/release-edge.sh ${{ github.ref == 'refs/heads/main' && 'latest' || '3x' }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
|
||||
release-pr:
|
||||
concurrency:
|
||||
group: release
|
||||
permissions:
|
||||
id-token: write
|
||||
pull-requests: write
|
||||
if: |
|
||||
github.event_name == 'pull_request' &&
|
||||
contains(github.event.pull_request.labels.*.name, '🧷 edge release')
|
||||
if: github.repository_owner == 'nuxt' && github.event_name != 'push'
|
||||
needs:
|
||||
- lint
|
||||
- build
|
||||
- test-fixtures
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
@ -325,7 +316,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@ -337,8 +328,4 @@ jobs:
|
||||
name: dist
|
||||
path: packages
|
||||
|
||||
- name: Release Edge
|
||||
run: ./scripts/release-edge.sh pr-${{ github.event.issue.number }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
- run: pnpm pkg-pr-new publish --compact './packages/kit' './packages/nuxt' './packages/rspack' './packages/schema' './packages/vite' './packages/webpack'
|
||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
6
.github/workflows/lint-monorepo.yml
vendored
6
.github/workflows/lint-monorepo.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -29,7 +29,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: lts/*
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@ -39,4 +39,4 @@ jobs:
|
||||
run: pnpm sherif -r multiple-dependency-versions
|
||||
|
||||
- name: Check engine ranges, peer dependency ranges and installed versions
|
||||
run: pnpm installed-check -d
|
||||
run: pnpm installed-check --no-include-workspace-root --ignore-dev --workspace-ignore='test/**,playground'
|
||||
|
2
.github/workflows/lint-workflows.yml
vendored
2
.github/workflows/lint-workflows.yml
vendored
@ -26,6 +26,6 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
# From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
|
||||
- name: Check workflow files
|
||||
uses: docker://rhysd/actionlint:1.7.4@sha256:82244e1db1c60d82c7792180a48dd0bcb838370bb589d53ff132503fc9485868
|
||||
uses: docker://rhysd/actionlint:1.7.6@sha256:e3856d413f923accc4120884ff79f6bdba3dd53fd42884d325f21af61cc15ce0
|
||||
with:
|
||||
args: -color
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: lts/*
|
||||
registry-url: "https://registry.npmjs.org/"
|
||||
cache: "pnpm"
|
||||
|
||||
|
4
.github/workflows/scorecards.yml
vendored
4
.github/workflows/scorecards.yml
vendored
@ -59,7 +59,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
if: github.repository == 'nuxt/nuxt' && success()
|
||||
with:
|
||||
name: SARIF file
|
||||
@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
||||
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||
if: github.repository == 'nuxt/nuxt' && success()
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
2
.github/workflows/semantic-pull-requests.yml
vendored
2
.github/workflows/semantic-pull-requests.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
|
||||
if: github.repository == 'nuxt/nuxt' && !startsWith(github.head_ref, 'v')
|
||||
runs-on: ubuntu-latest
|
||||
name: Semantic pull request
|
||||
name: semantic-pr
|
||||
steps:
|
||||
- name: Validate PR title
|
||||
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
|
||||
|
@ -638,11 +638,11 @@ We have raised PRs to update modules using EJS syntax, but if you need to do thi
|
||||
|
||||
* Moving your string interpolation logic directly into `getContents()`.
|
||||
* Using a custom function to handle the replacement, such as in https://github.com/nuxt-modules/color-mode/pull/240.
|
||||
* Continuing to use `lodash`, as a dependency of _your_ project rather than Nuxt:
|
||||
* Use `es-toolkit/compat` (a drop-in replacement for lodash template), as a dependency of _your_ project rather than Nuxt:
|
||||
|
||||
```diff
|
||||
+ import { readFileSync } from 'node:fs'
|
||||
+ import { template } from 'lodash-es'
|
||||
+ import { template } from 'es-toolkit/compat'
|
||||
// ...
|
||||
addTemplate({
|
||||
fileName: 'appinsights-vue.js'
|
||||
|
@ -100,6 +100,10 @@ Watch a video from Alexander Lichter about **Building a plain SPA with Nuxt!?**.
|
||||
|
||||
If you deploy your app to [static hosting](/docs/getting-started/deployment#static-hosting) with the `nuxi generate` or `nuxi build --prerender` commands, then by default, Nuxt will render every page as a separate static HTML file.
|
||||
|
||||
::warning
|
||||
If you prerender your app with the `nuxi generate` or `nuxi build --prerender` commands, then you will not be able to use any server endpoints as no server will be included in your output folder. If you need server functionality, use `nuxi build` instead.
|
||||
::
|
||||
|
||||
If you are using purely client-side rendering, then this might be unnecessary. You might only need a single `index.html` file, plus `200.html` and `404.html` fallbacks, which you can tell your static web host to serve up for all requests.
|
||||
|
||||
In order to achieve this we can change how the routes are prerendered. Just add this to [your hooks](/docs/api/advanced/hooks#nuxt-hooks-build-time) in your `nuxt.config.ts`:
|
||||
|
@ -71,7 +71,7 @@ export const useFoo = () => {
|
||||
|
||||
### Access plugin injections
|
||||
|
||||
You can access [plugin injections](/docs/guide/directory-structure/plugins#automatically-providing-helpers) from composables:
|
||||
You can access [plugin injections](/docs/guide/directory-structure/plugins#providing-helpers) from composables:
|
||||
|
||||
```js [composables/test.ts]
|
||||
export const useHello = () => {
|
||||
|
@ -61,7 +61,7 @@ This feature will likely be removed in a near future.
|
||||
|
||||
Emits `app:chunkError` hook when there is an error loading vite/webpack chunks. Default behavior is to perform a reload of the new route on navigation to a new route when a chunk fails to load.
|
||||
|
||||
If you set this to `'automatic-immediate'` Nuxt will reload the current route immediatly, instead of waiting for a navigation. This is useful for chunk errors that are not triggered by navigation, e.g., when your Nuxt app fails to load a [lazy component](/docs/guide/directory-structure/components#dynamic-imports). A potential downside of this behavior is undesired reloads, e.g., when your app does not need the chunk that caused the error.
|
||||
If you set this to `'automatic-immediate'` Nuxt will reload the current route immediately, instead of waiting for a navigation. This is useful for chunk errors that are not triggered by navigation, e.g., when your Nuxt app fails to load a [lazy component](/docs/guide/directory-structure/components#dynamic-imports). A potential downside of this behavior is undesired reloads, e.g., when your app does not need the chunk that caused the error.
|
||||
|
||||
You can disable automatic handling by setting this to `false`, or handle chunk errors manually by setting it to `manual`.
|
||||
|
||||
|
@ -162,7 +162,7 @@ export default defineNuxtRouteMiddleware(() => {
|
||||
|
||||
## Home Page
|
||||
|
||||
Now that we have our app middleware to protect our routes, we can use it on our home page that display our authenticated user informations. If the user is not authenticated, they will be redirected to the login page.
|
||||
Now that we have our app middleware to protect our routes, we can use it on our home page that display our authenticated user information. If the user is not authenticated, they will be redirected to the login page.
|
||||
|
||||
We'll use [`definePageMeta`](/docs/api/utils/define-page-meta) to apply the middleware to the route that we want to protect.
|
||||
|
||||
|
@ -69,7 +69,13 @@ const { data: posts } = await useAsyncData(
|
||||
- `immediate`: when set to `false`, will prevent the request from firing immediately. (defaults to `true`)
|
||||
- `default`: a factory function to set the default value of the `data`, before the async function resolves - useful with the `lazy: true` or `immediate: false` option
|
||||
- `transform`: a function that can be used to alter `handler` function result after resolving
|
||||
- `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled.
|
||||
- `getCachedData`: Provide a function which returns cached data. A `null` or `undefined` return value will trigger a fetch. By default, this is:
|
||||
```ts
|
||||
const getDefaultCachedData = (key) => nuxtApp.isHydrating
|
||||
? nuxtApp.payload.data[key]
|
||||
: nuxtApp.static.data[key]
|
||||
```
|
||||
Which only caches data when `experimental.payloadExtraction` of `nuxt.config` is enabled.
|
||||
- `pick`: only pick specified keys in this array from the `handler` function result
|
||||
- `watch`: watch reactive sources to auto-refresh
|
||||
- `deep`: return data in a deep ref object. It is `false` by default to return data in a shallow ref object for performance.
|
||||
@ -94,7 +100,13 @@ Learn how to use `transform` and `getCachedData` to avoid superfluous calls to a
|
||||
- `data`: the result of the asynchronous function that is passed in.
|
||||
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
|
||||
- `error`: an error object if the data fetching failed.
|
||||
- `status`: a string indicating the status of the data request (`"idle"`, `"pending"`, `"success"`, `"error"`).
|
||||
- `status`: a string indicating the status of the data request:
|
||||
- `idle`: when the request has not started, such as:
|
||||
- when `execute` has not yet been called and `{ immediate: false }` is set
|
||||
- when rendering HTML on the server and `{ server: false }` is set
|
||||
- `pending`: the request is in progress
|
||||
- `success`: the request has completed successfully
|
||||
- `error`: the request has failed
|
||||
- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `status` to `'idle'`, and mark any currently pending requests as cancelled.
|
||||
|
||||
By default, Nuxt waits until a `refresh` is finished before it can be executed again.
|
||||
|
@ -109,7 +109,13 @@ All fetch options can be given a `computed` or `ref` value. These will be watche
|
||||
- `immediate`: when set to `false`, will prevent the request from firing immediately. (defaults to `true`)
|
||||
- `default`: a factory function to set the default value of the `data`, before the async function resolves - useful with the `lazy: true` or `immediate: false` option
|
||||
- `transform`: a function that can be used to alter `handler` function result after resolving
|
||||
- `getCachedData`: Provide a function which returns cached data. A _null_ or _undefined_ return value will trigger a fetch. By default, this is: `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]`, which only caches data when `payloadExtraction` is enabled.
|
||||
- `getCachedData`: Provide a function which returns cached data. A `null` or `undefined` return value will trigger a fetch. By default, this is:
|
||||
```ts
|
||||
const getDefaultCachedData = (key) => nuxtApp.isHydrating
|
||||
? nuxtApp.payload.data[key]
|
||||
: nuxtApp.static.data[key]
|
||||
```
|
||||
Which only caches data when `experimental.payloadExtraction` of `nuxt.config` is enabled.
|
||||
- `pick`: only pick specified keys in this array from the `handler` function result
|
||||
- `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`. (You can [see an example here](/docs/getting-started/data-fetching#watch) of using `watch`.)
|
||||
- `deep`: return data in a deep ref object. It is `false` by default to return data in a shallow ref object for performance.
|
||||
@ -134,7 +140,13 @@ Learn how to use `transform` and `getCachedData` to avoid superfluous calls to a
|
||||
- `data`: the result of the asynchronous function that is passed in.
|
||||
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
|
||||
- `error`: an error object if the data fetching failed.
|
||||
- `status`: a string indicating the status of the data request (`"idle"`, `"pending"`, `"success"`, `"error"`).
|
||||
- `status`: a string indicating the status of the data request:
|
||||
- `idle`: when the request has not started, such as:
|
||||
- when `execute` has not yet been called and `{ immediate: false }` is set
|
||||
- when rendering HTML on the server and `{ server: false }` is set
|
||||
- `pending`: the request is in progress
|
||||
- `success`: the request has completed successfully
|
||||
- `error`: the request has failed
|
||||
- `clear`: a function which will set `data` to `undefined`, set `error` to `null`, set `status` to `'idle'`, and mark any currently pending requests as cancelled.
|
||||
|
||||
By default, Nuxt waits until a `refresh` is finished before it can be executed again.
|
||||
@ -147,7 +159,7 @@ If you have not fetched data on the server (for example, with `server: false`),
|
||||
|
||||
```ts [Signature]
|
||||
function useFetch<DataT, ErrorT>(
|
||||
url: string | Request | Ref<string | Request> | (() => string) | Request,
|
||||
url: string | Request | Ref<string | Request> | (() => string | Request),
|
||||
options?: UseFetchOptions<DataT>
|
||||
): Promise<AsyncData<DataT, ErrorT>>
|
||||
|
||||
|
@ -19,7 +19,7 @@ npx nuxi add <TEMPLATE> <NAME> [--cwd=<directory>] [--logLevel=<silent|info|verb
|
||||
<!--add-args-->
|
||||
Argument | Description
|
||||
--- | ---
|
||||
`TEMPLATE` | Specify which template to generate (options: <api\|plugin\|component\|composable\|middleware\|layout\|page>)
|
||||
`TEMPLATE` | Specify which template to generate (options: <api\|plugin\|component\|composable\|middleware\|layout\|page\|layer>)
|
||||
`NAME` | Specify name of the generated file
|
||||
<!--/add-args-->
|
||||
|
||||
@ -103,3 +103,10 @@ npx nuxi add middleware auth
|
||||
# Generates `server/api/hello.ts`
|
||||
npx nuxi add api hello
|
||||
```
|
||||
|
||||
## `nuxi add layer`
|
||||
|
||||
```bash [Terminal]
|
||||
# Generates `layers/subscribe/nuxt.config.ts`
|
||||
npx nuxi add layer subscribe
|
||||
```
|
||||
|
@ -4,7 +4,7 @@ description: 'Nuxt command to build your Nuxt module before publishing.'
|
||||
links:
|
||||
- label: Source
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/cli/blob/main/src/commands/build-module.ts
|
||||
to: https://github.com/nuxt/module-builder/blob/main/src/cli.ts
|
||||
size: xs
|
||||
---
|
||||
|
||||
|
@ -14,7 +14,7 @@ Nuxi provides a few utilities to work with [Nuxt modules](/modules) seamlessly.
|
||||
|
||||
<!--module-add-cmd-->
|
||||
```bash [Terminal]
|
||||
npx nuxi module add <MODULENAME> [--cwd=<directory>] [--logLevel=<silent|info|verbose>] [--skipInstall] [--skipConfig]
|
||||
npx nuxi module add <MODULENAME> [--cwd=<directory>] [--logLevel=<silent|info|verbose>] [--skipInstall] [--skipConfig] [--dev]
|
||||
```
|
||||
<!--/module-add-cmd-->
|
||||
|
||||
@ -31,6 +31,7 @@ Option | Default | Description
|
||||
`--logLevel=<silent\|info\|verbose>` | | Specify build-time log level
|
||||
`--skipInstall` | | Skip npm install
|
||||
`--skipConfig` | | Skip nuxt.config.ts update
|
||||
`--dev` | | Install module as dev dependency
|
||||
<!--/module-add-opts-->
|
||||
|
||||
The command lets you install [Nuxt modules](/modules) in your application with no manual work.
|
||||
|
@ -63,7 +63,7 @@ Each active version has its own nightly releases which are generated automatical
|
||||
|
||||
Release | | Initial release | End Of Life | Docs
|
||||
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
|
||||
**4.x** (scheduled) | | 2024 Q3 | |
|
||||
**4.x** (scheduled) | | approximately 1 month after release of nitro v3 | |
|
||||
**3.x** (stable) | <a href="https://npmjs.com/package/nuxt"><img alt="Nuxt latest 3.x version" src="https://flat.badgen.net/npm/v/nuxt?label=" class="not-prose"></a> | 2022-11-16 | TBA | [nuxt.com](/docs)
|
||||
**2.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 2.x version" src="https://flat.badgen.net/npm/v/nuxt/2x?label=" class="not-prose"></a> | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
|
||||
**1.x** (unsupported) | <a href="https://www.npmjs.com/package/nuxt?activeTab=versions"><img alt="Nuxt 1.x version" src="https://flat.badgen.net/npm/v/nuxt/1x?label=" class="not-prose"></a> | 2018-01-08 | 2019-09-21 |
|
||||
|
@ -166,6 +166,55 @@ export default createConfigForNuxt({
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
// manually specify dependencies for nuxt browser app
|
||||
{
|
||||
files: ['packages/nuxt/src/app/**', 'packages/nuxt/src/(components,head,imports,pages)/runtime/**'],
|
||||
name: 'local/client-packages',
|
||||
rules: {
|
||||
'@typescript-eslint/no-restricted-imports': ['error', {
|
||||
'patterns': [
|
||||
{
|
||||
allowTypeImports: true,
|
||||
group: [
|
||||
// disallow everything
|
||||
'[@a-z]*',
|
||||
// except certain dependencies
|
||||
...[
|
||||
// vue ecosystem
|
||||
'@unhead',
|
||||
'@vue',
|
||||
'@vue/shared',
|
||||
'vue/server-renderer',
|
||||
'vue',
|
||||
'vue-router',
|
||||
// other deps
|
||||
'devalue',
|
||||
'klona',
|
||||
// unjs ecosystem
|
||||
'defu',
|
||||
'ufo',
|
||||
'h3',
|
||||
'destr',
|
||||
'consola',
|
||||
'hookable',
|
||||
'unctx',
|
||||
'cookie-es',
|
||||
'perfect-debounce',
|
||||
'radix3',
|
||||
'ohash',
|
||||
'pathe',
|
||||
'uncrypto',
|
||||
// internal deps
|
||||
'nuxt/app',
|
||||
].map(r => `!${r}`),
|
||||
'!#[a-z]*/**', // aliases
|
||||
'!.*/**', // relative imports
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/fixtures/**', '**/fixture/**'],
|
||||
name: 'local/disables/fixtures',
|
||||
|
@ -26,6 +26,7 @@ exclude = [
|
||||
"https://awesome-lib.js/",
|
||||
"https://myawesome-lib.css/",
|
||||
"https://awesome-lib.css/",
|
||||
"https://mycdn.org/",
|
||||
'https://www.npmjs.com/package/(.*)importName(.*)',
|
||||
# TODO: address 404s (non-prerendered files?) from nuxt.com
|
||||
"https://nuxt.com/docs/guide/going-further/modules",
|
||||
|
60
package.json
60
package.json
@ -39,12 +39,12 @@
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@types/node": "22.10.2",
|
||||
"@unhead/dom": "1.11.14",
|
||||
"@unhead/schema": "1.11.14",
|
||||
"@unhead/shared": "1.11.14",
|
||||
"@unhead/ssr": "1.11.14",
|
||||
"@unhead/vue": "1.11.14",
|
||||
"@types/node": "22.10.5",
|
||||
"@unhead/dom": "1.11.16",
|
||||
"@unhead/schema": "1.11.16",
|
||||
"@unhead/shared": "1.11.16",
|
||||
"@unhead/ssr": "1.11.16",
|
||||
"@unhead/vue": "1.11.16",
|
||||
"@vue/compiler-core": "3.5.13",
|
||||
"@vue/compiler-dom": "3.5.13",
|
||||
"@vue/shared": "3.5.13",
|
||||
@ -56,28 +56,29 @@
|
||||
"nuxt": "workspace:*",
|
||||
"ohash": "1.1.4",
|
||||
"postcss": "8.4.49",
|
||||
"rollup": "4.29.1",
|
||||
"rollup": "4.30.1",
|
||||
"send": ">=1.1.0",
|
||||
"typescript": "5.7.2",
|
||||
"typescript": "5.7.3",
|
||||
"ufo": "1.5.4",
|
||||
"unbuild": "3.0.1",
|
||||
"unhead": "1.11.14",
|
||||
"unbuild": "3.3.0",
|
||||
"unhead": "1.11.16",
|
||||
"unimport": "3.14.5",
|
||||
"vite": "6.0.6",
|
||||
"vite": "6.0.7",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "0.17.2",
|
||||
"@nuxt/eslint-config": "0.7.4",
|
||||
"@arethetypeswrong/cli": "0.17.3",
|
||||
"@nuxt/cli": "3.20.0",
|
||||
"@nuxt/eslint-config": "0.7.5",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/rspack-builder": "workspace:*",
|
||||
"@nuxt/test-utils": "3.15.1",
|
||||
"@nuxt/test-utils": "3.15.4",
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/node": "22.10.2",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/semver": "7.5.8",
|
||||
"@unhead/schema": "1.11.14",
|
||||
"@unhead/vue": "1.11.14",
|
||||
"@unhead/schema": "1.11.16",
|
||||
"@unhead/vue": "1.11.16",
|
||||
"@vitest/coverage-v8": "2.1.8",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"autoprefixer": "10.4.20",
|
||||
@ -87,30 +88,30 @@
|
||||
"cssnano": "7.0.6",
|
||||
"destr": "2.0.3",
|
||||
"devalue": "5.1.1",
|
||||
"eslint": "9.17.0",
|
||||
"eslint": "9.18.0",
|
||||
"eslint-plugin-no-only-tests": "3.3.0",
|
||||
"eslint-plugin-perfectionist": "4.4.0",
|
||||
"eslint-typegen": "0.3.2",
|
||||
"eslint-plugin-perfectionist": "4.6.0",
|
||||
"eslint-typegen": "1.0.0",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"happy-dom": "16.0.1",
|
||||
"happy-dom": "16.5.3",
|
||||
"installed-check": "9.3.0",
|
||||
"jiti": "2.4.2",
|
||||
"knip": "5.41.1",
|
||||
"knip": "5.42.0",
|
||||
"markdownlint-cli": "0.43.0",
|
||||
"memfs": "4.15.1",
|
||||
"memfs": "4.17.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"nuxi": "3.17.2",
|
||||
"nuxt": "workspace:*",
|
||||
"nuxt-content-twoslash": "0.1.2",
|
||||
"ofetch": "1.4.1",
|
||||
"pathe": "1.1.2",
|
||||
"pathe": "2.0.1",
|
||||
"pkg-pr-new": "0.0.39",
|
||||
"playwright-core": "1.49.1",
|
||||
"semver": "7.6.3",
|
||||
"sherif": "1.1.1",
|
||||
"std-env": "3.8.0",
|
||||
"tinyexec": "0.3.1",
|
||||
"tinyexec": "0.3.2",
|
||||
"tinyglobby": "0.2.10",
|
||||
"typescript": "5.7.2",
|
||||
"typescript": "5.7.3",
|
||||
"ufo": "1.5.4",
|
||||
"vitest": "2.1.8",
|
||||
"vitest-environment-nuxt": "1.0.1",
|
||||
@ -118,9 +119,6 @@
|
||||
"vue-tsc": "2.2.0",
|
||||
"webpack": "5.97.1"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.1",
|
||||
"engines": {
|
||||
"node": "^18.20.4 || ^20.9.0 || ^22.0.0 || >=23.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.3",
|
||||
"version": ""
|
||||
}
|
||||
|
@ -37,9 +37,9 @@
|
||||
"ignore": "^7.0.0",
|
||||
"jiti": "^2.4.2",
|
||||
"klona": "^2.0.6",
|
||||
"mlly": "^1.7.3",
|
||||
"mlly": "^1.7.4",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
"pathe": "^2.0.1",
|
||||
"pkg-types": "^1.3.0",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.6.3",
|
||||
@ -52,12 +52,12 @@
|
||||
"@rspack/core": "1.1.8",
|
||||
"@types/semver": "7.5.8",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"unbuild": "3.0.1",
|
||||
"vite": "6.0.6",
|
||||
"unbuild": "3.3.0",
|
||||
"vite": "6.0.7",
|
||||
"vitest": "2.1.8",
|
||||
"webpack": "5.97.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.5"
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -95,31 +95,28 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
|
||||
paths.add(nuxtModule)
|
||||
|
||||
for (const path of paths) {
|
||||
for (const parentURL of nuxt.options.modulesDir) {
|
||||
try {
|
||||
const src = isAbsolute(path)
|
||||
? pathToFileURL(await resolvePath(path, { cwd: parentURL, fallbackToOriginal: false, extensions: nuxt.options.extensions })).href
|
||||
: await resolveModule(path, { url: pathToFileURL(parentURL.replace(/\/node_modules\/?$/, '')), extensions: nuxt.options.extensions })
|
||||
try {
|
||||
const src = isAbsolute(path)
|
||||
? pathToFileURL(await resolvePath(path, { fallbackToOriginal: false, extensions: nuxt.options.extensions })).href
|
||||
: await resolveModule(path, { url: nuxt.options.modulesDir.map(m => pathToFileURL(m.replace(/\/node_modules\/?$/, ''))), extensions: nuxt.options.extensions })
|
||||
|
||||
nuxtModule = await jiti.import(src, { default: true }) as NuxtModule
|
||||
resolvedModulePath = fileURLToPath(new URL(src))
|
||||
nuxtModule = await jiti.import(src, { default: true }) as NuxtModule
|
||||
resolvedModulePath = fileURLToPath(new URL(src))
|
||||
|
||||
// nuxt-module-builder generates a module.json with metadata including the version
|
||||
const moduleMetadataPath = new URL('module.json', src)
|
||||
if (existsSync(moduleMetadataPath)) {
|
||||
buildTimeModuleMeta = JSON.parse(await fsp.readFile(moduleMetadataPath, 'utf-8'))
|
||||
}
|
||||
break
|
||||
} catch (error: unknown) {
|
||||
const code = (error as Error & { code?: string }).code
|
||||
if (code === 'MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_UNSUPPORTED_DIR_IMPORT' || code === 'ENOTDIR') {
|
||||
continue
|
||||
}
|
||||
logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`)
|
||||
throw error
|
||||
// nuxt-module-builder generates a module.json with metadata including the version
|
||||
const moduleMetadataPath = new URL('module.json', src)
|
||||
if (existsSync(moduleMetadataPath)) {
|
||||
buildTimeModuleMeta = JSON.parse(await fsp.readFile(moduleMetadataPath, 'utf-8'))
|
||||
}
|
||||
break
|
||||
} catch (error: unknown) {
|
||||
const code = (error as Error & { code?: string }).code
|
||||
if (code === 'MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_UNSUPPORTED_DIR_IMPORT' || code === 'ENOTDIR') {
|
||||
continue
|
||||
}
|
||||
logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`)
|
||||
throw error
|
||||
}
|
||||
if (typeof nuxtModule !== 'string') { break }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import { tryUseNuxt, useNuxt } from './context'
|
||||
import { resolveNuxtModule } from './resolve'
|
||||
|
||||
/**
|
||||
* Renders given template using lodash template during build into the project buildDir
|
||||
* Renders given template during build into the virtual file system (and optionally to disk in the project `buildDir`)
|
||||
*/
|
||||
export function addTemplate<T> (_template: NuxtTemplate<T> | string) {
|
||||
const nuxt = useNuxt()
|
||||
@ -44,7 +44,7 @@ export function addServerTemplate (template: NuxtServerTemplate) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders given types using lodash template during build into the project buildDir
|
||||
* Renders given types during build to disk in the project `buildDir`
|
||||
* and register them as types.
|
||||
*/
|
||||
export function addTypeTemplate<T> (_template: NuxtTypeTemplate<T>) {
|
||||
@ -291,6 +291,10 @@ export async function _generateTypes (nuxt: Nuxt) {
|
||||
}))
|
||||
}
|
||||
|
||||
// Ensure `#build` is placed at the end of the paths object.
|
||||
// https://github.com/nuxt/nuxt/issues/30325
|
||||
sortTsPaths(tsConfig.compilerOptions.paths)
|
||||
|
||||
tsConfig.include = [...new Set(tsConfig.include.map(p => isAbsolute(p) ? relativeWithDot(nuxt.options.buildDir, p) : p))]
|
||||
tsConfig.exclude = [...new Set(tsConfig.exclude!.map(p => isAbsolute(p) ? relativeWithDot(nuxt.options.buildDir, p) : p))]
|
||||
|
||||
@ -330,6 +334,17 @@ export async function writeTypes (nuxt: Nuxt) {
|
||||
await writeFile()
|
||||
}
|
||||
|
||||
function sortTsPaths (paths: Record<string, string[]>) {
|
||||
for (const pathKey in paths) {
|
||||
if (pathKey.startsWith('#build')) {
|
||||
const pathValue = paths[pathKey]!
|
||||
// Delete & Reassign to ensure key is inserted at the end of object.
|
||||
delete paths[pathKey]
|
||||
paths[pathKey] = pathValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderAttrs (obj: Record<string, string>) {
|
||||
const attrs: string[] = []
|
||||
for (const key in obj) {
|
||||
|
@ -59,4 +59,42 @@ describe('tsConfig generation', () => {
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
||||
it('should add #build after #components to paths', async () => {
|
||||
const { tsConfig } = await _generateTypes(mockNuxtWithOptions({
|
||||
alias: {
|
||||
'~': '/my-app',
|
||||
'@': '/my-app',
|
||||
'some-custom-alias': '/my-app/some-alias',
|
||||
'#build': './build-dir',
|
||||
'#build/*': './build-dir/*',
|
||||
'#imports': './imports',
|
||||
'#components': './components',
|
||||
},
|
||||
}))
|
||||
|
||||
expect(tsConfig.compilerOptions?.paths).toMatchObject({
|
||||
'~': [
|
||||
'..',
|
||||
],
|
||||
'some-custom-alias': [
|
||||
'../some-alias',
|
||||
],
|
||||
'@': [
|
||||
'..',
|
||||
],
|
||||
'#imports': [
|
||||
'./imports',
|
||||
],
|
||||
'#components': [
|
||||
'./components',
|
||||
],
|
||||
'#build': [
|
||||
'./build-dir',
|
||||
],
|
||||
'#build/*': [
|
||||
'./build-dir/*',
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,2 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
import 'nuxi/cli'
|
||||
import '@nuxt/cli/cli'
|
||||
|
@ -22,7 +22,7 @@ export default defineBuildConfig({
|
||||
},
|
||||
},
|
||||
dependencies: [
|
||||
'nuxi',
|
||||
'@nuxt/cli',
|
||||
'vue-router',
|
||||
'ofetch',
|
||||
],
|
||||
|
@ -64,16 +64,17 @@
|
||||
"test:attw": "attw --pack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/cli": "^3.20.0",
|
||||
"@nuxt/devalue": "^2.0.2",
|
||||
"@nuxt/devtools": "^1.7.0",
|
||||
"@nuxt/kit": "workspace:*",
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@nuxt/telemetry": "^2.6.2",
|
||||
"@nuxt/telemetry": "^2.6.4",
|
||||
"@nuxt/vite-builder": "workspace:*",
|
||||
"@unhead/dom": "^1.11.14",
|
||||
"@unhead/shared": "^1.11.14",
|
||||
"@unhead/ssr": "^1.11.14",
|
||||
"@unhead/vue": "^1.11.14",
|
||||
"@unhead/dom": "^1.11.16",
|
||||
"@unhead/shared": "^1.11.16",
|
||||
"@unhead/ssr": "^1.11.16",
|
||||
"@unhead/vue": "^1.11.16",
|
||||
"@vue/shared": "^3.5.13",
|
||||
"acorn": "8.14.0",
|
||||
"c12": "^2.0.1",
|
||||
@ -97,14 +98,13 @@
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.2.0",
|
||||
"magic-string": "^0.30.17",
|
||||
"mlly": "^1.7.3",
|
||||
"mlly": "^1.7.4",
|
||||
"nanotar": "^0.1.1",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"nuxi": "^3.17.2",
|
||||
"nypm": "^0.4.1",
|
||||
"ofetch": "^1.4.1",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
"pathe": "^2.0.1",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"pkg-types": "^1.3.0",
|
||||
"radix3": "^1.1.2",
|
||||
@ -118,9 +118,9 @@
|
||||
"uncrypto": "^0.1.3",
|
||||
"unctx": "^2.4.1",
|
||||
"unenv": "^1.10.0",
|
||||
"unhead": "^1.11.14",
|
||||
"unhead": "^1.11.16",
|
||||
"unimport": "^3.14.5",
|
||||
"unplugin": "^2.1.0",
|
||||
"unplugin": "^2.1.2",
|
||||
"unplugin-vue-router": "^0.10.9",
|
||||
"unstorage": "^1.14.4",
|
||||
"untyped": "^1.5.2",
|
||||
@ -135,8 +135,8 @@
|
||||
"@types/estree": "1.0.6",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vue/compiler-sfc": "3.5.13",
|
||||
"unbuild": "3.0.1",
|
||||
"vite": "6.0.6",
|
||||
"unbuild": "3.3.0",
|
||||
"vite": "6.0.7",
|
||||
"vitest": "2.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { cloneVNode, createElementBlock, createStaticVNode, defineComponent, getCurrentInstance, h, onMounted, provide, ref } from 'vue'
|
||||
import { cloneVNode, createElementBlock, defineComponent, getCurrentInstance, h, onMounted, provide, ref } from 'vue'
|
||||
import type { ComponentInternalInstance, ComponentOptions, InjectionKey } from 'vue'
|
||||
import { isPromise } from '@vue/shared'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
import { getFragmentHTML } from './utils'
|
||||
import ServerPlaceholder from './server-placeholder'
|
||||
import { elToStaticVNode } from './utils'
|
||||
|
||||
export const clientOnlySymbol: InjectionKey<boolean> = Symbol.for('nuxt:client-only')
|
||||
|
||||
const STATIC_DIV = '<div></div>'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ClientOnly',
|
||||
inheritAttrs: false,
|
||||
@ -54,16 +56,14 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
|
||||
return (res.children === null || typeof res.children === 'string')
|
||||
? cloneVNode(res)
|
||||
: h(res)
|
||||
} else {
|
||||
const fragment = getFragmentHTML(ctx._.vnode.el ?? null) ?? ['<div></div>']
|
||||
return createStaticVNode(fragment.join(''), fragment.length)
|
||||
}
|
||||
return elToStaticVNode(ctx._.vnode.el, STATIC_DIV)
|
||||
}
|
||||
} else if (clone.template) {
|
||||
// handle runtime-compiler template
|
||||
clone.template = `
|
||||
<template v-if="mounted$">${component.template}</template>
|
||||
<template v-else><div></div></template>
|
||||
<template v-else>${STATIC_DIV}</template>
|
||||
`
|
||||
}
|
||||
|
||||
@ -105,10 +105,8 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
|
||||
return (res.children === null || typeof res.children === 'string')
|
||||
? cloneVNode(res)
|
||||
: h(res)
|
||||
} else {
|
||||
const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['<div></div>']
|
||||
return createStaticVNode(fragment.join(''), fragment.length)
|
||||
}
|
||||
return elToStaticVNode(instance?.vnode.el, STATIC_DIV)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@ -117,8 +115,7 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
|
||||
if (mounted$.value) {
|
||||
return h(setupState(...args), ctx.attrs)
|
||||
}
|
||||
const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['<div></div>']
|
||||
return createStaticVNode(fragment.join(''), fragment.length)
|
||||
return elToStaticVNode(instance?.vnode.el, STATIC_DIV)
|
||||
}
|
||||
}
|
||||
return Object.assign(setupState, { mounted$ })
|
||||
|
@ -7,7 +7,6 @@ import { type ActiveHeadEntry, type Head, injectHead } from '@unhead/vue'
|
||||
import { randomUUID } from 'uncrypto'
|
||||
import { joinURL, withQuery } from 'ufo'
|
||||
import type { FetchResponse } from 'ofetch'
|
||||
import { join } from 'pathe'
|
||||
|
||||
import type { NuxtIslandResponse } from '../types'
|
||||
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
||||
@ -37,7 +36,7 @@ async function loadComponents (source = appBaseURL, paths: NuxtIslandResponse['c
|
||||
for (const [component, item] of Object.entries(paths)) {
|
||||
if (!(components!.has(component))) {
|
||||
promises.push((async () => {
|
||||
const chunkSource = join(source, item.chunk)
|
||||
const chunkSource = joinURL(source, item.chunk)
|
||||
const c = await import(/* @vite-ignore */ chunkSource).then(m => m.default || m)
|
||||
components!.set(component, c)
|
||||
})())
|
||||
|
@ -3,6 +3,8 @@ import { Teleport, defineComponent, h, inject, provide, useId } from 'vue'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
// @ts-expect-error virtual file
|
||||
import { paths } from '#build/components-chunk'
|
||||
// @ts-expect-error virtual file
|
||||
import { buildAssetsURL } from '#internal/nuxt/paths'
|
||||
|
||||
type ExtendedComponent = Component & {
|
||||
__file: string
|
||||
@ -41,7 +43,7 @@ export default defineComponent({
|
||||
const name = (slotType.__name || slotType.name) as string
|
||||
|
||||
islandContext.components[to] = {
|
||||
chunk: import.meta.dev ? nuxtApp.$config.app.buildAssetsDir + paths[name] : paths[name],
|
||||
chunk: import.meta.dev ? buildAssetsURL(paths[name]) : paths[name],
|
||||
props: slot.props || {},
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { h } from 'vue'
|
||||
import type { Component, RendererNode } from 'vue'
|
||||
import { createStaticVNode, h } from 'vue'
|
||||
import type { Component, RendererNode, VNode } from 'vue'
|
||||
// eslint-disable-next-line
|
||||
import { isString, isPromise, isArray, isObject } from '@vue/shared'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
@ -117,9 +117,9 @@ export function vforToArray (source: any): any[] {
|
||||
* Handles `<!--[-->` Fragment elements
|
||||
* @param element the element to retrieve the HTML
|
||||
* @param withoutSlots purge all slots from the HTML string retrieved
|
||||
* @returns {string[]} An array of string which represent the content of each element. Use `.join('')` to retrieve a component vnode.el HTML
|
||||
* @returns {string[]|undefined} An array of string which represent the content of each element. Use `.join('')` to retrieve a component vnode.el HTML
|
||||
*/
|
||||
export function getFragmentHTML (element: RendererNode | null, withoutSlots = false): string[] | null {
|
||||
export function getFragmentHTML (element: RendererNode | null, withoutSlots = false): string[] | undefined {
|
||||
if (element) {
|
||||
if (element.nodeName === '#comment' && element.nodeValue === '[') {
|
||||
return getFragmentChildren(element, [], withoutSlots)
|
||||
@ -131,7 +131,6 @@ export function getFragmentHTML (element: RendererNode | null, withoutSlots = fa
|
||||
}
|
||||
return [element.outerHTML]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getFragmentChildren (element: RendererNode | null, blocks: string[] = [], withoutSlots = false) {
|
||||
@ -151,6 +150,20 @@ function getFragmentChildren (element: RendererNode | null, blocks: string[] = [
|
||||
return blocks
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a static vnode from an element
|
||||
* Default to a div if the element is not found and if a fallback is not provided
|
||||
* @param el renderer node retrieved from the component internal instance
|
||||
* @param staticNodeFallback fallback string to use if the element is not found. Must be a valid HTML string
|
||||
*/
|
||||
export function elToStaticVNode (el: RendererNode | null, staticNodeFallback?: string): VNode {
|
||||
const fragment: string[] | undefined = el ? getFragmentHTML(el) : staticNodeFallback ? [staticNodeFallback] : undefined
|
||||
if (fragment) {
|
||||
return createStaticVNode(fragment.join(''), fragment.length)
|
||||
}
|
||||
return h('div')
|
||||
}
|
||||
|
||||
function isStartFragment (element: RendererNode) {
|
||||
return element.nodeName === '#comment' && element.nodeValue === '['
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { existsSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
|
||||
import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, defineNuxtModule, findPath, logger, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, defineNuxtModule, findPath, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit'
|
||||
import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema'
|
||||
|
||||
import { distDir } from '../dirs'
|
||||
import { logger } from '../utils'
|
||||
import { componentNamesTemplate, componentsIslandsTemplate, componentsMetadataTemplate, componentsPluginTemplate, componentsTypeTemplate } from './templates'
|
||||
import { scanComponents } from './scan'
|
||||
|
||||
|
@ -5,9 +5,10 @@ import { pascalCase } from 'scule'
|
||||
import { relative } from 'pathe'
|
||||
import type { Component, ComponentsOptions } from 'nuxt/schema'
|
||||
|
||||
import { logger, tryUseNuxt } from '@nuxt/kit'
|
||||
import { tryUseNuxt } from '@nuxt/kit'
|
||||
import { QUOTE_RE, SX_RE, isVue } from '../../core/utils'
|
||||
import { installNuxtModule } from '../../core/features'
|
||||
import { logger } from '../../utils'
|
||||
|
||||
interface LoaderOptions {
|
||||
getComponents (): Component[]
|
||||
|
@ -2,11 +2,12 @@ import { readdir } from 'node:fs/promises'
|
||||
import { basename, dirname, extname, join, relative } from 'pathe'
|
||||
import { globby } from 'globby'
|
||||
import { kebabCase, pascalCase, splitByCase } from 'scule'
|
||||
import { isIgnored, logger, useNuxt } from '@nuxt/kit'
|
||||
import { isIgnored, useNuxt } from '@nuxt/kit'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import type { Component, ComponentsDir } from 'nuxt/schema'
|
||||
|
||||
import { QUOTE_RE, resolveComponentNameSegments } from '../core/utils'
|
||||
import { logger } from '../utils'
|
||||
|
||||
const ISLAND_RE = /\.island(?:\.global)?$/
|
||||
const GLOBAL_RE = /\.global(?:\.island)?$/
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { promises as fsp, mkdirSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join, relative, resolve } from 'pathe'
|
||||
import { defu } from 'defu'
|
||||
import { findPath, logger, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath } from '@nuxt/kit'
|
||||
import { findPath, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath } from '@nuxt/kit'
|
||||
import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from 'nuxt/schema'
|
||||
|
||||
import type { PluginMeta } from 'nuxt/app'
|
||||
|
||||
import { logger } from '../utils'
|
||||
import * as defaultTemplates from './templates'
|
||||
import { getNameFromPath, hasSuffix, uniqueBy } from './utils'
|
||||
import { extractMetadata, orderMap } from './plugins/plugin-metadata'
|
||||
|
@ -1,11 +1,12 @@
|
||||
import type { EventType } from '@parcel/watcher'
|
||||
import type { FSWatcher } from 'chokidar'
|
||||
import { watch as chokidarWatch } from 'chokidar'
|
||||
import { importModule, isIgnored, logger, tryResolveModule, useNuxt } from '@nuxt/kit'
|
||||
import { importModule, isIgnored, tryResolveModule, useNuxt } from '@nuxt/kit'
|
||||
import { debounce } from 'perfect-debounce'
|
||||
import { normalize, relative, resolve } from 'pathe'
|
||||
import type { Nuxt, NuxtBuilder } from 'nuxt/schema'
|
||||
|
||||
import { logger } from '../utils'
|
||||
import { generateApp as _generateApp, createApp } from './app'
|
||||
import { checkForExternalConfigurationFiles } from './external-config-files'
|
||||
import { cleanupCaches, getVueHash } from './cache'
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { findPath, logger } from '@nuxt/kit'
|
||||
import { findPath } from '@nuxt/kit'
|
||||
import { basename } from 'pathe'
|
||||
import { logger } from '../utils'
|
||||
|
||||
/**
|
||||
* Check for those external configuration files that are not compatible with Nuxt,
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { addDependency } from 'nypm'
|
||||
import { resolvePackageJSON } from 'pkg-types'
|
||||
import { logger, useNuxt } from '@nuxt/kit'
|
||||
import { useNuxt } from '@nuxt/kit'
|
||||
import { isCI, provider } from 'std-env'
|
||||
import { logger } from '../utils'
|
||||
|
||||
const isStackblitz = provider === 'stackblitz'
|
||||
|
||||
@ -52,7 +53,7 @@ export function installNuxtModule (name: string, options?: EnsurePackageInstalle
|
||||
installPrompts.add(name)
|
||||
const nuxt = useNuxt()
|
||||
return promptToInstall(name, async () => {
|
||||
const { runCommand } = await import('nuxi')
|
||||
const { runCommand } = await import('@nuxt/cli')
|
||||
await runCommand('module', ['add', name, '--cwd', nuxt.options.rootDir])
|
||||
}, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options })
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
|
||||
nitroConfig.prerender ||= {}
|
||||
nitroConfig.prerender.ignore ||= []
|
||||
nitroConfig.prerender.ignore.push(manifestPrefix)
|
||||
nitroConfig.prerender.ignore.push(joinURL(nuxt.options.app.baseURL, manifestPrefix))
|
||||
|
||||
nitroConfig.publicAssets!.unshift(
|
||||
// build manifest
|
||||
|
@ -4,7 +4,7 @@ import { join, normalize, relative, resolve } from 'pathe'
|
||||
import { createDebugger, createHooks } from 'hookable'
|
||||
import ignore from 'ignore'
|
||||
import type { LoadNuxtOptions } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addComponent, addPlugin, addPluginTemplate, addRouteMiddleware, addServerPlugin, addTypeTemplate, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addComponent, addPlugin, addPluginTemplate, addRouteMiddleware, addServerPlugin, addTypeTemplate, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit'
|
||||
import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema'
|
||||
import type { PackageJson } from 'pkg-types'
|
||||
import { readPackageJSON } from 'pkg-types'
|
||||
@ -31,6 +31,7 @@ import importsModule from '../imports/module'
|
||||
import { distDir, pkgDir } from '../dirs'
|
||||
import { version } from '../../package.json'
|
||||
import { scriptsStubsPreset } from '../imports/presets'
|
||||
import { logger } from '../utils'
|
||||
import { resolveTypePath } from './utils/types'
|
||||
import { createImportProtectionPatterns } from './plugins/import-protection'
|
||||
import { UnctxTransformPlugin } from './plugins/unctx'
|
||||
|
@ -36,7 +36,7 @@ export const createImportProtectionPatterns = (nuxt: { options: NuxtOptions }, o
|
||||
])
|
||||
}
|
||||
|
||||
for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:node_modules|presets|runtime|types))/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) {
|
||||
for (const i of [/(^|node_modules\/)@nuxt\/(cli|kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:node_modules|presets|runtime|types))/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) {
|
||||
patterns.push([i, `This module cannot be imported in ${context}.`])
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,10 @@ import type { Nuxt } from '@nuxt/schema'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import MagicString from 'magic-string'
|
||||
import { normalize } from 'pathe'
|
||||
import { logger } from '@nuxt/kit'
|
||||
import type { ObjectPlugin, PluginMeta } from 'nuxt/app'
|
||||
|
||||
import { parseAndWalk, withLocations } from '../../core/utils/parse'
|
||||
import { logger } from '../../utils'
|
||||
|
||||
const internalOrderMap = {
|
||||
// -50: pre-all (nuxt)
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { parseNodeModulePath, resolvePath } from 'mlly'
|
||||
import { isAbsolute, normalize } from 'pathe'
|
||||
import type { Plugin } from 'vite'
|
||||
import { logger, resolveAlias } from '@nuxt/kit'
|
||||
import { resolveAlias } from '@nuxt/kit'
|
||||
import type { Nuxt } from '@nuxt/schema'
|
||||
|
||||
import { pkgDir } from '../../dirs'
|
||||
import { logger } from '../../utils'
|
||||
|
||||
export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
|
||||
const exclude: string[] = ['virtual:', '\0virtual:', '/__skip_vite', '@vitest/']
|
||||
|
@ -5,7 +5,7 @@ import { resolve } from 'pathe'
|
||||
import { watch } from 'chokidar'
|
||||
import { defu } from 'defu'
|
||||
import { debounce } from 'perfect-debounce'
|
||||
import { createResolver, defineNuxtModule, importModule, logger, tryResolveModule } from '@nuxt/kit'
|
||||
import { createResolver, defineNuxtModule, importModule, tryResolveModule } from '@nuxt/kit'
|
||||
import {
|
||||
generateTypes,
|
||||
resolveSchema as resolveUntypedSchema,
|
||||
@ -13,6 +13,7 @@ import {
|
||||
import type { Schema, SchemaDefinition } from 'untyped'
|
||||
import untypedPlugin from 'untyped/babel-plugin'
|
||||
import { createJiti } from 'jiti'
|
||||
import { logger } from '../utils'
|
||||
|
||||
export default defineNuxtModule({
|
||||
meta: {
|
||||
|
@ -53,12 +53,30 @@ export function withLocations<T> (node: T): WithLocations<T> {
|
||||
return node as WithLocations<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to check whether scope A is a child of scope B.
|
||||
* @example
|
||||
* ```ts
|
||||
* isChildScope('0-1-2', '0-1') // true
|
||||
* isChildScope('0-1', '0-1') // false
|
||||
* ```
|
||||
*
|
||||
* @param a the child scope
|
||||
* @param b the parent scope
|
||||
* @returns true if scope A is a child of scope B, false otherwise (also when they are the same)
|
||||
*/
|
||||
function isChildScope (a: string, b: string) {
|
||||
return a.startsWith(b) && a.length > b.length
|
||||
}
|
||||
|
||||
abstract class BaseNode<T extends Node = Node> {
|
||||
abstract type: string
|
||||
readonly scope: string
|
||||
node: WithLocations<T>
|
||||
|
||||
constructor (node: WithLocations<T>) {
|
||||
constructor (node: WithLocations<T>, scope: string) {
|
||||
this.node = node
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,6 +90,14 @@ abstract class BaseNode<T extends Node = Node> {
|
||||
* For instance, for a function parameter, this would be the end of the function declaration.
|
||||
*/
|
||||
abstract get end (): number
|
||||
|
||||
/**
|
||||
* Check if the node is defined under a specific scope.
|
||||
* @param scope
|
||||
*/
|
||||
isUnderScope (scope: string) {
|
||||
return isChildScope(this.scope, scope)
|
||||
}
|
||||
}
|
||||
|
||||
class IdentifierNode extends BaseNode<Identifier> {
|
||||
@ -90,8 +116,8 @@ class FunctionParamNode extends BaseNode {
|
||||
type = 'FunctionParam' as const
|
||||
fnNode: WithLocations<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression>
|
||||
|
||||
constructor (node: WithLocations<Node>, fnNode: WithLocations<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression>) {
|
||||
super(node)
|
||||
constructor (node: WithLocations<Node>, scope: string, fnNode: WithLocations<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression>) {
|
||||
super(node, scope)
|
||||
this.fnNode = fnNode
|
||||
}
|
||||
|
||||
@ -120,8 +146,8 @@ class VariableNode extends BaseNode<Identifier> {
|
||||
type = 'Variable' as const
|
||||
variableNode: WithLocations<VariableDeclaration>
|
||||
|
||||
constructor (node: WithLocations<Identifier>, variableNode: WithLocations<VariableDeclaration>) {
|
||||
super(node)
|
||||
constructor (node: WithLocations<Identifier>, scope: string, variableNode: WithLocations<VariableDeclaration>) {
|
||||
super(node, scope)
|
||||
this.variableNode = variableNode
|
||||
}
|
||||
|
||||
@ -138,8 +164,8 @@ class ImportNode extends BaseNode<ImportSpecifier | ImportDefaultSpecifier | Imp
|
||||
type = 'Import' as const
|
||||
importNode: WithLocations<Node>
|
||||
|
||||
constructor (node: WithLocations<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>, importNode: WithLocations<Node>) {
|
||||
super(node)
|
||||
constructor (node: WithLocations<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>, scope: string, importNode: WithLocations<Node>) {
|
||||
super(node, scope)
|
||||
this.importNode = importNode
|
||||
}
|
||||
|
||||
@ -156,8 +182,8 @@ class CatchParamNode extends BaseNode {
|
||||
type = 'CatchParam' as const
|
||||
catchNode: WithLocations<CatchClause>
|
||||
|
||||
constructor (node: WithLocations<Node>, catchNode: WithLocations<CatchClause>) {
|
||||
super(node)
|
||||
constructor (node: WithLocations<Node>, scope: string, catchNode: WithLocations<CatchClause>) {
|
||||
super(node, scope)
|
||||
this.catchNode = catchNode
|
||||
}
|
||||
|
||||
@ -264,7 +290,7 @@ export class ScopeTracker {
|
||||
|
||||
const identifiers = getPatternIdentifiers(param)
|
||||
for (const identifier of identifiers) {
|
||||
this.declareIdentifier(identifier.name, new FunctionParamNode(identifier, fn))
|
||||
this.declareIdentifier(identifier.name, new FunctionParamNode(identifier, this.scopeIndexKey, fn))
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,10 +302,10 @@ export class ScopeTracker {
|
||||
this.declareIdentifier(
|
||||
identifier.name,
|
||||
parent.type === 'VariableDeclaration'
|
||||
? new VariableNode(identifier, parent)
|
||||
? new VariableNode(identifier, this.scopeIndexKey, parent)
|
||||
: parent.type === 'CatchClause'
|
||||
? new CatchParamNode(identifier, parent)
|
||||
: new FunctionParamNode(identifier, parent),
|
||||
? new CatchParamNode(identifier, this.scopeIndexKey, parent)
|
||||
: new FunctionParamNode(identifier, this.scopeIndexKey, parent),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -295,7 +321,7 @@ export class ScopeTracker {
|
||||
case 'FunctionDeclaration':
|
||||
// declare function name for named functions, skip for `export default`
|
||||
if (node.id?.name) {
|
||||
this.declareIdentifier(node.id.name, new FunctionNode(node))
|
||||
this.declareIdentifier(node.id.name, new FunctionNode(node, this.scopeIndexKey))
|
||||
}
|
||||
this.pushScope()
|
||||
for (const param of node.params) {
|
||||
@ -309,7 +335,7 @@ export class ScopeTracker {
|
||||
this.pushScope()
|
||||
// can be undefined, for example in class method definitions
|
||||
if (node.id?.name) {
|
||||
this.declareIdentifier(node.id.name, new FunctionNode(node))
|
||||
this.declareIdentifier(node.id.name, new FunctionNode(node, this.scopeIndexKey))
|
||||
}
|
||||
|
||||
this.pushScope()
|
||||
@ -333,7 +359,7 @@ export class ScopeTracker {
|
||||
case 'ClassDeclaration':
|
||||
// declare class name for named classes, skip for `export default`
|
||||
if (node.id?.name) {
|
||||
this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id)))
|
||||
this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id), this.scopeIndexKey))
|
||||
}
|
||||
break
|
||||
|
||||
@ -342,13 +368,13 @@ export class ScopeTracker {
|
||||
// e.g. const MyClass = class InternalClassName { // InternalClassName is only available within the class body
|
||||
this.pushScope()
|
||||
if (node.id?.name) {
|
||||
this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id)))
|
||||
this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id), this.scopeIndexKey))
|
||||
}
|
||||
break
|
||||
|
||||
case 'ImportDeclaration':
|
||||
for (const specifier of node.specifiers) {
|
||||
this.declareIdentifier(specifier.local.name, new ImportNode(withLocations(specifier), node))
|
||||
this.declareIdentifier(specifier.local.name, new ImportNode(withLocations(specifier), this.scopeIndexKey, node))
|
||||
}
|
||||
break
|
||||
|
||||
@ -429,6 +455,26 @@ export class ScopeTracker {
|
||||
return null
|
||||
}
|
||||
|
||||
getCurrentScope () {
|
||||
return this.scopeIndexKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current scope is a child of a specific scope.
|
||||
* @example
|
||||
* ```ts
|
||||
* // current scope is 0-1
|
||||
* isCurrentScopeUnder('0') // true
|
||||
* isCurrentScopeUnder('0-1') // false
|
||||
* ```
|
||||
*
|
||||
* @param scope the parent scope
|
||||
* @returns `true` if the current scope is a child of the specified scope, `false` otherwise (also when they are the same)
|
||||
*/
|
||||
isCurrentScopeUnder (scope: string) {
|
||||
return isChildScope(this.scopeIndexKey, scope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Freezes the scope tracker, preventing further declarations.
|
||||
* It also resets the scope index stack to its initial state, so that the scope tracker can be reused.
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { addBuildPlugin, addTemplate, addTypeTemplate, defineNuxtModule, isIgnored, logger, resolveAlias, tryResolveModule, updateTemplates, useNuxt } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addTemplate, addTypeTemplate, defineNuxtModule, isIgnored, resolveAlias, tryResolveModule, updateTemplates, useNuxt } from '@nuxt/kit'
|
||||
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
|
||||
import type { Import, Unimport } from 'unimport'
|
||||
import { createUnimport, scanDirExports, toExports } from 'unimport'
|
||||
@ -7,7 +7,7 @@ import type { ImportPresetWithDeprecation, ImportsOptions, ResolvedNuxtTemplate
|
||||
import escapeRE from 'escape-string-regexp'
|
||||
|
||||
import { lookupNodeModuleSubpath, parseNodeModulePath } from 'mlly'
|
||||
import { isDirectory } from '../utils'
|
||||
import { isDirectory, logger } from '../utils'
|
||||
import { TransformPlugin } from './transform'
|
||||
import { defaultPresets } from './presets'
|
||||
|
||||
|
10
packages/nuxt/src/pages/build.d.ts
vendored
10
packages/nuxt/src/pages/build.d.ts
vendored
@ -5,3 +5,13 @@ declare module '#build/router.options' {
|
||||
const _default: RouterOptions
|
||||
export default _default
|
||||
}
|
||||
|
||||
declare module '#build/routes' {
|
||||
import type { RouterOptions } from '@nuxt/schema'
|
||||
import type { Router, RouterOptions as VueRouterOptions } from 'vue-router'
|
||||
|
||||
export const handleHotUpdate: (_router: Router, _generateRoutes: RouterOptions['routes']) => void
|
||||
|
||||
const _default: VueRouterOptions['routes']
|
||||
export default _default
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { existsSync, readdirSync } from 'node:fs'
|
||||
import { mkdir, readFile } from 'node:fs/promises'
|
||||
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, defineNuxtModule, findPath, logger, resolvePath, updateTemplates, useNitro } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, defineNuxtModule, findPath, resolvePath, updateTemplates, useNitro } from '@nuxt/kit'
|
||||
import { dirname, join, relative, resolve } from 'pathe'
|
||||
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
|
||||
import { joinURL } from 'ufo'
|
||||
@ -14,6 +14,7 @@ import type { NitroRouteConfig } from 'nitro/types'
|
||||
import { defu } from 'defu'
|
||||
import { distDir } from '../dirs'
|
||||
import { resolveTypePath } from '../core/utils/types'
|
||||
import { logger } from '../utils'
|
||||
import { normalizeRoutes, resolvePagesRoutes, resolveRoutePaths } from './utils'
|
||||
import { extractRouteRules, getMappedPages } from './route-rules'
|
||||
import { PageMetaPlugin } from './plugins/page-meta'
|
||||
@ -75,7 +76,7 @@ export default defineNuxtModule({
|
||||
return true
|
||||
}
|
||||
|
||||
const pages = await resolvePagesRoutes()
|
||||
const pages = await resolvePagesRoutes(nuxt)
|
||||
if (pages.length) {
|
||||
if (nuxt.apps.default) {
|
||||
nuxt.apps.default.pages = pages
|
||||
@ -93,7 +94,7 @@ export default defineNuxtModule({
|
||||
}
|
||||
|
||||
nuxt.hook('app:templates', async (app) => {
|
||||
app.pages = await resolvePagesRoutes()
|
||||
app.pages = await resolvePagesRoutes(nuxt)
|
||||
|
||||
if (!nuxt.options.ssr && app.pages.some(p => p.mode === 'server')) {
|
||||
logger.warn('Using server pages with `ssr: false` is not supported with auto-detected component islands. Set `experimental.componentIslands` to `true`.')
|
||||
@ -178,7 +179,7 @@ export default defineNuxtModule({
|
||||
logs: nuxt.options.debug,
|
||||
async beforeWriteFiles (rootPage) {
|
||||
rootPage.children.forEach(child => child.delete())
|
||||
const pages = nuxt.apps.default?.pages || await resolvePagesRoutes()
|
||||
const pages = nuxt.apps.default?.pages || await resolvePagesRoutes(nuxt)
|
||||
if (nuxt.apps.default) {
|
||||
nuxt.apps.default.pages = pages
|
||||
}
|
||||
@ -630,22 +631,32 @@ const ROUTES_HMR_CODE = /* js */`
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept((mod) => {
|
||||
const router = import.meta.hot.data.router
|
||||
if (!router) {
|
||||
const generateRoutes = import.meta.hot.data.generateRoutes
|
||||
if (!router || !generateRoutes) {
|
||||
import.meta.hot.invalidate('[nuxt] Cannot replace routes because there is no active router. Reloading.')
|
||||
return
|
||||
}
|
||||
router.clearRoutes()
|
||||
for (const route of mod.default || mod) {
|
||||
router.addRoute(route)
|
||||
const routes = generateRoutes(mod.default || mod)
|
||||
function addRoutes (routes) {
|
||||
for (const route of routes) {
|
||||
router.addRoute(route)
|
||||
}
|
||||
router.replace(router.currentRoute.value.fullPath)
|
||||
}
|
||||
if (routes && 'then' in routes) {
|
||||
routes.then(addRoutes)
|
||||
} else {
|
||||
addRoutes(routes)
|
||||
}
|
||||
router.replace('')
|
||||
})
|
||||
}
|
||||
|
||||
export function handleHotUpdate(_router) {
|
||||
export function handleHotUpdate(_router, _generateRoutes) {
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.data ||= {}
|
||||
import.meta.hot.data.router = _router
|
||||
import.meta.hot.data.generateRoutes = _generateRoutes
|
||||
}
|
||||
}
|
||||
`
|
||||
|
@ -5,7 +5,6 @@ import type { StaticImport } from 'mlly'
|
||||
import { findExports, findStaticImports, parseStaticImport } from 'mlly'
|
||||
import MagicString from 'magic-string'
|
||||
import { isAbsolute } from 'pathe'
|
||||
import { logger } from '@nuxt/kit'
|
||||
|
||||
import {
|
||||
ScopeTracker,
|
||||
@ -16,6 +15,7 @@ import {
|
||||
walk,
|
||||
withLocations,
|
||||
} from '../../core/utils/parse'
|
||||
import { logger } from '../../utils'
|
||||
|
||||
interface PageMetaPluginOptions {
|
||||
dev?: boolean
|
||||
@ -173,7 +173,9 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
||||
}
|
||||
}
|
||||
|
||||
const scopeTracker = new ScopeTracker()
|
||||
const scopeTracker = new ScopeTracker({
|
||||
keepExitedScopes: true,
|
||||
})
|
||||
|
||||
function processDeclaration (scopeTrackerNode: ScopeTrackerNode | null) {
|
||||
if (scopeTrackerNode?.type === 'Variable') {
|
||||
@ -184,7 +186,7 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
||||
walk(decl.init, {
|
||||
enter: (node, parent) => {
|
||||
if (node.type === 'AwaitExpression') {
|
||||
logger.error(`[nuxt] Await expressions are not supported in definePageMeta. File: '${id}'`)
|
||||
logger.error(`Await expressions are not supported in definePageMeta. File: '${id}'`)
|
||||
throw new Error('await in definePageMeta')
|
||||
}
|
||||
if (
|
||||
@ -210,7 +212,13 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
||||
}
|
||||
}
|
||||
|
||||
parseAndWalk(code, id, {
|
||||
const ast = parseAndWalk(code, id, {
|
||||
scopeTracker,
|
||||
})
|
||||
|
||||
scopeTracker.freeze()
|
||||
|
||||
walk(ast, {
|
||||
scopeTracker,
|
||||
enter: (node) => {
|
||||
if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return }
|
||||
@ -220,17 +228,34 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
||||
|
||||
if (!meta) { return }
|
||||
|
||||
const definePageMetaScope = scopeTracker.getCurrentScope()
|
||||
|
||||
walk(meta, {
|
||||
scopeTracker,
|
||||
enter (node, parent) {
|
||||
if (
|
||||
isNotReferencePosition(node, parent)
|
||||
|| node.type !== 'Identifier' // checking for `node.type` to narrow down the type
|
||||
) { return }
|
||||
|
||||
const declaration = scopeTracker.getDeclaration(node.name)
|
||||
if (declaration) {
|
||||
// check if the declaration was made inside `definePageMeta` and if so, do not process it
|
||||
// (ensures that we don't hoist local variables in inline middleware, for example)
|
||||
if (
|
||||
declaration.isUnderScope(definePageMetaScope)
|
||||
// ensures that we compare the correct declaration to the reference
|
||||
// (when in the same scope, the declaration must come before the reference, otherwise it must be in a parent scope)
|
||||
&& (scopeTracker.isCurrentScopeUnder(declaration.scope) || declaration.start < node.start)
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (isStaticIdentifier(node.name)) {
|
||||
addImport(node.name)
|
||||
} else {
|
||||
processDeclaration(scopeTracker.getDeclaration(node.name))
|
||||
} else if (declaration) {
|
||||
processDeclaration(declaration)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -5,7 +5,6 @@ import defu from 'defu'
|
||||
|
||||
import { defineNuxtPlugin, useRuntimeConfig } from '#app/nuxt'
|
||||
import { prerenderRoutes } from '#app/composables/ssr'
|
||||
// @ts-expect-error virtual file
|
||||
import _routes from '#build/routes'
|
||||
import routerOptions, { hashMode } from '#build/router.options'
|
||||
// @ts-expect-error virtual file
|
||||
@ -39,7 +38,7 @@ function shouldPrerender (path: string) {
|
||||
return !_routeRulesMatcher || defu({} as Record<string, any>, ..._routeRulesMatcher.matchAll(path).reverse()).prerender
|
||||
}
|
||||
|
||||
function processRoutes (routes: RouteRecordRaw[], currentPath = '/', routesToPrerender = new Set<string>()) {
|
||||
function processRoutes (routes: readonly RouteRecordRaw[], currentPath = '/', routesToPrerender = new Set<string>()) {
|
||||
for (const route of routes) {
|
||||
// Add root of optional dynamic paths and catchalls
|
||||
if (OPTIONAL_PARAM_RE.test(route.path) && !route.children?.length && shouldPrerender(currentPath)) {
|
||||
|
@ -17,7 +17,6 @@ import { navigateTo } from '#app/composables/router'
|
||||
|
||||
// @ts-expect-error virtual file
|
||||
import { appManifest as isAppManifestEnabled } from '#build/nuxt.config.mjs'
|
||||
// @ts-expect-error virtual file
|
||||
import _routes, { handleHotUpdate } from '#build/routes'
|
||||
import routerOptions, { hashMode } from '#build/router.options'
|
||||
// @ts-expect-error virtual file
|
||||
@ -88,7 +87,7 @@ const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
|
||||
routes,
|
||||
})
|
||||
|
||||
handleHotUpdate(router)
|
||||
handleHotUpdate(router, routerOptions.routes ? routerOptions.routes : routes => routes)
|
||||
|
||||
if (import.meta.client && 'scrollRestoration' in window.history) {
|
||||
window.history.scrollRestoration = 'auto'
|
||||
|
@ -2,7 +2,7 @@ import { runInNewContext } from 'node:vm'
|
||||
import fs from 'node:fs'
|
||||
import { extname, normalize, relative, resolve } from 'pathe'
|
||||
import { encodePath, joinURL, withLeadingSlash } from 'ufo'
|
||||
import { logger, resolveFiles, resolvePath, useNuxt } from '@nuxt/kit'
|
||||
import { resolveFiles, resolvePath, useNuxt } from '@nuxt/kit'
|
||||
import { genArrayFromRaw, genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
|
||||
import escapeRE from 'escape-string-regexp'
|
||||
import { filename } from 'pathe/utils'
|
||||
@ -11,9 +11,9 @@ import { transform } from 'esbuild'
|
||||
import type { Property } from 'estree'
|
||||
import type { NuxtPage } from 'nuxt/schema'
|
||||
|
||||
import { parseAndWalk } from '../core/utils/parse'
|
||||
import { parseAndWalk, withLocations } from '../core/utils/parse'
|
||||
import { getLoader, uniqueBy } from '../core/utils'
|
||||
import { toArray } from '../utils'
|
||||
import { logger, toArray } from '../utils'
|
||||
|
||||
enum SegmentParserState {
|
||||
initial,
|
||||
@ -42,9 +42,7 @@ interface ScannedFile {
|
||||
absolutePath: string
|
||||
}
|
||||
|
||||
export async function resolvePagesRoutes (): Promise<NuxtPage[]> {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
export async function resolvePagesRoutes (nuxt = useNuxt()): Promise<NuxtPage[]> {
|
||||
const pagesDirs = nuxt.options._layers.map(
|
||||
layer => resolve(layer.config.srcDir, (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.pages || 'pages'),
|
||||
)
|
||||
@ -249,25 +247,27 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr
|
||||
const property = pageMetaArgument.properties.find((property): property is Property => property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === key)
|
||||
if (!property) { continue }
|
||||
|
||||
if (property.value.type === 'ObjectExpression') {
|
||||
const valueString = js.code.slice(property.value.range![0], property.value.range![1])
|
||||
const propertyValue = withLocations(property.value)
|
||||
|
||||
if (propertyValue.type === 'ObjectExpression') {
|
||||
const valueString = js.code.slice(propertyValue.start, propertyValue.end)
|
||||
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}\`).`)
|
||||
logger.debug(`Skipping extraction of \`${key}\` metadata as it is not JSON-serializable (reading \`${absolutePath}\`).`)
|
||||
dynamicProperties.add(key)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (property.value.type === 'ArrayExpression') {
|
||||
if (propertyValue.type === 'ArrayExpression') {
|
||||
const values: string[] = []
|
||||
for (const element of property.value.elements) {
|
||||
for (const element of propertyValue.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}\`).`)
|
||||
logger.debug(`Skipping extraction of \`${key}\` metadata as it is not an array of string literals (reading \`${absolutePath}\`).`)
|
||||
dynamicProperties.add(key)
|
||||
continue
|
||||
}
|
||||
@ -277,12 +277,12 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr
|
||||
continue
|
||||
}
|
||||
|
||||
if (property.value.type !== 'Literal' || (typeof property.value.value !== 'string' && typeof property.value.value !== 'boolean')) {
|
||||
console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not a string literal or array of string literals (reading \`${absolutePath}\`).`)
|
||||
if (propertyValue.type !== 'Literal' || (typeof propertyValue.value !== 'string' && typeof propertyValue.value !== 'boolean')) {
|
||||
logger.debug(`Skipping extraction of \`${key}\` metadata as it is not a string literal or array of string literals (reading \`${absolutePath}\`).`)
|
||||
dynamicProperties.add(key)
|
||||
continue
|
||||
}
|
||||
extractedMeta[key] = property.value.value
|
||||
extractedMeta[key] = propertyValue.value
|
||||
}
|
||||
|
||||
for (const property of pageMetaArgument.properties) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import { useLogger } from '@nuxt/kit'
|
||||
|
||||
/** @since 3.9.0 */
|
||||
export function toArray<T> (value: T | T[]): T[] {
|
||||
@ -8,3 +9,5 @@ export function toArray<T> (value: T | T[]): T[] {
|
||||
export async function isDirectory (path: string) {
|
||||
return (await fsp.lstat(path)).isDirectory()
|
||||
}
|
||||
|
||||
export const logger = useLogger('nuxt')
|
||||
|
@ -73,7 +73,6 @@ definePageMeta({ name: 'bar' })
|
||||
const meta = await getRouteMeta(`
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
name: 'some-custom-name',
|
||||
path: '/some-custom-path',
|
||||
validate: () => true,
|
||||
middleware: [
|
||||
@ -82,19 +81,32 @@ definePageMeta({ name: 'bar' })
|
||||
otherValue: {
|
||||
foo: 'bar',
|
||||
},
|
||||
// 'name', 'props' and 'alias' are part of 'defaultExtractionKeys'; they're extracted from the component, so we should test the AST walking for different value types
|
||||
name: 'some-custom-name',
|
||||
props: {
|
||||
foo: 'bar',
|
||||
},
|
||||
alias: ['/alias'],
|
||||
})
|
||||
</script>
|
||||
`, filePath)
|
||||
|
||||
expect(meta).toMatchInlineSnapshot(`
|
||||
{
|
||||
"alias": [
|
||||
"/alias",
|
||||
],
|
||||
"meta": {
|
||||
"__nuxt_dynamic_meta_key": Set {
|
||||
"props",
|
||||
"meta",
|
||||
},
|
||||
},
|
||||
"name": "some-custom-name",
|
||||
"path": "/some-custom-path",
|
||||
"props": {
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
`)
|
||||
})
|
||||
@ -381,6 +393,188 @@ definePageMeta({
|
||||
`)
|
||||
})
|
||||
|
||||
it('should not import static identifiers when shadowed in the same scope', () => {
|
||||
const sfc = `
|
||||
<script setup lang="ts">
|
||||
import { useState } from '#app/composables/state'
|
||||
|
||||
definePageMeta({
|
||||
middleware: () => {
|
||||
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||
const auth = useState('auth')
|
||||
if (!auth.value.isLoggedIn) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
`
|
||||
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||
expect(transformPlugin.transform.call({
|
||||
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 'latest',
|
||||
locations: true,
|
||||
...opts,
|
||||
}),
|
||||
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
||||
"const __nuxt_page_meta = {
|
||||
middleware: () => {
|
||||
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||
const auth = useState('auth')
|
||||
if (!auth.value.isLoggedIn) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
},
|
||||
}
|
||||
export default __nuxt_page_meta"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should not import static identifiers when shadowed in parent scope', () => {
|
||||
const sfc = `
|
||||
<script setup lang="ts">
|
||||
import { useState } from '#app/composables/state'
|
||||
|
||||
definePageMeta({
|
||||
middleware: () => {
|
||||
function isLoggedIn() {
|
||||
const auth = useState('auth')
|
||||
return auth.value.isLoggedIn
|
||||
}
|
||||
|
||||
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||
if (!isLoggedIn()) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
`
|
||||
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||
expect(transformPlugin.transform.call({
|
||||
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 'latest',
|
||||
locations: true,
|
||||
...opts,
|
||||
}),
|
||||
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
||||
"const __nuxt_page_meta = {
|
||||
middleware: () => {
|
||||
function isLoggedIn() {
|
||||
const auth = useState('auth')
|
||||
return auth.value.isLoggedIn
|
||||
}
|
||||
|
||||
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||
if (!isLoggedIn()) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
},
|
||||
}
|
||||
export default __nuxt_page_meta"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should import static identifiers when a shadowed and a non-shadowed one is used', () => {
|
||||
const sfc = `
|
||||
<script setup lang="ts">
|
||||
import { useState } from '#app/composables/state'
|
||||
|
||||
definePageMeta({
|
||||
middleware: [
|
||||
() => {
|
||||
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||
const auth = useState('auth')
|
||||
if (!auth.value.isLoggedIn) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
},
|
||||
() => {
|
||||
const auth = useState('auth')
|
||||
if (!auth.value.isLoggedIn) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
</script>
|
||||
`
|
||||
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||
expect(transformPlugin.transform.call({
|
||||
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 'latest',
|
||||
locations: true,
|
||||
...opts,
|
||||
}),
|
||||
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
||||
"import { useState } from '#app/composables/state'
|
||||
|
||||
const __nuxt_page_meta = {
|
||||
middleware: [
|
||||
() => {
|
||||
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||
const auth = useState('auth')
|
||||
if (!auth.value.isLoggedIn) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
},
|
||||
() => {
|
||||
const auth = useState('auth')
|
||||
if (!auth.value.isLoggedIn) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
export default __nuxt_page_meta"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should import static identifiers when a shadowed and a non-shadowed one is used in the same scope', () => {
|
||||
const sfc = `
|
||||
<script setup lang="ts">
|
||||
import { useState } from '#app/composables/state'
|
||||
|
||||
definePageMeta({
|
||||
middleware: () => {
|
||||
const auth1 = useState('auth')
|
||||
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||
const auth2 = useState('auth')
|
||||
if (!auth1.value.isLoggedIn || !auth2.value.isLoggedIn) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
`
|
||||
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||
expect(transformPlugin.transform.call({
|
||||
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 'latest',
|
||||
locations: true,
|
||||
...opts,
|
||||
}),
|
||||
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
||||
"import { useState } from '#app/composables/state'
|
||||
|
||||
const __nuxt_page_meta = {
|
||||
middleware: () => {
|
||||
const auth1 = useState('auth')
|
||||
const useState = (key) => ({ value: { isLoggedIn: false } })
|
||||
const auth2 = useState('auth')
|
||||
if (!auth1.value.isLoggedIn || !auth2.value.isLoggedIn) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
},
|
||||
}
|
||||
export default __nuxt_page_meta"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should work with esbuild.keepNames = true', async () => {
|
||||
const sfc = `
|
||||
<script setup lang="ts">
|
||||
@ -485,6 +679,8 @@ function recursive () {
|
||||
recursive()
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
definePageMeta({
|
||||
middleware: [
|
||||
() => {
|
||||
@ -501,9 +697,24 @@ definePageMeta({
|
||||
prop = 'prop'
|
||||
test () {}
|
||||
}
|
||||
|
||||
const someFunction = () => {
|
||||
const someValue = 'someValue'
|
||||
console.log(someValue)
|
||||
}
|
||||
|
||||
console.log(hoisted.value, val)
|
||||
},
|
||||
],
|
||||
validate: (route) => {
|
||||
return route.params.id === 'test'
|
||||
}
|
||||
})
|
||||
|
||||
// the order of a ref relative to the 'definePageMeta' call should be preserved (in contrast to a simple const)
|
||||
// this tests whether the extraction handles all variables in the upper scope
|
||||
const hoisted = ref('hoisted')
|
||||
|
||||
</script>
|
||||
`
|
||||
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||
@ -522,6 +733,7 @@ definePageMeta({
|
||||
function recursive () {
|
||||
recursive()
|
||||
}
|
||||
const hoisted = ref('hoisted')
|
||||
const __nuxt_page_meta = {
|
||||
middleware: [
|
||||
() => {
|
||||
@ -538,8 +750,18 @@ definePageMeta({
|
||||
prop = 'prop'
|
||||
test () {}
|
||||
}
|
||||
|
||||
const someFunction = () => {
|
||||
const someValue = 'someValue'
|
||||
console.log(someValue)
|
||||
}
|
||||
|
||||
console.log(hoisted.value, val)
|
||||
},
|
||||
],
|
||||
validate: (route) => {
|
||||
return route.params.id === 'test'
|
||||
}
|
||||
}
|
||||
export default __nuxt_page_meta"
|
||||
`)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { assert, describe, expect, it } from 'vitest'
|
||||
import { getUndeclaredIdentifiersInFunction, parseAndWalk } from '../src/core/utils/parse'
|
||||
import { TestScopeTracker } from './fixture/scope-tracker'
|
||||
|
||||
@ -667,4 +667,87 @@ describe('parsing', () => {
|
||||
|
||||
expect(processedFunctions).toBe(5)
|
||||
})
|
||||
|
||||
it ('should correctly compare identifiers defined in different scopes', () => {
|
||||
const code = `
|
||||
// ""
|
||||
const a = 1
|
||||
|
||||
// ""
|
||||
const func = () => {
|
||||
// "0-0"
|
||||
const b = 2
|
||||
|
||||
// "0-0"
|
||||
function foo() {
|
||||
// "0-0-0-0"
|
||||
const c = 3
|
||||
}
|
||||
}
|
||||
|
||||
// ""
|
||||
const func2 = () => {
|
||||
// "1-0"
|
||||
const d = 2
|
||||
|
||||
// "1-0"
|
||||
function bar() {
|
||||
// "1-0-0-0"
|
||||
const e = 3
|
||||
}
|
||||
}
|
||||
|
||||
// ""
|
||||
const f = 4
|
||||
`
|
||||
|
||||
const scopeTracker = new TestScopeTracker({
|
||||
keepExitedScopes: true,
|
||||
})
|
||||
|
||||
parseAndWalk(code, filename, {
|
||||
scopeTracker,
|
||||
})
|
||||
|
||||
const a = scopeTracker.getDeclarationFromScope('a', '')
|
||||
const func = scopeTracker.getDeclarationFromScope('func', '')
|
||||
const foo = scopeTracker.getDeclarationFromScope('foo', '0-0')
|
||||
const b = scopeTracker.getDeclarationFromScope('b', '0-0')
|
||||
const c = scopeTracker.getDeclarationFromScope('c', '0-0-0-0')
|
||||
const func2 = scopeTracker.getDeclarationFromScope('func2', '')
|
||||
const bar = scopeTracker.getDeclarationFromScope('bar', '1-0')
|
||||
const d = scopeTracker.getDeclarationFromScope('d', '1-0')
|
||||
const e = scopeTracker.getDeclarationFromScope('e', '1-0-0-0')
|
||||
const f = scopeTracker.getDeclarationFromScope('f', '')
|
||||
|
||||
assert(a && func && foo && b && c && func2 && bar && d && e && f, 'All declarations should be found')
|
||||
|
||||
// identifiers in the same scope should be equal
|
||||
expect(f.isUnderScope(a.scope)).toBe(false)
|
||||
expect(func.isUnderScope(a.scope)).toBe(false)
|
||||
expect(d.isUnderScope(bar.scope)).toBe(false)
|
||||
|
||||
// identifiers in deeper scopes should be under the scope of the parent scope
|
||||
expect(b.isUnderScope(a.scope)).toBe(true)
|
||||
expect(b.isUnderScope(func.scope)).toBe(true)
|
||||
expect(c.isUnderScope(a.scope)).toBe(true)
|
||||
expect(c.isUnderScope(b.scope)).toBe(true)
|
||||
expect(d.isUnderScope(a.scope)).toBe(true)
|
||||
expect(d.isUnderScope(func2.scope)).toBe(true)
|
||||
expect(e.isUnderScope(a.scope)).toBe(true)
|
||||
expect(e.isUnderScope(d.scope)).toBe(true)
|
||||
|
||||
// identifiers in parent scope should not be under the scope of the children
|
||||
expect(a.isUnderScope(b.scope)).toBe(false)
|
||||
expect(a.isUnderScope(c.scope)).toBe(false)
|
||||
expect(a.isUnderScope(d.scope)).toBe(false)
|
||||
expect(a.isUnderScope(e.scope)).toBe(false)
|
||||
expect(b.isUnderScope(c.scope)).toBe(false)
|
||||
|
||||
// identifiers in parallel scopes should not influence each other
|
||||
expect(d.isUnderScope(b.scope)).toBe(false)
|
||||
expect(e.isUnderScope(b.scope)).toBe(false)
|
||||
expect(b.isUnderScope(d.scope)).toBe(false)
|
||||
expect(c.isUnderScope(e.scope)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { resolve } from 'pathe'
|
||||
import { consola } from 'consola'
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import type { ComponentsDir } from 'nuxt/schema'
|
||||
|
||||
@ -10,6 +11,7 @@ const rFixture = (...p: string[]) => resolve(fixtureDir, ...p)
|
||||
|
||||
vi.mock('@nuxt/kit', () => ({
|
||||
isIgnored: () => false,
|
||||
useLogger: () => consola.create({}).withTag('nuxt'),
|
||||
}))
|
||||
|
||||
const dirs: ComponentsDir[] = [
|
||||
|
@ -46,11 +46,10 @@
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"jiti": "^2.4.2",
|
||||
"knitwork": "^1.2.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"magic-string": "^0.30.17",
|
||||
"memfs": "^4.15.1",
|
||||
"memfs": "^4.17.0",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
"pathe": "^2.0.1",
|
||||
"pify": "^6.1.0",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-import": "^16.1.0",
|
||||
@ -62,7 +61,7 @@
|
||||
"time-fix-plugin": "^2.0.7",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^2.1.0",
|
||||
"unplugin": "^2.1.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-bundle-renderer": "^2.1.1",
|
||||
"vue-loader": "^17.4.2",
|
||||
@ -73,18 +72,17 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
"rollup": "4.29.1",
|
||||
"unbuild": "3.0.1",
|
||||
"rollup": "4.30.1",
|
||||
"unbuild": "3.3.0",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.5 || ^20.9.0 || >=22.0.0"
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -20,45 +20,60 @@ export default defineBuildConfig({
|
||||
'src/index',
|
||||
'src/builder-env',
|
||||
],
|
||||
hooks: {
|
||||
'rollup:options' (ctx, options) {
|
||||
ctx.options.rollup.dts.respectExternal = false
|
||||
const isExternal = options.external! as (id: string, importer?: string, isResolved?: boolean) => boolean
|
||||
options.external = (source, importer, isResolved) => {
|
||||
if (source === 'untyped' || source === 'knitwork') {
|
||||
return false
|
||||
}
|
||||
return isExternal(source, importer, isResolved)
|
||||
}
|
||||
},
|
||||
},
|
||||
externals: [
|
||||
// Type imports
|
||||
'nuxt/app',
|
||||
'cssnano',
|
||||
'autoprefixer',
|
||||
'ofetch',
|
||||
'vue-router',
|
||||
'vue-bundle-renderer',
|
||||
'@unhead/schema',
|
||||
'vue',
|
||||
'unctx',
|
||||
'hookable',
|
||||
'nitro',
|
||||
'nitropack',
|
||||
'webpack',
|
||||
'webpack-bundle-analyzer',
|
||||
'rollup-plugin-visualizer',
|
||||
'vite',
|
||||
'@vitejs/plugin-vue',
|
||||
'@vitejs/plugin-vue-jsx',
|
||||
'mini-css-extract-plugin',
|
||||
'css-minimizer-webpack-plugin',
|
||||
'webpack-dev-middleware',
|
||||
'h3',
|
||||
'webpack-hot-middleware',
|
||||
'postcss',
|
||||
'@vue/language-core',
|
||||
'autoprefixer',
|
||||
'c12',
|
||||
'compatx',
|
||||
'consola',
|
||||
'ignore',
|
||||
'vue-loader',
|
||||
'css-minimizer-webpack-plugin',
|
||||
'cssnano',
|
||||
'esbuild-loader',
|
||||
'file-loader',
|
||||
'h3',
|
||||
'hookable',
|
||||
'ignore',
|
||||
'mini-css-extract-plugin',
|
||||
'nitro',
|
||||
'nitropack',
|
||||
'nuxt/app',
|
||||
'ofetch',
|
||||
'pkg-types',
|
||||
'postcss',
|
||||
'pug',
|
||||
'rollup-plugin-visualizer',
|
||||
'sass-loader',
|
||||
'c12',
|
||||
'@vue/language-core',
|
||||
'scule',
|
||||
'unctx',
|
||||
'unimport',
|
||||
'vite',
|
||||
'vue',
|
||||
'vue-bundle-renderer',
|
||||
'vue-loader',
|
||||
'vue-router',
|
||||
'webpack',
|
||||
'webpack-bundle-analyzer',
|
||||
'webpack-dev-middleware',
|
||||
'webpack-hot-middleware',
|
||||
// Implicit
|
||||
'@vue/compiler-core',
|
||||
'@vue/compiler-sfc',
|
||||
'@vue/shared',
|
||||
'untyped',
|
||||
],
|
||||
})
|
||||
|
@ -37,22 +37,29 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pug": "2.0.10",
|
||||
"@unhead/schema": "1.11.14",
|
||||
"@unhead/schema": "1.11.16",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "4.1.1",
|
||||
"@vue/compiler-core": "3.5.13",
|
||||
"@vue/compiler-sfc": "3.5.13",
|
||||
"@vue/language-core": "2.2.0",
|
||||
"c12": "2.0.1",
|
||||
"compatx": "0.1.8",
|
||||
"esbuild-loader": "4.2.2",
|
||||
"file-loader": "6.2.0",
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"hookable": "5.5.3",
|
||||
"ignore": "7.0.0",
|
||||
"nitro": "npm:nitro-nightly@3.0.0-beta-28796231.359af68d",
|
||||
"ofetch": "1.4.1",
|
||||
"pkg-types": "1.3.0",
|
||||
"sass-loader": "16.0.4",
|
||||
"unbuild": "3.0.1",
|
||||
"scule": "1.3.0",
|
||||
"unbuild": "3.3.0",
|
||||
"unctx": "2.4.1",
|
||||
"vite": "6.0.6",
|
||||
"unimport": "3.14.5",
|
||||
"untyped": "1.5.2",
|
||||
"vite": "6.0.7",
|
||||
"vue": "3.5.13",
|
||||
"vue-bundle-renderer": "2.1.1",
|
||||
"vue-loader": "17.4.2",
|
||||
@ -61,19 +68,10 @@
|
||||
"webpack-dev-middleware": "7.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"c12": "^2.0.1",
|
||||
"compatx": "^0.1.8",
|
||||
"consola": "^3.3.3",
|
||||
"defu": "^6.1.4",
|
||||
"hookable": "^5.5.3",
|
||||
"pathe": "^1.1.2",
|
||||
"pkg-types": "^1.3.0",
|
||||
"scule": "^1.3.0",
|
||||
"std-env": "^3.8.0",
|
||||
"ufo": "^1.5.4",
|
||||
"uncrypto": "^0.1.3",
|
||||
"unimport": "^3.14.5",
|
||||
"untyped": "^1.5.2"
|
||||
"pathe": "^2.0.1",
|
||||
"std-env": "^3.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
|
@ -85,20 +85,14 @@ export default defineUntypedSchema({
|
||||
},
|
||||
|
||||
/**
|
||||
* You can provide your own templates which will be rendered based
|
||||
* on Nuxt configuration. This feature is specially useful for using with modules.
|
||||
* It is recommended to use `addTemplate` from `@nuxt/kit` instead of this option.
|
||||
*
|
||||
* Templates are rendered using [`lodash/template`](https://lodash.com/docs/4.17.15#template).
|
||||
* @example
|
||||
* ```js
|
||||
* templates: [
|
||||
* {
|
||||
* src: '~/modules/support/plugin.js', // `src` can be absolute or relative
|
||||
* dst: 'support.js', // `dst` is relative to project `.nuxt` dir
|
||||
* options: {
|
||||
* // Options are provided to template as `options` key
|
||||
* live_chat: false
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* ```
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { readdir } from 'node:fs/promises'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { defineUntypedSchema } from 'untyped'
|
||||
import { basename, join, relative, resolve } from 'pathe'
|
||||
import { isDebug, isDevelopment, isTest } from 'std-env'
|
||||
import { defu } from 'defu'
|
||||
import { findWorkspaceDir } from 'pkg-types'
|
||||
import { randomUUID } from 'uncrypto'
|
||||
|
||||
import type { RuntimeConfig } from '../types/config'
|
||||
|
||||
export default defineUntypedSchema({
|
||||
|
@ -16,7 +16,7 @@ export default defineUntypedSchema({
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @type {boolean | { key: string; cert: string }}
|
||||
* @type {boolean | { key: string; cert: string } | { pfx: string; passphrase: string }}
|
||||
*/
|
||||
https: false,
|
||||
|
||||
|
@ -73,7 +73,7 @@ export default defineUntypedSchema({
|
||||
|
||||
/**
|
||||
* You can extend generated `.nuxt/tsconfig.json` using this option.
|
||||
* @type {0 extends 1 & VueCompilerOptions ? typeof import('pkg-types')['TSConfig'] : typeof import('pkg-types')['TSConfig'] & { vueCompilerOptions?: typeof import('@vue/language-core')['VueCompilerOptions']}}
|
||||
* @type {0 extends 1 & VueCompilerOptions ? typeof import('pkg-types')['TSConfig'] : typeof import('pkg-types')['TSConfig'] & { vueCompilerOptions?: Omit<typeof import('@vue/language-core')['VueCompilerOptions'], 'plugins'> & { plugins?: string[] } }}
|
||||
*/
|
||||
tsConfig: {},
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { consola } from 'consola'
|
||||
import { resolve } from 'pathe'
|
||||
import { isTest } from 'std-env'
|
||||
import { withoutLeadingSlash } from 'ufo'
|
||||
import { defineUntypedSchema } from 'untyped'
|
||||
|
||||
export default defineUntypedSchema({
|
||||
@ -97,7 +96,7 @@ export default defineUntypedSchema({
|
||||
clearScreen: true,
|
||||
build: {
|
||||
assetsDir: {
|
||||
$resolve: async (val, get) => val ?? withoutLeadingSlash((await get('app') as Record<string, string>).buildAssetsDir),
|
||||
$resolve: async (val, get) => val ?? (await get('app') as Record<string, string>).buildAssetsDir?.replace(/^\/+/, ''),
|
||||
},
|
||||
emptyOutDir: false,
|
||||
},
|
||||
|
@ -23,7 +23,7 @@ export type WatchEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir'
|
||||
|
||||
// If the user does not have `@vue/language-core` installed, VueCompilerOptions will be typed as `any`,
|
||||
// thus making the whole `VueTSConfig` type `any`. We only augment TSConfig if VueCompilerOptions is available.
|
||||
export type VueTSConfig = 0 extends 1 & VueCompilerOptions ? TSConfig : TSConfig & { vueCompilerOptions?: VueCompilerOptions }
|
||||
export type VueTSConfig = 0 extends 1 & VueCompilerOptions ? TSConfig : TSConfig & { vueCompilerOptions?: Omit<VueCompilerOptions, 'plugins'> & { plugins?: string[] } }
|
||||
|
||||
export type NuxtPage = {
|
||||
name?: string
|
||||
|
@ -17,22 +17,19 @@
|
||||
"prerender": "pnpm build && jiti ./lib/prerender"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unocss/reset": "0.65.3",
|
||||
"@unocss/reset": "65.4.0",
|
||||
"beasties": "0.2.0",
|
||||
"html-validate": "9.1.0",
|
||||
"html-validate": "9.1.3",
|
||||
"htmlnano": "2.1.1",
|
||||
"jiti": "2.4.2",
|
||||
"knitwork": "1.2.0",
|
||||
"pathe": "1.1.2",
|
||||
"pathe": "2.0.1",
|
||||
"prettier": "3.4.2",
|
||||
"scule": "1.3.0",
|
||||
"svgo": "3.3.2",
|
||||
"tinyexec": "0.3.1",
|
||||
"tinyexec": "0.3.2",
|
||||
"tinyglobby": "0.2.10",
|
||||
"unocss": "0.65.3",
|
||||
"vite": "6.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||
"unocss": "65.4.0",
|
||||
"vite": "6.0.7"
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"rollup": "4.29.1",
|
||||
"unbuild": "3.0.1",
|
||||
"rollup": "4.30.1",
|
||||
"unbuild": "3.3.0",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -47,16 +47,16 @@
|
||||
"jiti": "^2.4.2",
|
||||
"knitwork": "^1.2.0",
|
||||
"magic-string": "^0.30.17",
|
||||
"mlly": "^1.7.3",
|
||||
"pathe": "^1.1.2",
|
||||
"mlly": "^1.7.4",
|
||||
"pathe": "^2.0.1",
|
||||
"pkg-types": "^1.3.0",
|
||||
"postcss": "^8.4.49",
|
||||
"rollup-plugin-visualizer": "^5.13.1",
|
||||
"std-env": "^3.8.0",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^2.1.0",
|
||||
"vite": "^6.0.6",
|
||||
"unplugin": "^2.1.2",
|
||||
"vite": "^6.0.7",
|
||||
"vite-node": "^2.1.8",
|
||||
"vite-plugin-checker": "^0.8.0",
|
||||
"vue-bundle-renderer": "^2.1.1"
|
||||
@ -65,6 +65,6 @@
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.5 || ^20.9.0 || >=22.0.0"
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -45,12 +45,11 @@
|
||||
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
|
||||
"jiti": "^2.4.2",
|
||||
"knitwork": "^1.2.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"magic-string": "^0.30.17",
|
||||
"memfs": "^4.15.1",
|
||||
"memfs": "^4.17.0",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
"pathe": "^2.0.1",
|
||||
"pify": "^6.1.0",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-import": "^16.1.0",
|
||||
@ -62,7 +61,7 @@
|
||||
"time-fix-plugin": "^2.0.7",
|
||||
"ufo": "^1.5.4",
|
||||
"unenv": "^1.10.0",
|
||||
"unplugin": "^2.1.0",
|
||||
"unplugin": "^2.1.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-bundle-renderer": "^2.1.1",
|
||||
"vue-loader": "^17.4.2",
|
||||
@ -75,18 +74,17 @@
|
||||
"devDependencies": {
|
||||
"@nuxt/schema": "workspace:*",
|
||||
"@rspack/core": "1.1.8",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/pify": "5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.9",
|
||||
"rollup": "4.29.1",
|
||||
"unbuild": "3.0.1",
|
||||
"rollup": "4.30.1",
|
||||
"unbuild": "3.3.0",
|
||||
"vue": "3.5.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.5 || ^20.9.0 || >=22.0.0"
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import escapeRegExp from 'escape-string-regexp'
|
||||
import { joinURL } from 'ufo'
|
||||
import type { NuxtOptions } from '@nuxt/schema'
|
||||
import { isTest } from 'std-env'
|
||||
import { defu } from 'defu'
|
||||
import type { WarningFilter } from '../plugins/warning-ignore'
|
||||
import WarningIgnorePlugin from '../plugins/warning-ignore'
|
||||
import type { WebpackConfigContext } from '../utils/config'
|
||||
@ -27,7 +28,7 @@ export async function base (ctx: WebpackConfigContext) {
|
||||
}
|
||||
|
||||
function baseConfig (ctx: WebpackConfigContext) {
|
||||
ctx.config = {
|
||||
ctx.config = defu({}, {
|
||||
name: ctx.name,
|
||||
entry: { app: [resolve(ctx.options.appDir, ctx.options.experimental.asyncEntry ? 'entry.async' : 'entry')] },
|
||||
module: { rules: [] },
|
||||
@ -45,7 +46,7 @@ function baseConfig (ctx: WebpackConfigContext) {
|
||||
output: getOutput(ctx),
|
||||
stats: statsMap[ctx.nuxt.options.logLevel] ?? statsMap.info,
|
||||
...ctx.config,
|
||||
}
|
||||
} satisfies Configuration)
|
||||
}
|
||||
|
||||
function basePlugins (ctx: WebpackConfigContext) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import type { Configuration } from 'webpack'
|
||||
import type { Nuxt, NuxtOptions } from '@nuxt/schema'
|
||||
import { logger } from '@nuxt/kit'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { toArray } from './index'
|
||||
|
||||
export interface WebpackConfigContext {
|
||||
@ -63,9 +62,3 @@ export function fileName (ctx: WebpackConfigContext, key: string) {
|
||||
|
||||
return fileName
|
||||
}
|
||||
|
||||
export function getWebpackConfig (ctx: WebpackConfigContext): Configuration {
|
||||
// Clone to avoid leaking config between Client and Server
|
||||
// TODO: rewrite webpack implementation to avoid necessity for this
|
||||
return cloneDeep(ctx.config)
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import { DynamicBasePlugin } from './plugins/dynamic-base'
|
||||
import { ChunkErrorPlugin } from './plugins/chunk'
|
||||
import { createMFS } from './utils/mfs'
|
||||
import { client, server } from './configs'
|
||||
import { applyPresets, createWebpackConfigContext, getWebpackConfig } from './utils/config'
|
||||
import { applyPresets, createWebpackConfigContext } from './utils/config'
|
||||
import { dynamicRequire } from './nitro/plugins/dynamic-require'
|
||||
|
||||
import { builder, webpack } from '#builder'
|
||||
@ -28,7 +28,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
||||
const ctx = createWebpackConfigContext(nuxt)
|
||||
ctx.userConfig = defu(nuxt.options.webpack[`$${preset.name as 'client' | 'server'}`], ctx.userConfig)
|
||||
await applyPresets(ctx, preset)
|
||||
return getWebpackConfig(ctx)
|
||||
return ctx.config
|
||||
}))
|
||||
|
||||
/** Inject rollup plugin for Nitro to handle dynamic imports from webpack chunks */
|
||||
|
3019
pnpm-lock.yaml
3019
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,10 @@
|
||||
"main",
|
||||
"3.x"
|
||||
],
|
||||
"ignoreDeps": [
|
||||
"node",
|
||||
"npm"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "vitest",
|
||||
|
@ -7,11 +7,13 @@ import { join } from 'pathe'
|
||||
|
||||
describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM_CI)('minimal nuxt application', () => {
|
||||
const rootDir = fileURLToPath(new URL('./fixtures/minimal', import.meta.url))
|
||||
const pagesRootDir = fileURLToPath(new URL('./fixtures/minimal-pages', import.meta.url))
|
||||
|
||||
beforeAll(async () => {
|
||||
await Promise.all([
|
||||
exec('pnpm', ['nuxi', 'build', rootDir], { nodeOptions: { env: { EXTERNAL_VUE: 'false' } } }),
|
||||
exec('pnpm', ['nuxi', 'build', rootDir], { nodeOptions: { env: { EXTERNAL_VUE: 'true' } } }),
|
||||
exec('pnpm', ['nuxi', 'build', pagesRootDir]),
|
||||
])
|
||||
}, 120 * 1000)
|
||||
|
||||
@ -33,14 +35,33 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
`)
|
||||
})
|
||||
|
||||
it('default client bundle size (pages)', async () => {
|
||||
const clientStats = await analyzeSizes(['**/*.js'], join(pagesRootDir, '.output/public'))
|
||||
|
||||
expect.soft(roundToKilobytes(clientStats!.totalBytes)).toMatchInlineSnapshot(`"175k"`)
|
||||
|
||||
const files = clientStats!.files.map(f => f.replace(/\..*\.js/, '.js'))
|
||||
|
||||
expect([...files]).toMatchInlineSnapshot(`
|
||||
[
|
||||
"_nuxt/a.js",
|
||||
"_nuxt/client-component.js",
|
||||
"_nuxt/default.js",
|
||||
"_nuxt/entry.js",
|
||||
"_nuxt/index.js",
|
||||
"_nuxt/server-component.js",
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
||||
it('default server bundle size', async () => {
|
||||
const serverDir = join(rootDir, '.output/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"210k"`)
|
||||
|
||||
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1396k"`)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1398k"`)
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
@ -65,6 +86,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
"entities",
|
||||
"estree-walker",
|
||||
"hookable",
|
||||
"packrup",
|
||||
"source-map-js",
|
||||
"ufo",
|
||||
"unhead",
|
||||
@ -81,7 +103,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"560k"`)
|
||||
|
||||
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"94.4k"`)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"96.4k"`)
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
@ -95,10 +117,53 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
"db0",
|
||||
"devalue",
|
||||
"hookable",
|
||||
"packrup",
|
||||
"unhead",
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
||||
it('default server bundle size (pages)', async () => {
|
||||
const serverDir = join(pagesRootDir, '.output/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"303k"`)
|
||||
|
||||
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1398k"`)
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
.map(m => m.replace('/package.json', '').replace('node_modules/', ''))
|
||||
.sort()
|
||||
expect(packages).toMatchInlineSnapshot(`
|
||||
[
|
||||
"@babel/parser",
|
||||
"@unhead/dom",
|
||||
"@unhead/shared",
|
||||
"@unhead/ssr",
|
||||
"@vue/compiler-core",
|
||||
"@vue/compiler-dom",
|
||||
"@vue/compiler-ssr",
|
||||
"@vue/reactivity",
|
||||
"@vue/runtime-core",
|
||||
"@vue/runtime-dom",
|
||||
"@vue/server-renderer",
|
||||
"@vue/shared",
|
||||
"db0",
|
||||
"devalue",
|
||||
"entities",
|
||||
"estree-walker",
|
||||
"hookable",
|
||||
"packrup",
|
||||
"source-map-js",
|
||||
"ufo",
|
||||
"unhead",
|
||||
"vue",
|
||||
"vue-bundle-renderer",
|
||||
]
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
async function analyzeSizes (pattern: string[], rootDir: string) {
|
||||
|
3
test/fixtures/minimal-pages/app.vue
vendored
Normal file
3
test/fixtures/minimal-pages/app.vue
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<NuxtLayout><NuxtPage /></NuxtLayout>
|
||||
</template>
|
3
test/fixtures/minimal-pages/error.vue
vendored
Normal file
3
test/fixtures/minimal-pages/error.vue
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>Error page</div>
|
||||
</template>
|
3
test/fixtures/minimal-pages/layouts/default.vue
vendored
Normal file
3
test/fixtures/minimal-pages/layouts/default.vue
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
1
test/fixtures/minimal-pages/middleware/test.global.ts
vendored
Normal file
1
test/fixtures/minimal-pages/middleware/test.global.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default defineNuxtRouteMiddleware(() => {})
|
27
test/fixtures/minimal-pages/nuxt.config.ts
vendored
Normal file
27
test/fixtures/minimal-pages/nuxt.config.ts
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const nuxtEntry = fileURLToPath(new URL('../../../packages/nuxt/dist/index.mjs', import.meta.url))
|
||||
const isStubbed = readFileSync(nuxtEntry, 'utf-8').includes('const _module = await jiti')
|
||||
|
||||
export default defineNuxtConfig({
|
||||
$production: {
|
||||
vite: {
|
||||
$client: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
chunkFileNames: '_nuxt/[name].js',
|
||||
entryFileNames: '_nuxt/[name].js',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sourcemap: false,
|
||||
compatibilityDate: '2024-06-28',
|
||||
typescript: {
|
||||
typeCheck: isStubbed ? false : 'build',
|
||||
},
|
||||
})
|
13
test/fixtures/minimal-pages/package.json
vendored
Normal file
13
test/fixtures/minimal-pages/package.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "fixture-minimal-pages",
|
||||
"scripts": {
|
||||
"build": "nuxi build"
|
||||
},
|
||||
"dependencies": {
|
||||
"nuxt": "workspace:*"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.5 || ^20.9.0 || >=22.0.0"
|
||||
}
|
||||
}
|
3
test/fixtures/minimal-pages/pages/a.client.vue
vendored
Normal file
3
test/fixtures/minimal-pages/pages/a.client.vue
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>Client-only page</div>
|
||||
</template>
|
3
test/fixtures/minimal-pages/pages/b.server.vue
vendored
Normal file
3
test/fixtures/minimal-pages/pages/b.server.vue
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>Server-only page</div>
|
||||
</template>
|
3
test/fixtures/minimal-pages/pages/index.vue
vendored
Normal file
3
test/fixtures/minimal-pages/pages/index.vue
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>Hello World!</div>
|
||||
</template>
|
1
test/fixtures/minimal-pages/plugins/test.ts
vendored
Normal file
1
test/fixtures/minimal-pages/plugins/test.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default defineNuxtPlugin(() => {})
|
3
test/fixtures/minimal-pages/tsconfig.json
vendored
Normal file
3
test/fixtures/minimal-pages/tsconfig.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
Loading…
Reference in New Issue
Block a user