Merge branch 'main' into docs/kit

This commit is contained in:
Andrey Yolkin 2023-08-25 18:37:01 +03:00 committed by GitHub
commit a19074b205
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 1294 additions and 2775 deletions

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:

View File

@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with: with:
fetch-depth: 0 fetch-depth: 0
- run: corepack enable - run: corepack enable

View File

@ -38,7 +38,7 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:
@ -75,7 +75,7 @@ jobs:
- build - build
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:
@ -114,7 +114,7 @@ jobs:
module: ['bundler', 'node'] module: ['bundler', 'node']
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:
@ -142,7 +142,7 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:
@ -178,7 +178,7 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:
@ -250,7 +250,7 @@ jobs:
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with: with:
fetch-depth: 0 fetch-depth: 0
- run: corepack enable - run: corepack enable
@ -289,7 +289,7 @@ jobs:
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with: with:
fetch-depth: 0 fetch-depth: 0
- run: corepack enable - run: corepack enable

View File

@ -17,6 +17,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@f6fff72a3217f580d5afd49a46826795305b63c7 # v3.0.8 uses: actions/dependency-review-action@f6fff72a3217f580d5afd49a46826795305b63c7 # v3.0.8

View File

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:

View File

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable - run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:

View File

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
# From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions # From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
- name: Check workflow files - name: Check workflow files
run: | run: |

View File

@ -21,7 +21,7 @@ jobs:
permissions: permissions:
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with: with:
ref: '2.x' ref: '2.x'
fetch-depth: 0 # All history fetch-depth: 0 # All history

View File

@ -29,7 +29,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with: with:
ref: refs/pull/${{ github.event.issue.number }}/merge ref: refs/pull/${{ github.event.issue.number }}/merge
fetch-depth: 0 fetch-depth: 0

View File

@ -10,7 +10,7 @@ jobs:
reproduire: reproduire:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp - uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
with: with:
label: needs reproduction label: needs reproduction

View File

@ -31,7 +31,7 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with: with:
persist-credentials: false persist-credentials: false

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ jspm_packages
package-lock.json package-lock.json
packages/*/README.md packages/*/README.md
!packages/nuxi/README.md
packages/*/LICENSE packages/*/LICENSE
*/**/yarn.lock */**/yarn.lock
/.yarn /.yarn

View File

@ -6,7 +6,6 @@
"@nuxt/test-utils": "./packages/test-utils", "@nuxt/test-utils": "./packages/test-utils",
"@nuxt/vite": "./packages/vite", "@nuxt/vite": "./packages/vite",
"@nuxt/webpack": "./packages/webpack", "@nuxt/webpack": "./packages/webpack",
"nuxi": "./packages/nuxi",
"nuxt": "./packages/nuxt" "nuxt": "./packages/nuxt"
} }
} }

View File

@ -88,6 +88,10 @@ npm install
pnpm install pnpm install
``` ```
```bash [bun]
bun install
```
:: ::
## Development Server ## Development Server
@ -108,6 +112,10 @@ npm run dev -- -o
pnpm dev -o pnpm dev -o
``` ```
```bash [bun]
bun run dev -o
```
:: ::
::alert{type=success icon=✨ .font-bold} ::alert{type=success icon=✨ .font-bold}

View File

@ -7,4 +7,4 @@ head.title: "node_modules/"
# Node modules Directory # Node modules Directory
The package manager ([`npm`](https://docs.npmjs.com/cli/v7/commands/npm) or [`yarn`](https://yarnpkg.com/) or [`pnpm`](https://pnpm.io/cli/install)) creates the [`node_modules/` directory](/docs/guide/directory-structure/node_modules) to store the dependencies of your project. The package manager ([`npm`](https://docs.npmjs.com/cli/v7/commands/npm) or [`yarn`](https://yarnpkg.com/) or [`pnpm`](https://pnpm.io/cli/install) or [`bun`](https://bun.sh/package-manager)) creates the [`node_modules/` directory](/docs/guide/directory-structure/node_modules) to store the dependencies of your project.

View File

@ -14,7 +14,7 @@ Nuxt automatically scans files inside these directories to register API and serv
Each file should export a default function defined with `defineEventHandler()` or `eventHandler()` (alias). Each file should export a default function defined with `defineEventHandler()` or `eventHandler()` (alias).
The handler can directly return JSON data, a `Promise` or use `event.node.res.end()` to send a response. The handler can directly return JSON data, a `Promise`, or use `event.node.res.end()` to send a response.
**Example:** Create the `/api/hello` route with `server/api/hello.ts` file: **Example:** Create the `/api/hello` route with `server/api/hello.ts` file:

View File

@ -30,7 +30,7 @@ Update `nuxt` dependency inside `package.json`:
} }
``` ```
Remove lockfile (`package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml`) and reinstall dependencies. Remove lockfile (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, or `bun.lockb`) and reinstall dependencies.
## Opting Out From the Edge Channel ## Opting Out From the Edge Channel
@ -45,7 +45,7 @@ Update `nuxt` dependency inside `package.json`:
} }
``` ```
Remove lockfile (`package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml`) and reinstall dependencies. Remove lockfile (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, or `bun.lockb`) and reinstall dependencies.
## Using Latest `nuxi` CLI From Edge ## Using Latest `nuxi` CLI From Edge

View File

@ -12,24 +12,24 @@ Within your pages, components, and plugins you can use useAsyncData to get acces
## Type ## Type
```ts [Signature] ```ts [Signature]
function useAsyncData( function useAsyncData<DataT, DataE>(
handler: (nuxtApp?: NuxtApp) => Promise<DataT>, handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
options?: AsyncDataOptions<DataT> options?: AsyncDataOptions<DataT>
): AsyncData<DataT> ): AsyncData<DataT, DataE>
function useAsyncData( function useAsyncData<DataT, DataE>(
key: string, key: string,
handler: (nuxtApp?: NuxtApp) => Promise<DataT>, handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
options?: AsyncDataOptions<DataT> options?: AsyncDataOptions<DataT>
): Promise<AsyncData<DataT>> ): Promise<AsyncData<DataT, DataE>
type AsyncDataOptions<DataT> = { type AsyncDataOptions<DataT> = {
server?: boolean server?: boolean
lazy?: boolean lazy?: boolean
immediate?: boolean
default?: () => DataT | Ref<DataT> | null default?: () => DataT | Ref<DataT> | null
transform?: (input: DataT) => DataT transform?: (input: DataT) => DataT
pick?: string[] pick?: string[]
watch?: WatchSource[] watch?: WatchSource[]
immediate?: boolean
} }
type AsyncData<DataT, ErrorT> = { type AsyncData<DataT, ErrorT> = {
@ -53,13 +53,13 @@ type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
* **key**: a unique key to ensure that data fetching can be properly de-duplicated across requests. If you do not provide a key, then a key that is unique to the file name and line number of the instance of [`useAsyncData`](/docs/api/composables/use-async-data) will be generated for you. * **key**: a unique key to ensure that data fetching can be properly de-duplicated across requests. If you do not provide a key, then a key that is unique to the file name and line number of the instance of [`useAsyncData`](/docs/api/composables/use-async-data) will be generated for you.
* **handler**: an asynchronous function that returns a value * **handler**: an asynchronous function that returns a value
* **options**: * **options**:
* _lazy_: whether to resolve the async function after loading the route, instead of blocking client-side navigation (defaults to `false`)
* _default_: a factory function to set the default value of the data, before the async function resolves - particularly useful with the `lazy: true` option
* _server_: whether to fetch the data on the server (defaults to `true`) * _server_: whether to fetch the data on the server (defaults to `true`)
* _lazy_: whether to resolve the async function after loading the route, instead of blocking client-side navigation (defaults to `false`)
* _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 * _transform_: a function that can be used to alter `handler` function result after resolving
* _pick_: only pick specified keys in this array from the `handler` function result * _pick_: only pick specified keys in this array from the `handler` function result
* _watch_: watch reactive sources to auto-refresh * _watch_: watch reactive sources to auto-refresh
* _immediate_: When set to `false`, will prevent the request from firing immediately. (defaults to `true`)
Under the hood, `lazy: false` uses `<Suspense>` to block the loading of the route before the data has been fetched. Consider using `lazy: true` and implementing a loading state instead for a snappier user experience. Under the hood, `lazy: false` uses `<Suspense>` to block the loading of the route before the data has been fetched. Consider using `lazy: true` and implementing a loading state instead for a snappier user experience.

View File

@ -10,12 +10,12 @@ It automatically generates a key based on URL and fetch options, provides type h
## Type ## Type
```ts [Signature] ```ts [Signature]
function useFetch( function useFetch<DataT, ErrorT>(
url: string | Request | Ref<string | Request> | () => string | Request, url: string | Request | Ref<string | Request> | () => string | Request,
options?: UseFetchOptions<DataT> options?: UseFetchOptions<DataT>
): Promise<AsyncData<DataT>> ): Promise<AsyncData<DataT, ErrorT>>
type UseFetchOptions = { type UseFetchOptions<DataT> = {
key?: string key?: string
method?: string method?: string
query?: SearchParams query?: SearchParams
@ -29,7 +29,7 @@ type UseFetchOptions = {
default?: () => DataT default?: () => DataT
transform?: (input: DataT) => DataT transform?: (input: DataT) => DataT
pick?: string[] pick?: string[]
watch?: WatchSource[] watch?: WatchSource[] | false
} }
type AsyncData<DataT, ErrorT> = { type AsyncData<DataT, ErrorT> = {
@ -63,14 +63,15 @@ type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
All fetch options can be given a `computed` or `ref` value. These will be watched and new requests made automatically with any new values if they are updated. All fetch options can be given a `computed` or `ref` value. These will be watched and new requests made automatically with any new values if they are updated.
:: ::
* **Options (from [`useAsyncData`](/docs/api/composables/use-async-data) )**: * **Options (from `useAsyncData`)**:
* `key`: a unique key to ensure that data fetching can be properly de-duplicated across requests, if not provided, it will be generated based on the static code location where [`useAsyncData`](/docs/api/composables/use-async-data) is used. * `key`: a unique key to ensure that data fetching can be properly de-duplicated across requests, if not provided, it will be generated based on the static code location where `useAsyncData` is used.
* `server`: Whether to fetch the data on the server (defaults to `true`). * `server`: whether to fetch the data on the server (defaults to `true`)
* `default`: A factory function to set the default value of the data, before the async function resolves - particularly useful with the `lazy: true` option. * `lazy`: whether to resolve the async function after loading the route, instead of blocking client-side navigation (defaults to `false`)
* `pick`: Only pick specified keys in this array from the `handler` function result. * `immediate`: when set to `false`, will prevent the request from firing immediately. (defaults to `true`)
* `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`. * `default`: a factory function to set the default value of the `data`, before the async function resolves - useful with the `lazy: true` or `immediate: false` option
* `transform`: A function that can be used to alter `handler` function result after resolving. * `transform`: a function that can be used to alter `handler` function result after resolving
* `immediate`: When set to `false`, will prevent the request from firing immediately. (defaults to `true`) * `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`.
::alert{type=warning} ::alert{type=warning}
If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the [`useFetch`](/docs/api/composables/use-fetch) call will not match other [`useFetch`](/docs/api/composables/use-fetch) calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`. If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the [`useFetch`](/docs/api/composables/use-fetch) call will not match other [`useFetch`](/docs/api/composables/use-fetch) calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`.

View File

@ -12,7 +12,7 @@ Within your pages, components, and plugins you can use `useRequestEvent` to acce
const event = useRequestEvent() const event = useRequestEvent()
// Get the URL // Get the URL
const url = event.node.req.url const url = event.path
``` ```
::alert{icon=👉} ::alert{icon=👉}

View File

@ -157,7 +157,7 @@ We recommend using [VS Code](https://code.visualstudio.com/) along with the [ESL
#### No Prettier #### No Prettier
Since ESLint is already configured to format the code, there is no need to duplicate the functionality with Prettier. To format the code, you can run `yarn lint --fix` or `pnpm lint --fix` or referring the [ESLint section](#use-eslint) for IDE Setup. Since ESLint is already configured to format the code, there is no need to duplicate the functionality with Prettier. To format the code, you can run `yarn lint --fix`, `pnpm lint --fix`, or `bun run lint --fix` or referring the [ESLint section](#use-eslint) for IDE Setup.
If you have Prettier installed in your editor, we recommend you disable it when working on the project to avoid conflict. If you have Prettier installed in your editor, we recommend you disable it when working on the project to avoid conflict.

View File

@ -33,11 +33,10 @@
"@nuxt/test-utils": "workspace:*", "@nuxt/test-utils": "workspace:*",
"@nuxt/vite-builder": "workspace:*", "@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*",
"nuxi": "workspace:*",
"nuxt": "workspace:*", "nuxt": "workspace:*",
"vite": "4.4.9", "vite": "4.4.9",
"vue": "3.3.4", "vue": "3.3.4",
"magic-string": "^0.30.2" "magic-string": "^0.30.3"
}, },
"devDependencies": { "devDependencies": {
"@actions/core": "1.10.0", "@actions/core": "1.10.0",
@ -45,11 +44,11 @@
"@nuxt/webpack-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*",
"@nuxtjs/eslint-config-typescript": "12.0.0", "@nuxtjs/eslint-config-typescript": "12.0.0",
"@types/fs-extra": "11.0.1", "@types/fs-extra": "11.0.1",
"@types/node": "18.17.6", "@types/node": "18.17.11",
"@types/semver": "7.5.0", "@types/semver": "7.5.0",
"case-police": "0.6.1", "case-police": "0.6.1",
"chalk": "5.3.0", "chalk": "5.3.0",
"changelogen": "0.5.4", "changelogen": "0.5.5",
"cheerio": "1.0.0-rc.12", "cheerio": "1.0.0-rc.12",
"consola": "3.2.3", "consola": "3.2.3",
"devalue": "4.3.2", "devalue": "4.3.2",
@ -61,21 +60,21 @@
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"globby": "13.2.2", "globby": "13.2.2",
"h3": "1.8.0", "h3": "1.8.0",
"happy-dom": "10.10.4", "happy-dom": "10.11.0",
"jiti": "1.19.3", "jiti": "1.19.3",
"markdownlint-cli": "^0.33.0", "markdownlint-cli": "^0.33.0",
"nitropack": "2.5.2", "nitropack": "2.6.0",
"nuxi": "workspace:*", "nuxi": "npm:nuxi-ng@0.3.0-1692970235.c259efa",
"nuxt": "workspace:*", "nuxt": "workspace:*",
"nuxt-vitest": "0.10.2", "nuxt-vitest": "0.10.2",
"ofetch": "1.1.1", "ofetch": "1.3.3",
"pathe": "1.1.1", "pathe": "1.1.1",
"playwright-core": "1.37.1", "playwright-core": "1.37.1",
"rimraf": "5.0.1", "rimraf": "5.0.1",
"semver": "7.5.4", "semver": "7.5.4",
"std-env": "3.4.0", "std-env": "3.4.3",
"typescript": "5.1.6", "typescript": "5.2.2",
"ufo": "1.2.0", "ufo": "1.3.0",
"vite": "4.4.9", "vite": "4.4.9",
"vitest": "0.33.0", "vitest": "0.33.0",
"vitest-environment-nuxt": "0.10.2", "vitest-environment-nuxt": "0.10.2",

View File

@ -34,9 +34,9 @@
"pkg-types": "^1.0.3", "pkg-types": "^1.0.3",
"scule": "^1.0.0", "scule": "^1.0.0",
"semver": "^7.5.4", "semver": "^7.5.4",
"ufo": "^1.2.0", "ufo": "^1.3.0",
"unctx": "^2.3.1", "unctx": "^2.3.1",
"unimport": "^3.1.3", "unimport": "^3.2.0",
"untyped": "^1.4.0" "untyped": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
@ -44,7 +44,7 @@
"@types/lodash-es": "4.17.8", "@types/lodash-es": "4.17.8",
"@types/semver": "7.5.0", "@types/semver": "7.5.0",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"nitropack": "2.5.2", "nitropack": "2.6.0",
"unbuild": "latest", "unbuild": "latest",
"vite": "4.4.9", "vite": "4.4.9",
"vitest": "0.33.0", "vitest": "0.33.0",

View File

@ -1,6 +1,6 @@
import { existsSync, readFileSync } from 'node:fs' import { existsSync, readFileSync } from 'node:fs'
import ignore from 'ignore' import ignore from 'ignore'
import { join, relative } from 'pathe' import { join, relative, resolve } from 'pathe'
import { tryUseNuxt } from './context' import { tryUseNuxt } from './context'
/** /**
@ -16,14 +16,7 @@ export function isIgnored (pathname: string): boolean {
if (!nuxt._ignore) { if (!nuxt._ignore) {
nuxt._ignore = ignore(nuxt.options.ignoreOptions) nuxt._ignore = ignore(nuxt.options.ignoreOptions)
const resolvedIgnore = nuxt.options.ignore.flatMap(s => resolveGroupSyntax(s)) nuxt._ignore.add(resolveIgnorePatterns())
nuxt._ignore.add(resolvedIgnore)
const nuxtignoreFile = join(nuxt.options.rootDir, '.nuxtignore')
if (existsSync(nuxtignoreFile)) {
nuxt._ignore.add(readFileSync(nuxtignoreFile, 'utf-8'))
}
} }
const cwds = nuxt.options._layers?.map(layer => layer.cwd).sort((a, b) => b.length - a.length) const cwds = nuxt.options._layers?.map(layer => layer.cwd).sort((a, b) => b.length - a.length)
@ -35,6 +28,31 @@ export function isIgnored (pathname: string): boolean {
return !!(relativePath && nuxt._ignore.ignores(relativePath)) return !!(relativePath && nuxt._ignore.ignores(relativePath))
} }
export function resolveIgnorePatterns (relativePath?: string): string[] {
const nuxt = tryUseNuxt()
// Happens with CLI reloads
if (!nuxt) {
return []
}
if (!nuxt._ignorePatterns) {
nuxt._ignorePatterns = nuxt.options.ignore.flatMap(s => resolveGroupSyntax(s))
const nuxtignoreFile = join(nuxt.options.rootDir, '.nuxtignore')
if (existsSync(nuxtignoreFile)) {
const contents = readFileSync(nuxtignoreFile, 'utf-8')
nuxt._ignorePatterns.push(...contents.trim().split(/\r?\n/))
}
}
if (relativePath) {
return nuxt._ignorePatterns.map(p => p.startsWith('*') || p.startsWith('!*') ? p : relative(relativePath, resolve(nuxt.options.rootDir, p)))
}
return nuxt._ignorePatterns
}
/** /**
* This function turns string containing groups '**\/*.{spec,test}.{js,ts}' into an array of strings. * This function turns string containing groups '**\/*.{spec,test}.{js,ts}' into an array of strings.
* For example will '**\/*.{spec,test}.{js,ts}' be resolved to: * For example will '**\/*.{spec,test}.{js,ts}' be resolved to:

View File

@ -14,7 +14,7 @@ export * from './build'
export * from './compatibility' export * from './compatibility'
export * from './components' export * from './components'
export * from './context' export * from './context'
export { isIgnored } from './ignore' export { isIgnored, resolveIgnorePatterns } from './ignore'
export * from './layout' export * from './layout'
export * from './pages' export * from './pages'
export * from './plugin' export * from './plugin'

5
packages/nuxi/README.md Normal file
View File

@ -0,0 +1,5 @@
# Nuxt CLI (nuxi)
⚡️ Next Generation CLI Experience for [Nuxt](https://nuxt.com/).
- 👉 View on GitHub at https://github.com/nuxt/cli

View File

@ -1,2 +0,0 @@
#!/usr/bin/env node
import('../dist/cli-wrapper.mjs')

View File

@ -1,31 +0,0 @@
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
declaration: true,
rollup: {
inlineDependencies: true,
resolve: {
exportConditions: ['production', 'node'] as any
}
},
entries: [
'src/cli',
'src/cli-run',
'src/cli-wrapper',
'src/index'
],
externals: [
'@nuxt/kit',
'@nuxt/schema',
'@nuxt/test-utils',
'fsevents',
// TODO: Fix rollup/unbuild issue
'node:url',
'node:buffer',
'node:path',
'node:child_process',
'node:process',
'node:path',
'node:os'
]
})

View File

@ -1,60 +0,0 @@
{
"name": "nuxi",
"version": "3.6.5",
"repository": "nuxt/nuxt",
"license": "MIT",
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
".": "./dist/index.mjs",
"./cli": "./bin/nuxi.mjs"
},
"bin": "./bin/nuxi.mjs",
"files": [
"bin",
"dist"
],
"scripts": {
"prepack": "unbuild"
},
"devDependencies": {
"@nuxt/kit": "workspace:../kit",
"@nuxt/schema": "workspace:../schema",
"@types/clear": "0.1.2",
"@types/flat": "5.0.2",
"@types/mri": "1.1.1",
"@types/semver": "7.5.0",
"c12": "1.4.2",
"chokidar": "3.5.3",
"clear": "0.1.0",
"clipboardy": "3.0.0",
"colorette": "2.0.20",
"consola": "3.2.3",
"deep-object-diff": "1.1.9",
"defu": "6.1.2",
"destr": "2.0.1",
"execa": "7.2.0",
"flat": "5.0.2",
"giget": "1.1.2",
"h3": "1.8.0",
"jiti": "1.19.3",
"listhen": "1.3.0",
"mlly": "1.4.0",
"mri": "1.2.0",
"nitropack": "2.5.2",
"ohash": "1.1.3",
"pathe": "1.1.1",
"perfect-debounce": "1.0.0",
"pkg-types": "1.0.3",
"scule": "1.0.0",
"semver": "7.5.4",
"ufo": "1.2.0",
"unbuild": "latest"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
}

View File

@ -1,5 +0,0 @@
// @ts-expect-error internal property for tracking start time
process._startTime = Date.now()
// @ts-expect-error `default` property is not declared
import('./cli').then(r => (r.default || r).main())

View File

@ -1,58 +0,0 @@
/**
* This file is used to wrap the CLI entrypoint in a restartable process.
*/
import { fileURLToPath } from 'node:url'
import { fork } from 'node:child_process'
import type { ChildProcess } from 'node:child_process'
const cliEntry = new URL('../dist/cli-run.mjs', import.meta.url)
// Only enable wrapper for nuxt dev command
if (process.argv[2] === 'dev') {
process.env.__CLI_ARGV__ = JSON.stringify(process.argv)
startSubprocess()
} else {
import(cliEntry.href)
}
function startSubprocess () {
let childProc: ChildProcess | undefined
const onShutdown = () => {
if (childProc) {
childProc.kill()
childProc = undefined
}
}
process.on('exit', onShutdown)
process.on('SIGTERM', onShutdown) // Graceful shutdown
process.on('SIGINT', onShutdown) // Ctrl-C
process.on('SIGQUIT', onShutdown) // Ctrl-\
start()
function start () {
const _argv: string[] = (process.env.__CLI_ARGV__ ? JSON.parse(process.env.__CLI_ARGV__) : process.argv).slice(2)
const execArguments: string[] = getInspectArgs()
childProc = fork(fileURLToPath(cliEntry), [], { execArgv: execArguments })
childProc.on('close', (code) => { if (code) { process.exit(code) } })
childProc.on('message', (message) => {
if ((message as { type: string })?.type === 'nuxt:restart') {
childProc?.kill()
startSubprocess()
}
})
function getInspectArgs (): string[] {
const inspectArgv = _argv.find(argvItem => argvItem.includes('--inspect'))
if (!inspectArgv) {
return []
}
return [inspectArgv]
}
}
}

View File

@ -1,87 +0,0 @@
import mri from 'mri'
import { red } from 'colorette'
import type { ConsolaReporter } from 'consola'
import { consola } from 'consola'
import { checkEngines } from './utils/engines'
import type { Command, NuxtCommand } from './commands'
import { commands } from './commands'
import { showHelp } from './utils/help'
import { showBanner } from './utils/banner'
async function _main () {
const _argv = (process.env.__CLI_ARGV__ ? JSON.parse(process.env.__CLI_ARGV__) : process.argv).slice(2)
const args = mri(_argv, {
boolean: [
'no-clear'
]
})
const command = args._.shift() || 'usage'
showBanner(command === 'dev' && args.clear !== false && !args.help)
if (!(command in commands)) {
console.log('\n' + red('Invalid command ' + command))
await commands.usage().then(r => r.invoke())
process.exit(1)
}
// Check Node.js version in background
setTimeout(() => { checkEngines().catch(() => {}) }, 1000)
const cmd = await commands[command as Command]() as NuxtCommand
if (args.h || args.help) {
showHelp(cmd.meta)
} else {
const result = await cmd.invoke(args)
return result
}
}
// Wrap all console logs with consola for better DX
consola.wrapAll()
// Filter out unwanted logs
// TODO: Use better API from consola for intercepting logs
const wrapReporter = (reporter: ConsolaReporter) => ({
log (logObj, ctx) {
if (!logObj.args || !logObj.args.length) { return }
const msg = logObj.args[0]
if (typeof msg === 'string' && !process.env.DEBUG) {
// Hide vue-router 404 warnings
if (msg.startsWith('[Vue Router warn]: No match found for location with path')) {
return
}
// Suppress warning about native Node.js fetch
if (msg.includes('ExperimentalWarning: The Fetch API is an experimental feature')) {
return
}
// TODO: resolve upstream in Vite
// Hide sourcemap warnings related to node_modules
if (msg.startsWith('Sourcemap') && msg.includes('node_modules')) {
return
}
}
return reporter.log(logObj, ctx)
}
}) satisfies ConsolaReporter
consola.options.reporters = consola.options.reporters.map(wrapReporter)
process.on('unhandledRejection', err => consola.error('[unhandledRejection]', err))
process.on('uncaughtException', err => consola.error('[uncaughtException]', err))
export function main () {
_main()
.then((result) => {
if (result === 'error') {
process.exit(1)
} else if (result !== 'wait') {
process.exit()
}
})
.catch((error) => {
consola.error(error)
process.exit(1)
})
}

View File

@ -1,62 +0,0 @@
import { existsSync, promises as fsp } from 'node:fs'
import { dirname, resolve } from 'pathe'
import { consola } from 'consola'
import { loadKit } from '../utils/kit'
import { templates } from '../utils/templates'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'add',
usage: `npx nuxi add [--cwd] [--force] ${Object.keys(templates).join('|')} <name>`,
description: 'Create a new template file.'
},
async invoke (args) {
const cwd = resolve(args.cwd || '.')
const template = args._[0]
const name = args._[1]
// Validate template name
if (!templates[template]) {
consola.error(`Template ${template} is not supported. Possible values: ${Object.keys(templates).join(', ')}`)
process.exit(1)
}
// Validate options
if (!name) {
consola.error('name argument is missing!')
process.exit(1)
}
// Load config in order to respect srcDir
const kit = await loadKit(cwd)
const config = await kit.loadNuxtConfig({ cwd })
// Resolve template
const res = templates[template]({ name, args })
// Resolve full path to generated file
const path = resolve(config.srcDir, res.path)
// Ensure not overriding user code
if (!args.force && existsSync(path)) {
consola.error(`File exists: ${path} . Use --force to override or use a different name.`)
process.exit(1)
}
// Ensure parent directory exists
const parentDir = dirname(path)
if (!existsSync(parentDir)) {
consola.info('Creating directory', parentDir)
if (template === 'page') {
consola.info('This enables vue-router functionality!')
}
await fsp.mkdir(parentDir, { recursive: true })
}
// Write file
await fsp.writeFile(path, res.contents.trim() + '\n')
consola.info(`🪄 Generated a new ${template} in ${path}`)
}
})

View File

@ -1,110 +0,0 @@
import { promises as fsp } from 'node:fs'
import { join, resolve } from 'pathe'
import { createApp, eventHandler, lazyEventHandler, toNodeListener } from 'h3'
import { listen } from 'listhen'
import type { NuxtAnalyzeMeta } from '@nuxt/schema'
import { defu } from 'defu'
import { loadKit } from '../utils/kit'
import { clearDir } from '../utils/fs'
import { overrideEnv } from '../utils/env'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'analyze',
usage: 'npx nuxi analyze [--log-level] [--name] [--no-serve] [rootDir]',
description: 'Build nuxt and analyze production bundle (experimental)'
},
async invoke (args, options = {}) {
overrideEnv('production')
const name = args.name || 'default'
const slug = name.trim().replace(/[^a-z0-9_-]/gi, '_')
const rootDir = resolve(args._[0] || '.')
let analyzeDir = join(rootDir, '.nuxt/analyze', slug)
let buildDir = join(analyzeDir, '.nuxt')
let outDir = join(analyzeDir, '.output')
const startTime = Date.now()
const { loadNuxt, buildNuxt } = await loadKit(rootDir)
const nuxt = await loadNuxt({
rootDir,
overrides: defu(options.overrides, {
build: {
analyze: true
},
analyzeDir,
buildDir,
nitro: {
output: {
dir: outDir
}
},
logLevel: args['log-level']
})
})
analyzeDir = nuxt.options.analyzeDir
buildDir = nuxt.options.buildDir
outDir = nuxt.options.nitro.output?.dir || outDir
await clearDir(analyzeDir)
await buildNuxt(nuxt)
const endTime = Date.now()
const meta: NuxtAnalyzeMeta = {
name,
slug,
startTime,
endTime,
analyzeDir,
buildDir,
outDir
}
await nuxt.callHook('build:analyze:done', meta)
await fsp.writeFile(join(analyzeDir, 'meta.json'), JSON.stringify(meta, null, 2), 'utf-8')
console.info('Analyze results are available at: `' + analyzeDir + '`')
console.warn('Do not deploy analyze results! Use `nuxi build` before deploying.')
if (args.serve !== false && !process.env.CI) {
const app = createApp()
const serveFile = (filePath: string) => lazyEventHandler(async () => {
const contents = await fsp.readFile(filePath, 'utf-8')
return eventHandler((event) => { event.node.res.end(contents) })
})
console.info('Starting stats server...')
app.use('/client', serveFile(join(analyzeDir, 'client.html')))
app.use('/nitro', serveFile(join(analyzeDir, 'nitro.html')))
app.use(eventHandler(() => `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Nuxt Bundle Stats (experimental)</title>
</head>
<h1>Nuxt Bundle Stats (experimental)</h1>
<ul>
<li>
<a href="/nitro">Nitro server bundle stats</a>
</li>
<li>
<a href="/client">Client bundle stats</a>
</li>
</ul>
</html>
`))
await listen(toNodeListener(app))
return 'wait' as const
}
}
})

View File

@ -1,34 +0,0 @@
import { execa } from 'execa'
import { consola } from 'consola'
import { resolve } from 'pathe'
import { tryResolveModule } from '../utils/esm'
import { defineNuxtCommand } from './index'
const MODULE_BUILDER_PKG = '@nuxt/module-builder'
export default defineNuxtCommand({
meta: {
name: 'build-module',
usage: 'npx nuxi build-module [--stub] [rootDir]',
description: `Helper command for using ${MODULE_BUILDER_PKG}`
},
async invoke (args) {
// Find local installed version
const rootDir = resolve(args._[0] || '.')
const hasLocal = await tryResolveModule(`${MODULE_BUILDER_PKG}/package.json`, rootDir)
const execArgs = Object.entries({
'--stub': args.stub,
'--prepare': args.prepare
}).filter(([, value]) => value).map(([key]) => key)
let cmd = 'nuxt-module-build'
if (!hasLocal) {
consola.warn(`Cannot find locally installed version of \`${MODULE_BUILDER_PKG}\` (>=0.2.0). Falling back to \`npx ${MODULE_BUILDER_PKG}\``)
cmd = 'npx'
execArgs.unshift(MODULE_BUILDER_PKG)
}
await execa(cmd, execArgs, { preferLocal: true, stdio: 'inherit', cwd: rootDir })
}
})

View File

@ -1,70 +0,0 @@
import { relative, resolve } from 'pathe'
import { consola } from 'consola'
import type { Nitro } from 'nitropack'
// we are deliberately inlining this code as a backup in case user has `@nuxt/schema<3.7`
import { writeTypes as writeTypesLegacy } from '../../../kit/src/template'
import { loadKit } from '../utils/kit'
import { clearBuildDir } from '../utils/fs'
import { overrideEnv } from '../utils/env'
import { showVersions } from '../utils/banner'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'build',
usage: 'npx nuxi build [--prerender] [--dotenv] [--log-level] [rootDir]',
description: 'Build nuxt for production deployment'
},
async invoke (args, options = {}) {
overrideEnv('production')
const rootDir = resolve(args._[0] || '.')
showVersions(rootDir)
const { loadNuxt, buildNuxt, useNitro, writeTypes = writeTypesLegacy } = await loadKit(rootDir)
const nuxt = await loadNuxt({
rootDir,
dotenv: {
cwd: rootDir,
fileName: args.dotenv
},
overrides: {
logLevel: args['log-level'],
// TODO: remove in 3.8
_generate: args.prerender,
...(args.prerender ? { nitro: { static: true } } : {}),
...(options?.overrides || {})
}
})
let nitro: Nitro | undefined
// In Bridge, if nitro is not enabled, useNitro will throw an error
try {
// Use ? for backward compatibility for Nuxt <= RC.10
nitro = useNitro?.()
} catch {}
await clearBuildDir(nuxt.options.buildDir)
await writeTypes(nuxt)
nuxt.hook('build:error', (err) => {
consola.error('Nuxt Build Error:', err)
process.exit(1)
})
await buildNuxt(nuxt)
if (args.prerender) {
if (!nuxt.options.ssr) {
consola.warn('HTML content not prerendered because `ssr: false` was set. You can read more in `https://nuxt.com/docs/getting-started/deployment#static-hosting`.')
}
// TODO: revisit later if/when nuxt build --prerender will output hybrid
const dir = nitro?.options.output.publicDir
const publicDir = dir ? relative(process.cwd(), dir) : '.output/public'
consola.success(`You can now deploy \`${publicDir}\` to any static hosting!`)
}
}
})

View File

@ -1,15 +0,0 @@
import { resolve } from 'pathe'
import { cleanupNuxtDirs } from '../utils/nuxt'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'cleanup',
usage: 'npx nuxi clean|cleanup',
description: 'Cleanup generated nuxt files and caches'
},
async invoke (args) {
const rootDir = resolve(args._[0] || '.')
await cleanupNuxtDirs(rootDir)
}
})

View File

@ -1,190 +0,0 @@
import type { AddressInfo } from 'node:net'
import type { RequestListener } from 'node:http'
import { relative, resolve } from 'pathe'
import chokidar from 'chokidar'
import { debounce } from 'perfect-debounce'
import type { Nuxt } from '@nuxt/schema'
import { consola } from 'consola'
import { withTrailingSlash } from 'ufo'
import { setupDotenv } from 'c12'
// we are deliberately inlining this code as a backup in case user has `@nuxt/schema<3.7`
import { writeTypes as writeTypesLegacy } from '../../../kit/src/template'
import { showBanner, showVersions } from '../utils/banner'
import { loadKit } from '../utils/kit'
import { importModule } from '../utils/esm'
import { overrideEnv } from '../utils/env'
import { loadNuxtManifest, writeNuxtManifest } from '../utils/nuxt'
import { clearBuildDir } from '../utils/fs'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'dev',
usage: 'npx nuxi dev [rootDir] [--dotenv] [--log-level] [--clipboard] [--open, -o] [--port, -p] [--host, -h] [--https] [--ssl-cert] [--ssl-key]',
description: 'Run nuxt development server'
},
async invoke (args, options = {}) {
overrideEnv('development')
const rootDir = resolve(args._[0] || '.')
showVersions(rootDir)
await setupDotenv({ cwd: rootDir, fileName: args.dotenv })
const { loadNuxt, loadNuxtConfig, buildNuxt, writeTypes = writeTypesLegacy } = await loadKit(rootDir)
const config = await loadNuxtConfig({
cwd: rootDir,
overrides: {
dev: true,
logLevel: args['log-level'],
...(options.overrides || {})
}
})
const { listen } = await import('listhen')
const { toNodeListener } = await import('h3')
let currentHandler: RequestListener | undefined
let loadingMessage = 'Nuxt is starting...'
const loadingHandler: RequestListener = async (_req, res) => {
const loadingTemplate = config.devServer.loadingTemplate ?? await importModule('@nuxt/ui-templates', config.modulesDir).then(r => r.loading)
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
res.statusCode = 503 // Service Unavailable
res.end(loadingTemplate({ loading: loadingMessage }))
}
const serverHandler: RequestListener = (req, res) => {
return currentHandler ? currentHandler(req, res) : loadingHandler(req, res)
}
const listener = await listen(serverHandler, {
showURL: false,
clipboard: args.clipboard,
open: args.open || args.o,
port: args.port || args.p || process.env.NUXT_PORT || process.env.NITRO_PORT || config.devServer.port,
hostname: args.host || args.h || process.env.NUXT_HOST || process.env.NITRO_HOST || config.devServer.host,
https: (args.https !== false && (args.https || config.devServer.https))
? {
cert: args['ssl-cert'] || process.env.NUXT_SSL_CERT || process.env.NITRO_SSL_CERT || (typeof config.devServer.https !== 'boolean' && config.devServer.https.cert) || undefined,
key: args['ssl-key'] || process.env.NUXT_SSL_KEY || process.env.NITRO_SSL_KEY || (typeof config.devServer.https !== 'boolean' && config.devServer.https.key) || undefined
}
: false
})
let currentNuxt: Nuxt
let distWatcher: chokidar.FSWatcher
const showURL = () => {
listener.showURL({
// TODO: Normalize URL with trailing slash within schema
baseURL: withTrailingSlash(currentNuxt?.options.app.baseURL) || '/'
})
}
async function hardRestart (reason?: string) {
if (process.send) {
await listener.close().catch(() => {})
await currentNuxt.close().catch(() => {})
await watcher.close().catch(() => {})
await distWatcher.close().catch(() => {})
consola.info(`${reason ? reason + '. ' : ''}Restarting nuxt...`)
process.send({ type: 'nuxt:restart' })
} else {
await load(true, reason)
}
}
const load = async (isRestart: boolean, reason?: string) => {
try {
loadingMessage = `${reason ? reason + '. ' : ''}${isRestart ? 'Restarting' : 'Starting'} nuxt...`
currentHandler = undefined
if (isRestart) {
consola.info(loadingMessage)
}
if (currentNuxt) {
await currentNuxt.close()
}
if (distWatcher) {
await distWatcher.close()
}
currentNuxt = await loadNuxt({
rootDir,
dev: true,
ready: false,
overrides: {
logLevel: args['log-level'],
vite: {
clearScreen: args.clear
},
...(options.overrides || {})
}
})
if (!isRestart) {
showURL()
}
// Write manifest and also check if we need cache invalidation
if (!isRestart) {
const previousManifest = await loadNuxtManifest(currentNuxt.options.buildDir)
const newManifest = await writeNuxtManifest(currentNuxt)
if (previousManifest && newManifest && previousManifest._hash !== newManifest._hash) {
await clearBuildDir(currentNuxt.options.buildDir)
}
}
await currentNuxt.ready()
const unsub = currentNuxt.hooks.hook('restart', async (options) => {
unsub() // we use this instead of `hookOnce` for Nuxt Bridge support
if (options?.hard) { return hardRestart() }
await load(true)
})
await currentNuxt.hooks.callHook('listen', listener.server, listener)
const address = (listener.server.address() || {}) as AddressInfo
currentNuxt.options.devServer.url = listener.url
currentNuxt.options.devServer.port = address.port
currentNuxt.options.devServer.host = address.address
currentNuxt.options.devServer.https = listener.https
await Promise.all([
writeTypes(currentNuxt).catch(console.error),
buildNuxt(currentNuxt)
])
distWatcher = chokidar.watch(resolve(currentNuxt.options.buildDir, 'dist'), { ignoreInitial: true, depth: 0 })
distWatcher.on('unlinkDir', () => {
dLoad(true, '.nuxt/dist directory has been removed')
})
currentHandler = toNodeListener(currentNuxt.server.app)
if (isRestart && args.clear !== false) {
showBanner()
showURL()
}
} catch (err) {
consola.error(`Cannot ${isRestart ? 'restart' : 'start'} nuxt: `, err)
currentHandler = undefined
loadingMessage = 'Error while loading nuxt. Please check console and fix errors.'
}
}
// Watch for config changes
// TODO: Watcher service, modules, and requireTree
const dLoad = debounce(load)
const watcher = chokidar.watch([rootDir], { ignoreInitial: true, depth: 0 })
watcher.on('all', (_event, _file) => {
const file = relative(rootDir, _file)
if (file === (args.dotenv || '.env')) { return hardRestart('.env updated') }
if (RESTART_RE.test(file)) {
dLoad(true, `${file} updated`)
}
})
await load(false)
return 'wait' as const
}
})
const RESTART_RE = /^(nuxt\.config\.(js|ts|mjs|cjs)|\.nuxtignore|\.nuxtrc)$/

View File

@ -1,24 +0,0 @@
import { resolve } from 'pathe'
import { execa } from 'execa'
import { showHelp } from '../utils/help'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'enable',
usage: 'npx nuxi devtools enable|disable [rootDir]',
description: 'Enable or disable features in a Nuxt project'
},
async invoke (args) {
const [command, _rootDir = '.'] = args._
const rootDir = resolve(_rootDir)
if (!['enable', 'disable'].includes(command)) {
console.error(`Unknown command \`${command}\`.`)
showHelp(this.meta)
process.exit(1)
}
await execa('npx', ['@nuxt/devtools-wizard@latest', command, rootDir], { stdio: 'inherit', cwd: rootDir })
}
})

View File

@ -1,14 +0,0 @@
import buildCommand from './build'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'generate',
usage: 'npx nuxi generate [rootDir] [--dotenv]',
description: 'Build Nuxt and prerender static routes'
},
async invoke (args, options = {}) {
args.prerender = true
await buildCommand.invoke(args, options)
}
})

View File

@ -1,46 +0,0 @@
import type { Argv } from 'mri'
const _rDefault = (r: any) => r.default || r
export const commands = {
dev: () => import('./dev').then(_rDefault),
build: () => import('./build').then(_rDefault),
'build-module': () => import('./build-module').then(_rDefault),
cleanup: () => import('./cleanup').then(_rDefault),
clean: () => import('./cleanup').then(_rDefault),
preview: () => import('./preview').then(_rDefault),
start: () => import('./preview').then(_rDefault),
analyze: () => import('./analyze').then(_rDefault),
generate: () => import('./generate').then(_rDefault),
prepare: () => import('./prepare').then(_rDefault),
typecheck: () => import('./typecheck').then(_rDefault),
usage: () => import('./usage').then(_rDefault),
info: () => import('./info').then(_rDefault),
init: () => import('./init').then(_rDefault),
create: () => import('./init').then(_rDefault),
devtools: () => import('./devtools').then(_rDefault),
upgrade: () => import('./upgrade').then(_rDefault),
test: () => import('./test').then(_rDefault),
add: () => import('./add').then(_rDefault),
new: () => import('./add').then(_rDefault)
}
export type Command = keyof typeof commands
export interface NuxtCommandMeta {
name: string;
usage: string;
description: string;
[key: string]: any;
}
export type CLIInvokeResult = void | 'error' | 'wait'
export interface NuxtCommand {
invoke(args: Argv, options?: Record<string, any>): Promise<CLIInvokeResult> | CLIInvokeResult
meta: NuxtCommandMeta
}
export function defineNuxtCommand (command: NuxtCommand): NuxtCommand {
return command
}

View File

@ -1,161 +0,0 @@
import os from 'node:os'
import { existsSync, readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { resolve } from 'pathe'
import jiti from 'jiti'
import destr from 'destr'
import type { PackageJson } from 'pkg-types'
import { splitByCase } from 'scule'
import clipboardy from 'clipboardy'
import type { NuxtModule } from '@nuxt/schema'
import type { packageManagerLocks } from '../utils/packageManagers'
import { getPackageManager, getPackageManagerVersion } from '../utils/packageManagers'
import { findup } from '../utils/fs'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'info',
usage: 'npx nuxi info [rootDir]',
description: 'Get information about nuxt project'
},
async invoke (args) {
// Resolve rootDir
const rootDir = resolve(args._[0] || '.')
// Load nuxt.config
const nuxtConfig = getNuxtConfig(rootDir)
// Find nearest package.json
const { dependencies = {}, devDependencies = {} } = findPackage(rootDir)
// Utils to query a dependency version
const getDepVersion = (name: string) => getPkg(name, rootDir)?.version || dependencies[name] || devDependencies[name]
const listModules = (arr = []) => arr
.map(m => normalizeConfigModule(m, rootDir))
.filter(Boolean)
.map((name) => {
const npmName = name!.split('/').splice(0, 2).join('/') // @foo/bar/baz => @foo/bar
const v = getDepVersion(npmName)
return '`' + (v ? `${name}@${v}` : name) + '`'
})
.join(', ')
// Check nuxt version
const nuxtVersion = getDepVersion('nuxt') || getDepVersion('nuxt-edge') || getDepVersion('nuxt3') || '0.0.0'
const isNuxt3 = nuxtVersion.startsWith('3')
const builder = isNuxt3
? nuxtConfig.builder /* latest schema */ || (nuxtConfig.vite !== false ? 'vite' : 'webpack') /* previous schema */
: nuxtConfig.bridge?.vite
? 'vite' /* bridge vite implementation */
: (nuxtConfig.buildModules?.includes('nuxt-vite')
? 'vite' /* nuxt-vite */
: 'webpack')
let packageManager: keyof typeof packageManagerLocks | 'unknown' | null = getPackageManager(rootDir)
if (packageManager) {
packageManager += '@' + getPackageManagerVersion(packageManager)
} else {
packageManager = 'unknown'
}
const infoObj = {
OperatingSystem: os.type(),
NodeVersion: process.version,
NuxtVersion: nuxtVersion,
NitroVersion: getDepVersion('nitropack'),
PackageManager: packageManager,
Builder: builder,
UserConfig: Object.keys(nuxtConfig).map(key => '`' + key + '`').join(', '),
RuntimeModules: listModules(nuxtConfig.modules),
BuildModules: listModules(nuxtConfig.buildModules || [])
}
console.log('RootDir:', rootDir)
let maxLength = 0
const entries = Object.entries(infoObj).map(([key, val]) => {
const label = splitByCase(key).join(' ')
if (label.length > maxLength) { maxLength = label.length }
return [label, val || '-']
})
let infoStr = ''
for (const [label, value] of entries) {
infoStr += '- ' + (label + ': ').padEnd(maxLength + 2) + (value.includes('`') ? value : '`' + value + '`') + '\n'
}
const copied = await clipboardy.write(infoStr).then(() => true).catch(() => false)
const splitter = '------------------------------'
console.log(`Nuxt project info: ${copied ? '(copied to clipboard)' : ''}\n\n${splitter}\n${infoStr}${splitter}\n`)
const isNuxt3OrBridge = infoObj.NuxtVersion.startsWith('3') || infoObj.BuildModules.includes('bridge')
console.log([
'👉 Report an issue: https://github.com/nuxt/nuxt/issues/new',
'👉 Suggest an improvement: https://github.com/nuxt/nuxt/discussions/new',
`👉 Read documentation: ${isNuxt3OrBridge ? 'https://nuxt.com' : 'https://v2.nuxt.com'}`
].join('\n\n') + '\n')
}
})
function normalizeConfigModule (module: NuxtModule | string | null | undefined, rootDir: string): string | null {
if (!module) {
return null
}
if (typeof module === 'string') {
return module
.split(rootDir).pop()! // Strip rootDir
.split('node_modules').pop()! // Strip node_modules
.replace(/^\//, '')
}
if (typeof module === 'function') {
return `${module.name}()`
}
if (Array.isArray(module)) {
return normalizeConfigModule(module[0], rootDir)
}
return null
}
function getNuxtConfig (rootDir: string) {
try {
(globalThis as any).defineNuxtConfig = (c: any) => c
const result = jiti(rootDir, { interopDefault: true, esmResolve: true })('./nuxt.config')
delete (globalThis as any).defineNuxtConfig
return result
} catch (err) {
// TODO: Show error as warning if it is not 404
return {}
}
}
function getPkg (name: string, rootDir: string) {
// Assume it is in {rootDir}/node_modules/${name}/package.json
let pkgPath = resolve(rootDir, 'node_modules', name, 'package.json')
// Try to resolve for more accuracy
const _require = createRequire(rootDir)
try { pkgPath = _require.resolve(name + '/package.json') } catch (_err) {
// console.log('not found:', name)
}
return readJSONSync(pkgPath) as PackageJson
}
function findPackage (rootDir: string) {
return findup(rootDir, (dir) => {
const p = resolve(dir, 'package.json')
if (existsSync(p)) {
return readJSONSync(p) as PackageJson
}
}) || {}
}
function readJSONSync (filePath: string) {
try {
return destr(readFileSync(filePath, 'utf-8'))
} catch (err) {
// TODO: Warn error
return null
}
}

View File

@ -1,68 +0,0 @@
import { writeFile } from 'node:fs/promises'
import { downloadTemplate, startShell } from 'giget'
import { relative } from 'pathe'
import { consola } from 'consola'
import { defineNuxtCommand } from './index'
const rpath = (p: string) => relative(process.cwd(), p)
const DEFAULT_REGISTRY = 'https://raw.githubusercontent.com/nuxt/starter/templates/templates'
export default defineNuxtCommand({
meta: {
name: 'init',
usage: 'npx nuxi init|create [--template,-t] [--force] [--offline] [--prefer-offline] [--shell] [dir]',
description: 'Initialize a fresh project'
},
async invoke (args) {
// Clone template
const template = args.template || args.t || 'v3'
if (typeof template === 'boolean') {
consola.error('Please specify a template!')
process.exit(1)
}
let t
try {
t = await downloadTemplate(template, {
dir: args._[0] as string,
force: args.force,
offline: args.offline,
preferOffline: args['prefer-offline'],
registry: process.env.NUXI_INIT_REGISTRY || DEFAULT_REGISTRY
})
} catch (err) {
if (process.env.DEBUG) {
throw err
}
consola.error((err as Error).toString())
process.exit(1)
}
// Show next steps
const relativeDist = rpath(t.dir)
// Write .nuxtrc with `shamefully-hoist=true` for pnpm
const usingPnpm = (process.env.npm_config_user_agent || '').includes('pnpm')
if (usingPnpm) {
await writeFile(`${relativeDist}/.npmrc`, 'shamefully-hoist=true')
}
const nextSteps = [
!args.shell && relativeDist.length > 1 && `\`cd ${relativeDist}\``,
'Install dependencies with `npm install` or `yarn install` or `pnpm install`',
'Start development server with `npm run dev` or `yarn dev` or `pnpm run dev`'
].filter(Boolean)
consola.log(`✨ Nuxt project is created with \`${t.name}\` template. Next steps:`)
for (const step of nextSteps) {
consola.log(` ${step}`)
}
if (args.shell) {
startShell(t.dir)
}
}
})

View File

@ -1,35 +0,0 @@
import { relative, resolve } from 'pathe'
import { consola } from 'consola'
// we are deliberately inlining this code as a backup in case user has `@nuxt/schema<3.7`
import { writeTypes as writeTypesLegacy } from '../../../kit/src/template'
import { clearBuildDir } from '../utils/fs'
import { loadKit } from '../utils/kit'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'prepare',
usage: 'npx nuxi prepare [--log-level] [rootDir]',
description: 'Prepare nuxt for development/build'
},
async invoke (args, options = {}) {
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const rootDir = resolve(args._[0] || '.')
const { loadNuxt, buildNuxt, writeTypes = writeTypesLegacy } = await loadKit(rootDir)
const nuxt = await loadNuxt({
rootDir,
overrides: {
_prepare: true,
logLevel: args['log-level'],
...(options.overrides || {})
}
})
await clearBuildDir(nuxt.options.buildDir)
await buildNuxt(nuxt)
await writeTypes(nuxt)
consola.success('Types generated in', relative(process.cwd(), nuxt.options.buildDir))
}
})

View File

@ -1,55 +0,0 @@
import { existsSync, promises as fsp } from 'node:fs'
import { dirname, relative } from 'node:path'
import { execa } from 'execa'
import { setupDotenv } from 'c12'
import { resolve } from 'pathe'
import { consola } from 'consola'
import { loadKit } from '../utils/kit'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'preview',
usage: 'npx nuxi preview|start [--dotenv] [rootDir]',
description: 'Launches nitro server for local testing after `nuxi build`.'
},
async invoke (args, options = {}) {
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const rootDir = resolve(args._[0] || '.')
const { loadNuxtConfig } = await loadKit(rootDir)
const config = await loadNuxtConfig({ cwd: rootDir, overrides: options?.overrides || {} })
const resolvedOutputDir = resolve(config.srcDir || rootDir, config.nitro.srcDir || 'server', config.nitro.output?.dir || '.output', 'nitro.json')
const defaultOutput = resolve(rootDir, '.output', 'nitro.json') // for backwards compatibility
const nitroJSONPaths = [resolvedOutputDir, defaultOutput]
const nitroJSONPath = nitroJSONPaths.find(p => existsSync(p))
if (!nitroJSONPath) {
consola.error('Cannot find `nitro.json`. Did you run `nuxi build` first? Search path:\n', nitroJSONPaths)
process.exit(1)
}
const outputPath = dirname(nitroJSONPath)
const nitroJSON = JSON.parse(await fsp.readFile(nitroJSONPath, 'utf-8'))
consola.info('Node.js version:', process.versions.node)
consola.info('Preset:', nitroJSON.preset)
consola.info('Working dir:', relative(process.cwd(), outputPath))
if (!nitroJSON.commands.preview) {
consola.error('Preview is not supported for this build.')
process.exit(1)
}
const envExists = args.dotenv ? existsSync(resolve(rootDir, args.dotenv)) : existsSync(rootDir)
if (envExists) {
consola.info('Loading `.env`. This will not be loaded when running the server in production.')
await setupDotenv({ cwd: rootDir, fileName: args.dotenv })
}
consola.info('Starting preview command:', nitroJSON.commands.preview)
const [command, ...commandArgs] = nitroJSON.commands.preview.split(' ')
consola.log('')
await execa(command, commandArgs, { stdio: 'inherit', cwd: outputPath })
}
})

View File

@ -1,43 +0,0 @@
import { resolve } from 'pathe'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'test',
usage: 'npx nuxi test [--dev] [--watch] [rootDir]',
description: 'Run tests'
},
async invoke (args, options = {}) {
process.env.NODE_ENV = process.env.NODE_ENV || 'test'
const rootDir = resolve(args._[0] || '.')
const { runTests } = await importTestUtils()
await runTests({
rootDir,
dev: !!args.dev,
watch: !!args.watch,
...(options || {})
})
if (args.watch) {
return 'wait' as const
}
}
})
async function importTestUtils (): Promise<typeof import('@nuxt/test-utils')> {
let err
for (const pkg of ['@nuxt/test-utils-edge', '@nuxt/test-utils']) {
try {
const exports = await import(pkg)
// Detect old @nuxt/test-utils
if (!exports.runTests) {
throw new Error('Invalid version of `@nuxt/test-utils` is installed!')
}
return exports
} catch (_err) {
err = _err
}
}
console.error(err)
throw new Error('`@nuxt/test-utils-edge` seems missing. Run `npm i -D @nuxt/test-utils-edge` or `yarn add -D @nuxt/test-utils-edge` to install.')
}

View File

@ -1,43 +0,0 @@
import { execa } from 'execa'
import { resolve } from 'pathe'
// we are deliberately inlining this code as a backup in case user has `@nuxt/schema<3.7`
import { writeTypes as writeTypesLegacy } from '../../../kit/src/template'
import { tryResolveModule } from '../utils/esm'
import { loadKit } from '../utils/kit'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'typecheck',
usage: 'npx nuxi typecheck [--log-level] [rootDir]',
description: 'Runs `vue-tsc` to check types throughout your app.'
},
async invoke (args, options = {}) {
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const rootDir = resolve(args._[0] || '.')
const { loadNuxt, buildNuxt, writeTypes = writeTypesLegacy } = await loadKit(rootDir)
const nuxt = await loadNuxt({
rootDir,
overrides: {
_prepare: true,
logLevel: args['log-level'],
...(options?.overrides || {})
}
})
// Generate types and build nuxt instance
await writeTypes(nuxt)
await buildNuxt(nuxt)
await nuxt.close()
// Prefer local install if possible
const hasLocalInstall = await tryResolveModule('typescript', rootDir) && await tryResolveModule('vue-tsc/package.json', rootDir)
if (hasLocalInstall) {
await execa('vue-tsc', ['--noEmit'], { preferLocal: true, stdio: 'inherit', cwd: rootDir })
} else {
await execa('npx', '-p vue-tsc -p typescript vue-tsc --noEmit'.split(' '), { stdio: 'inherit', cwd: rootDir })
}
}
})

View File

@ -1,74 +0,0 @@
import { execSync } from 'node:child_process'
import { consola } from 'consola'
import { resolve } from 'pathe'
import { readPackageJSON } from 'pkg-types'
import { getPackageManager, packageManagerLocks } from '../utils/packageManagers'
import { rmRecursive, touchFile } from '../utils/fs'
import { cleanupNuxtDirs, nuxtVersionToGitIdentifier } from '../utils/nuxt'
import { defineNuxtCommand } from './index'
async function getNuxtVersion (path: string): Promise<string | null> {
try {
const pkg = await readPackageJSON('nuxt', { url: path })
if (!pkg.version) {
consola.warn('Cannot find any installed nuxt versions in ', path)
}
return pkg.version || null
} catch {
return null
}
}
export default defineNuxtCommand({
meta: {
name: 'upgrade',
usage: 'npx nuxi upgrade [--force|-f]',
description: 'Upgrade nuxt'
},
async invoke (args) {
const rootDir = resolve(args._[0] || '.')
// Check package manager
const packageManager = getPackageManager(rootDir)
if (!packageManager) {
console.error('Cannot detect Package Manager in', rootDir)
process.exit(1)
}
const packageManagerVersion = execSync(`${packageManager} --version`).toString('utf8').trim()
consola.info('Package Manager:', packageManager, packageManagerVersion)
// Check currently installed nuxt version
const currentVersion = await getNuxtVersion(rootDir) || '[unknown]'
consola.info('Current nuxt version:', currentVersion)
// Force install
if (args.force || args.f) {
consola.info('Removing lock-file and node_modules...')
const pmLockFile = resolve(rootDir, packageManagerLocks[packageManager])
await rmRecursive([pmLockFile, resolve(rootDir, 'node_modules')])
await touchFile(pmLockFile)
}
// Install latest version
consola.info('Installing latest Nuxt 3 release...')
execSync(`${packageManager} ${packageManager === 'yarn' ? 'add' : 'install'} -D nuxt`, { stdio: 'inherit', cwd: rootDir })
// Cleanup after upgrade
await cleanupNuxtDirs(rootDir)
// Check installed nuxt version again
const upgradedVersion = await getNuxtVersion(rootDir) || '[unknown]'
consola.info('Upgraded nuxt version:', upgradedVersion)
if (upgradedVersion === currentVersion) {
consola.success('You\'re already using the latest version of nuxt.')
} else {
consola.success('Successfully upgraded nuxt from', currentVersion, 'to', upgradedVersion)
const commitA = nuxtVersionToGitIdentifier(currentVersion)
const commitB = nuxtVersionToGitIdentifier(upgradedVersion)
if (commitA && commitB) {
consola.info('Changelog:', `https://github.com/nuxt/nuxt/compare/${commitA}...${commitB}`)
}
}
}
})

View File

@ -1,21 +0,0 @@
import { cyan } from 'colorette'
import { showHelp } from '../utils/help'
import { commands, defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'help',
usage: 'nuxt help',
description: 'Show help'
},
invoke (_args) {
const sections: string[] = []
sections.push(`Usage: ${cyan(`npx nuxi ${Object.keys(commands).join('|')} [args]`)}`)
console.log(sections.join('\n\n') + '\n')
// Reuse the same wording as in `-h` commands
showHelp({})
}
})

View File

@ -1 +0,0 @@
export * from './run'

View File

@ -1,13 +0,0 @@
import mri from 'mri'
import type { Command, NuxtCommand } from './commands'
import { commands } from './commands'
export async function runCommand (command: string, argv = process.argv.slice(2), options: Record<string, any> = {}) {
const args = mri(argv)
args.clear = false // used by dev
const cmd = await commands[command as Command]() as NuxtCommand
if (!cmd) {
throw new Error(`Invalid command ${command}`)
}
await cmd.invoke(args, options)
}

View File

@ -1,21 +0,0 @@
import clear from 'clear'
import { bold, gray, green } from 'colorette'
import { version } from '../../package.json'
import { tryRequireModule } from './cjs'
export function showBanner (_clear?: boolean) {
if (_clear) { clear() }
console.log(gray(`Nuxi ${(bold(version))}`))
}
export function showVersions (cwd: string) {
const getPkgVersion = (pkg: string) => {
return tryRequireModule(`${pkg}/package.json`, cwd)?.version || ''
}
const nuxtVersion = getPkgVersion('nuxt') || getPkgVersion('nuxt-edge')
const nitroVersion = getPkgVersion('nitropack')
console.log(gray(
green(`Nuxt ${bold(nuxtVersion)}`) +
(nitroVersion ? ` with Nitro ${(bold(nitroVersion))}` : '')
))
}

View File

@ -1,27 +0,0 @@
import { createRequire } from 'node:module'
import { normalize } from 'pathe'
function getModulePaths (paths?: string | string[]): string[] {
return ([] as Array<string | undefined>)
.concat(
global.__NUXT_PREPATHS__,
paths,
process.cwd(),
global.__NUXT_PATHS__
)
.filter(Boolean) as string[]
}
const _require = createRequire(process.cwd())
function resolveModule (id: string, paths?: string | string[]) {
return normalize(_require.resolve(id, { paths: getModulePaths(paths) }))
}
function requireModule (id: string, paths?: string | string[]) {
return _require(resolveModule(id, paths))
}
export function tryRequireModule (id: string, paths?: string | string[]) {
try { return requireModule(id, paths) } catch { return null }
}

View File

@ -1,31 +0,0 @@
import flatten from 'flat'
import { detailedDiff } from 'deep-object-diff'
import { blue, cyan, green, red } from 'colorette'
function normalizeDiff (diffObj: any, type: 'added' | 'deleted' | 'updated', ignore: string[]) {
return Object.entries(flatten(diffObj) as Record<string, any>)
.map(([key, value]) => ({ key, value, type }))
.filter(item => !ignore.includes(item.key) && typeof item.value !== 'function')
}
export function diff (a: any, b: any, ignore: string[]) {
const _diff: any = detailedDiff(a, b)
return [
...normalizeDiff(_diff.added, 'added', ignore),
...normalizeDiff(_diff.deleted, 'deleted', ignore),
...normalizeDiff(_diff.updated, 'updated', ignore)
]
}
const typeMap = {
added: green('added'),
deleted: red('deleted'),
updated: blue('updated')
}
export function printDiff (diff: any) {
for (const item of diff) {
console.log(' ', typeMap[item.type as keyof typeof typeMap] || item.type, cyan(item.key), item.value ? `~> ${cyan(item.value)}` : '')
}
console.log()
}

View File

@ -1,12 +0,0 @@
import { engines } from '../../package.json'
export async function checkEngines () {
const satisfies = await import('semver/functions/satisfies.js')
.then(r => r.default || r as any as typeof import('semver/functions/satisfies.js')) // npm/node-semver#381
const currentNode = process.versions.node
const nodeRange = engines.node
if (!satisfies(currentNode, nodeRange)) {
console.warn(`Current version of Node.js (\`${currentNode}\`) is unsupported and might cause issues.\n Please upgrade to a compatible version (${nodeRange}).`)
}
}

View File

@ -1,8 +0,0 @@
export const overrideEnv = (targetEnv: string) => {
const currentEnv = process.env.NODE_ENV
if (currentEnv && currentEnv !== targetEnv) {
console.warn(`Changing \`NODE_ENV\` from \`${currentEnv}\` to \`${targetEnv}\`, to avoid unintended behavior.`)
}
process.env.NODE_ENV = targetEnv
}

View File

@ -1,13 +0,0 @@
import { pathToFileURL } from 'node:url'
import { interopDefault, resolvePath } from 'mlly'
export async function tryResolveModule (id: string, url = import.meta.url) {
try {
return await resolvePath(id, { url })
} catch { }
}
export async function importModule (id: string, url: string | string[] = import.meta.url) {
const resolvedPath = await resolvePath(id, { url })
return import(pathToFileURL(resolvedPath).href).then(interopDefault)
}

View File

@ -1,45 +0,0 @@
import { existsSync, promises as fsp } from 'node:fs'
import { dirname, join } from 'pathe'
import { consola } from 'consola'
export async function clearDir (path: string, exclude?: string[]) {
if (!exclude) {
await fsp.rm(path, { recursive: true, force: true })
} else if (existsSync(path)) {
const files = await fsp.readdir(path)
await Promise.all(files.map(async (name) => {
if (!exclude.includes(name)) {
await fsp.rm(join(path, name), { recursive: true, force: true })
}
}))
}
await fsp.mkdir(path, { recursive: true })
}
export function clearBuildDir (path: string) {
return clearDir(path, ['cache', 'analyze'])
}
export async function rmRecursive (paths: string[]) {
await Promise.all(paths.filter(p => typeof p === 'string').map(async (path) => {
consola.debug('Removing recursive path', path)
await fsp.rm(path, { recursive: true, force: true }).catch(() => {})
}))
}
export async function touchFile (path: string) {
const time = new Date()
await fsp.utimes(path, time, time).catch(() => {})
}
export function findup<T> (rootDir: string, fn: (dir: string) => T | undefined): T | null {
let dir = rootDir
while (dir !== dirname(dir)) {
const res = fn(dir)
if (res) {
return res
}
dir = dirname(dir)
}
return null
}

View File

@ -1,20 +0,0 @@
import { cyan, magenta } from 'colorette'
import type { NuxtCommandMeta } from '../commands'
export function showHelp (meta?: Partial<NuxtCommandMeta>) {
const sections: string[] = []
if (meta) {
if (meta.usage) {
sections.push(magenta('> ') + 'Usage: ' + cyan(meta.usage))
}
if (meta.description) {
sections.push(magenta('⋮ ') + meta.description)
}
}
sections.push(`Use ${cyan('npx nuxi [command] --help')} to see help for each command`)
console.log(sections.join('\n\n') + '\n')
}

View File

@ -1,24 +0,0 @@
import { importModule, tryResolveModule } from './esm'
export const loadKit = async (rootDir: string): Promise<typeof import('@nuxt/kit')> => {
try {
// Without PNP (or if users have a local install of kit, we bypass resolving from nuxt)
const localKit = await tryResolveModule('@nuxt/kit', rootDir)
// Otherwise, we resolve Nuxt _first_ as it is Nuxt's kit dependency that will be used
const rootURL = localKit ? rootDir : await tryResolveNuxt() || rootDir
return await importModule('@nuxt/kit', rootURL) as typeof import('@nuxt/kit')
} catch (e: any) {
if (e.toString().includes("Cannot find module '@nuxt/kit'")) {
throw new Error('nuxi requires `@nuxt/kit` to be installed in your project. Try installing `nuxt` v3 or `@nuxt/bridge` first.')
}
throw e
}
}
async function tryResolveNuxt () {
for (const pkg of ['nuxt3', 'nuxt', 'nuxt-edge']) {
const path = await tryResolveModule(pkg)
if (path) { return path }
}
return null
}

View File

@ -1,68 +0,0 @@
import { promises as fsp } from 'node:fs'
import { dirname, resolve } from 'pathe'
import { consola } from 'consola'
import { hash } from 'ohash'
import type { Nuxt } from '@nuxt/schema'
import { rmRecursive } from './fs'
interface NuxtProjectManifest {
_hash: string | null
project: {
rootDir: string
},
versions: {
nuxt: string
}
}
export async function cleanupNuxtDirs (rootDir: string) {
consola.info('Cleaning up generated nuxt files and caches...')
await rmRecursive([
'.nuxt',
'.output',
'dist',
'node_modules/.vite',
'node_modules/.cache'
].map(dir => resolve(rootDir, dir)))
}
export function nuxtVersionToGitIdentifier (version: string) {
// match the git identifier in the release, for example: 3.0.0-rc.8-27677607.a3a8706
const id = /\.([0-9a-f]{7,8})$/.exec(version)
if (id?.[1]) {
return id[1]
}
// match github tag, for example 3.0.0-rc.8
return `v${version}`
}
function resolveNuxtManifest (nuxt: Nuxt): NuxtProjectManifest {
const manifest: NuxtProjectManifest = {
_hash: null,
project: {
rootDir: nuxt.options.rootDir
},
versions: {
nuxt: nuxt._version
}
}
manifest._hash = hash(manifest)
return manifest
}
export async function writeNuxtManifest (nuxt: Nuxt): Promise<NuxtProjectManifest> {
const manifest = resolveNuxtManifest(nuxt)
const manifestPath = resolve(nuxt.options.buildDir, 'nuxt.json')
await fsp.mkdir(dirname(manifestPath), { recursive: true })
await fsp.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8')
return manifest
}
export async function loadNuxtManifest (buildDir: string): Promise<NuxtProjectManifest | null> {
const manifestPath = resolve(buildDir, 'nuxt.json')
const manifest: NuxtProjectManifest | null = await fsp.readFile(manifestPath, 'utf-8')
.then(data => JSON.parse(data) as NuxtProjectManifest)
.catch(() => null)
return manifest
}

View File

@ -1,28 +0,0 @@
import { execSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import { resolve } from 'pathe'
import { findup } from './fs'
export const packageManagerLocks = {
yarn: 'yarn.lock',
npm: 'package-lock.json',
pnpm: 'pnpm-lock.yaml',
bun: 'bun.lockb'
}
type PackageManager = keyof typeof packageManagerLocks
export function getPackageManager (rootDir: string) {
return findup(rootDir, (dir) => {
for (const name in packageManagerLocks) {
const path = packageManagerLocks[name as PackageManager]
if (path && existsSync(resolve(dir, path))) {
return name
}
}
}) as PackageManager | null
}
export function getPackageManagerVersion (name: string) {
return execSync(`${name} --version`).toString('utf8').trim()
}

View File

@ -1,119 +0,0 @@
import { upperFirst } from 'scule'
interface TemplateOptions {
name: string,
args: Record<string, any>
}
interface Template {
(options: TemplateOptions): { path: string, contents: string }
}
const httpMethods = ['connect', 'delete', 'get', 'head', 'options', 'post', 'put', 'trace', 'patch']
const api: Template = ({ name, args }) => ({
path: `server/api/${name}${applySuffix(args, httpMethods, 'method')}.ts`,
contents: `
export default defineEventHandler((event) => {
return 'Hello ${name}'
})
`
})
const plugin: Template = ({ name, args }) => ({
path: `plugins/${name}${applySuffix(args, ['client', 'server'], 'mode')}.ts`,
contents: `
export default defineNuxtPlugin((nuxtApp) => {})
`
})
const component: Template = ({ name, args }) => ({
path: `components/${name}${applySuffix(args, ['client', 'server'], 'mode')}.vue`,
contents: `
<script lang="ts" setup></script>
<template>
<div>
Component: ${name}
</div>
</template>
<style scoped></style>
`
})
const composable: Template = ({ name }) => {
const nameWithUsePrefix = name.startsWith('use') ? name : `use${upperFirst(name)}`
return {
path: `composables/${name}.ts`,
contents: `
export const ${nameWithUsePrefix} = () => {
return ref()
}
`
}
}
const middleware: Template = ({ name, args }) => ({
path: `middleware/${name}${applySuffix(args, ['global'])}.ts`,
contents: `
export default defineNuxtRouteMiddleware((to, from) => {})
`
})
const layout: Template = ({ name }) => ({
path: `layouts/${name}.vue`,
contents: `
<script lang="ts" setup></script>
<template>
<div>
Layout: ${name}
<slot />
</div>
</template>
<style scoped></style>
`
})
const page: Template = ({ name }) => ({
path: `pages/${name}.vue`,
contents: `
<script lang="ts" setup></script>
<template>
<div>
Page: foo
</div>
</template>
<style scoped></style>
`
})
export const templates = {
api,
plugin,
component,
composable,
middleware,
layout,
page
} as Record<string, Template>
// -- internal utils --
function applySuffix (args: TemplateOptions['args'], suffixes: string[], unwrapFrom?: string): string {
let suffix = ''
// --client
for (const s of suffixes) {
if (args[s]) {
suffix += '.' + s
}
}
// --mode=server
if (unwrapFrom && args[unwrapFrom] && suffixes.includes(args[unwrapFrom])) {
suffix += '.' + args[unwrapFrom]
}
return suffix
}

View File

@ -58,9 +58,9 @@
"@nuxt/telemetry": "^2.4.1", "@nuxt/telemetry": "^2.4.1",
"@nuxt/ui-templates": "^1.3.1", "@nuxt/ui-templates": "^1.3.1",
"@nuxt/vite-builder": "workspace:../vite", "@nuxt/vite-builder": "workspace:../vite",
"@unhead/dom": "^1.3.5", "@unhead/dom": "^1.3.7",
"@unhead/ssr": "^1.3.5", "@unhead/ssr": "^1.3.7",
"@unhead/vue": "^1.3.5", "@unhead/vue": "^1.3.7",
"@vue/shared": "^3.3.4", "@vue/shared": "^3.3.4",
"acorn": "8.10.0", "acorn": "8.10.0",
"c12": "^1.4.2", "c12": "^1.4.2",
@ -79,26 +79,26 @@
"jiti": "^1.19.3", "jiti": "^1.19.3",
"klona": "^2.0.6", "klona": "^2.0.6",
"knitwork": "^1.0.0", "knitwork": "^1.0.0",
"magic-string": "^0.30.2", "magic-string": "^0.30.3",
"mlly": "^1.4.0", "mlly": "^1.4.0",
"nitropack": "^2.5.2", "nitropack": "^2.6.0",
"nuxi": "workspace:../nuxi", "nuxi": "npm:nuxi-ng@0.3.0-1692970235.c259efa",
"nypm": "^0.3.0", "nypm": "^0.3.1",
"ofetch": "^1.1.1", "ofetch": "^1.3.3",
"ohash": "^1.1.3", "ohash": "^1.1.3",
"pathe": "^1.1.1", "pathe": "^1.1.1",
"perfect-debounce": "^1.0.0", "perfect-debounce": "^1.0.0",
"pkg-types": "^1.0.3", "pkg-types": "^1.0.3",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"scule": "^1.0.0", "scule": "^1.0.0",
"std-env": "^3.4.0", "std-env": "^3.4.3",
"strip-literal": "^1.3.0", "strip-literal": "^1.3.0",
"ufo": "^1.2.0", "ufo": "^1.3.0",
"ultrahtml": "^1.3.0", "ultrahtml": "^1.3.0",
"uncrypto": "^0.1.3", "uncrypto": "^0.1.3",
"unctx": "^2.3.1", "unctx": "^2.3.1",
"unenv": "^1.7.1", "unenv": "^1.7.3",
"unimport": "^3.1.3", "unimport": "^3.2.0",
"unplugin": "^1.4.0", "unplugin": "^1.4.0",
"unplugin-vue-router": "^0.6.4", "unplugin-vue-router": "^0.6.4",
"untyped": "^1.4.0", "untyped": "^1.4.0",
@ -108,11 +108,11 @@
"vue-router": "^4.2.4" "vue-router": "^4.2.4"
}, },
"devDependencies": { "devDependencies": {
"@parcel/watcher": "2.2.0", "@parcel/watcher": "2.3.0",
"@types/estree": "1.0.1", "@types/estree": "1.0.1",
"@types/fs-extra": "11.0.1", "@types/fs-extra": "11.0.1",
"@types/prompts": "2.4.4", "@types/prompts": "2.4.4",
"@vitejs/plugin-vue": "4.3.1", "@vitejs/plugin-vue": "4.3.3",
"unbuild": "latest", "unbuild": "latest",
"vite": "4.4.9", "vite": "4.4.9",
"vitest": "0.33.0" "vitest": "0.33.0"

View File

@ -145,7 +145,7 @@ export function useAsyncData<
const hasCachedData = () => getCachedData() !== undefined const hasCachedData = () => getCachedData() !== undefined
// Create or use a shared asyncData entity // Create or use a shared asyncData entity
if (!nuxt._asyncData[key]) { if (!nuxt._asyncData[key] || !options.immediate) {
nuxt._asyncData[key] = { nuxt._asyncData[key] = {
data: ref(getCachedData() ?? options.default!()), data: ref(getCachedData() ?? options.default!()),
pending: ref(!hasCachedData()), pending: ref(!hasCachedData()),

View File

@ -2,7 +2,7 @@ import type { Ref } from 'vue'
import { getCurrentInstance, nextTick, onUnmounted, ref, toRaw, watch } from 'vue' import { getCurrentInstance, nextTick, onUnmounted, ref, toRaw, watch } from 'vue'
import type { CookieParseOptions, CookieSerializeOptions } from 'cookie-es' import type { CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
import { parse, serialize } from 'cookie-es' import { parse, serialize } from 'cookie-es'
import { deleteCookie, getCookie, setCookie } from 'h3' import { deleteCookie, getCookie, getRequestHeader, setCookie } from 'h3'
import type { H3Event } from 'h3' import type { H3Event } from 'h3'
import destr from 'destr' import destr from 'destr'
import { isEqual } from 'ohash' import { isEqual } from 'ohash'
@ -80,7 +80,7 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
function readRawCookies (opts: CookieOptions = {}): Record<string, string> | undefined { function readRawCookies (opts: CookieOptions = {}): Record<string, string> | undefined {
if (import.meta.server) { if (import.meta.server) {
return parse(useRequestEvent()?.node.req.headers.cookie || '', opts) return parse(getRequestHeader(useRequestEvent(), 'cookie') || '', opts)
} else if (import.meta.client) { } else if (import.meta.client) {
return parse(document.cookie, opts) return parse(document.cookie, opts)
} }

View File

@ -1,5 +1,5 @@
import type { H3Event } from 'h3' import type { H3Event } from 'h3'
import { setResponseStatus as _setResponseStatus } from 'h3' import { setResponseStatus as _setResponseStatus, getRequestHeaders } from 'h3'
import type { NuxtApp } from '../nuxt' import type { NuxtApp } from '../nuxt'
import { useNuxtApp } from '../nuxt' import { useNuxtApp } from '../nuxt'
@ -7,7 +7,8 @@ export function useRequestHeaders<K extends string = string> (include: K[]): { [
export function useRequestHeaders (): Readonly<Record<string, string>> export function useRequestHeaders (): Readonly<Record<string, string>>
export function useRequestHeaders (include?: any[]) { export function useRequestHeaders (include?: any[]) {
if (import.meta.client) { return {} } if (import.meta.client) { return {} }
const headers = useNuxtApp().ssrContext?.event.node.req.headers ?? {} const event = useNuxtApp().ssrContext?.event
const headers = event ? getRequestHeaders(event) : {}
if (!include) { return headers } if (!include) { return headers }
return Object.fromEntries(include.map(key => key.toLowerCase()).filter(key => headers[key]).map(key => [key, headers[key]])) return Object.fromEntries(include.map(key => key.toLowerCase()).filter(key => headers[key]).map(key => [key, headers[key]]))
} }

View File

@ -208,16 +208,16 @@ export default defineNuxtModule<ComponentsOptions>({
config.plugins = config.plugins || [] config.plugins = config.plugins || []
if (nuxt.options.experimental.treeshakeClientOnly && isServer) { if (nuxt.options.experimental.treeshakeClientOnly && isServer) {
config.plugins.push(TreeShakeTemplatePlugin.vite({ config.plugins.push(TreeShakeTemplatePlugin.vite({
sourcemap: nuxt.options.sourcemap[mode], sourcemap: !!nuxt.options.sourcemap[mode],
getComponents getComponents
})) }))
} }
config.plugins.push(clientFallbackAutoIdPlugin.vite({ config.plugins.push(clientFallbackAutoIdPlugin.vite({
sourcemap: nuxt.options.sourcemap[mode], sourcemap: !!nuxt.options.sourcemap[mode],
rootDir: nuxt.options.rootDir rootDir: nuxt.options.rootDir
})) }))
config.plugins.push(loaderPlugin.vite({ config.plugins.push(loaderPlugin.vite({
sourcemap: nuxt.options.sourcemap[mode], sourcemap: !!nuxt.options.sourcemap[mode],
getComponents, getComponents,
mode, mode,
transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined, transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined,
@ -252,16 +252,16 @@ export default defineNuxtModule<ComponentsOptions>({
config.plugins = config.plugins || [] config.plugins = config.plugins || []
if (nuxt.options.experimental.treeshakeClientOnly && mode === 'server') { if (nuxt.options.experimental.treeshakeClientOnly && mode === 'server') {
config.plugins.push(TreeShakeTemplatePlugin.webpack({ config.plugins.push(TreeShakeTemplatePlugin.webpack({
sourcemap: nuxt.options.sourcemap[mode], sourcemap: !!nuxt.options.sourcemap[mode],
getComponents getComponents
})) }))
} }
config.plugins.push(clientFallbackAutoIdPlugin.webpack({ config.plugins.push(clientFallbackAutoIdPlugin.webpack({
sourcemap: nuxt.options.sourcemap[mode], sourcemap: !!nuxt.options.sourcemap[mode],
rootDir: nuxt.options.rootDir rootDir: nuxt.options.rootDir
})) }))
config.plugins.push(loaderPlugin.webpack({ config.plugins.push(loaderPlugin.webpack({
sourcemap: nuxt.options.sourcemap[mode], sourcemap: !!nuxt.options.sourcemap[mode],
getComponents, getComponents,
mode, mode,
transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined, transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined,

View File

@ -38,7 +38,8 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
const directory = basename(dir.path) const directory = basename(dir.path)
if (!siblings.includes(directory)) { if (!siblings.includes(directory)) {
const caseCorrected = siblings.find(sibling => sibling.toLowerCase() === directory.toLowerCase()) const directoryLowerCase = directory.toLowerCase()
const caseCorrected = siblings.find(sibling => sibling.toLowerCase() === directoryLowerCase)
if (caseCorrected) { if (caseCorrected) {
const nuxt = useNuxt() const nuxt = useNuxt()
const original = relative(nuxt.options.srcDir, dir.path) const original = relative(nuxt.options.srcDir, dir.path)
@ -95,10 +96,7 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
const componentName = resolveComponentName(fileName, prefixParts) const componentName = resolveComponentName(fileName, prefixParts)
if (resolvedNames.has(componentName + suffix) || resolvedNames.has(componentName)) { if (resolvedNames.has(componentName + suffix) || resolvedNames.has(componentName)) {
console.warn(`[nuxt] Two component files resolving to the same name \`${componentName}\`:\n` + warnAboutDuplicateComponent(componentName, filePath, resolvedNames.get(componentName) || resolvedNames.get(componentName + suffix)!)
`\n - ${filePath}` +
`\n - ${resolvedNames.get(componentName)}`
)
continue continue
} }
resolvedNames.set(componentName + suffix, filePath) resolvedNames.set(componentName + suffix, filePath)
@ -136,10 +134,14 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
continue continue
} }
// Ignore component if component is already defined (with same mode) const existingComponent = components.find(c => c.pascalName === component.pascalName && ['all', component.mode].includes(c.mode))
if (!components.some(c => c.pascalName === component.pascalName && ['all', component.mode].includes(c.mode))) { if (existingComponent) {
components.push(component) // Ignore component if component is already defined (with same mode)
warnAboutDuplicateComponent(componentName, filePath, existingComponent.filePath)
continue
} }
components.push(component)
} }
scannedPaths.push(dir.path) scannedPaths.push(dir.path)
} }
@ -174,3 +176,10 @@ export function resolveComponentName (fileName: string, prefixParts: string[]) {
return pascalCase(componentNameParts) + pascalCase(fileNameParts) return pascalCase(componentNameParts) + pascalCase(fileNameParts)
} }
function warnAboutDuplicateComponent (componentName: string, filePath: string, duplicatePath: string) {
console.warn(`[nuxt] Two component files resolving to the same name \`${componentName}\`:\n` +
`\n - ${filePath}` +
`\n - ${duplicatePath}`
)
}

View File

@ -3,7 +3,7 @@ import { cpus } from 'node:os'
import { join, relative, resolve } from 'pathe' import { join, relative, resolve } from 'pathe'
import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack' import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack'
import type { Nitro, NitroConfig } from 'nitropack' import type { Nitro, NitroConfig } from 'nitropack'
import { logger } from '@nuxt/kit' import { logger, resolveIgnorePatterns } from '@nuxt/kit'
import escapeRE from 'escape-string-regexp' import escapeRE from 'escape-string-regexp'
import { defu } from 'defu' import { defu } from 'defu'
import fsExtra from 'fs-extra' import fsExtra from 'fs-extra'
@ -31,11 +31,10 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
: [/node_modules/] : [/node_modules/]
const spaLoadingTemplate = nuxt.options.spaLoadingTemplate ?? resolve(nuxt.options.srcDir, 'app/spa-loading-template.html') const spaLoadingTemplate = nuxt.options.spaLoadingTemplate ?? resolve(nuxt.options.srcDir, 'app/spa-loading-template.html')
if (spaLoadingTemplate && nuxt.options.spaLoadingTemplate && !existsSync(spaLoadingTemplate)) { if (spaLoadingTemplate && nuxt.options.spaLoadingTemplate && typeof spaLoadingTemplate !== 'boolean' && !existsSync(spaLoadingTemplate)) {
console.warn(`[nuxt] Could not load custom \`spaLoadingTemplate\` path as it does not exist: \`${spaLoadingTemplate}\`.`) console.warn(`[nuxt] Could not load custom \`spaLoadingTemplate\` path as it does not exist: \`${spaLoadingTemplate}\`.`)
} }
// @ts-expect-error `typescriptBundlerResolution` coming in next nitro version
const nitroConfig: NitroConfig = defu(_nitroConfig, { const nitroConfig: NitroConfig = defu(_nitroConfig, {
debug: nuxt.options.debug, debug: nuxt.options.debug,
rootDir: nuxt.options.rootDir, rootDir: nuxt.options.rootDir,
@ -44,9 +43,8 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
dev: nuxt.options.dev, dev: nuxt.options.dev,
buildDir: nuxt.options.buildDir, buildDir: nuxt.options.buildDir,
experimental: { experimental: {
// @ts-expect-error `typescriptBundlerResolution` coming in next nitro version asyncContext: nuxt.options.experimental.asyncContext,
typescriptBundlerResolution: nuxt.options.experimental.typescriptBundlerResolution || nuxt.options.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler' || _nitroConfig.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler', typescriptBundlerResolution: nuxt.options.experimental.typescriptBundlerResolution || nuxt.options.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler' || _nitroConfig.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler'
asyncContext: nuxt.options.experimental.asyncContext
}, },
imports: { imports: {
autoImport: nuxt.options.imports.autoImport as boolean, autoImport: nuxt.options.imports.autoImport as boolean,
@ -91,7 +89,9 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
'#spa-template': () => { '#spa-template': () => {
if (!spaLoadingTemplate) { return 'export const template = ""' } if (!spaLoadingTemplate) { return 'export const template = ""' }
try { try {
return `export const template = ${JSON.stringify(readFileSync(spaLoadingTemplate, 'utf-8'))}` if (spaLoadingTemplate !== true) {
return `export const template = ${JSON.stringify(readFileSync(spaLoadingTemplate, 'utf-8'))}`
}
} catch {} } catch {}
return `export const template = ${JSON.stringify(defaultSpaLoadingTemplate({}))}` return `export const template = ${JSON.stringify(defaultSpaLoadingTemplate({}))}`
} }
@ -117,6 +117,11 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
tsConfig: { tsConfig: {
include: [ include: [
join(nuxt.options.buildDir, 'types/nitro-nuxt.d.ts') join(nuxt.options.buildDir, 'types/nitro-nuxt.d.ts')
],
exclude: [
...nuxt.options.modulesDir.map(m => relativeWithDot(nuxt.options.buildDir, m)),
// nitro generate output: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/core/nitro.ts#L186
relativeWithDot(nuxt.options.buildDir, resolve(nuxt.options.rootDir, 'dist'))
] ]
} }
}, },
@ -204,6 +209,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
// Resolve user-provided paths // Resolve user-provided paths
nitroConfig.srcDir = resolve(nuxt.options.rootDir, nuxt.options.srcDir, nitroConfig.srcDir!) nitroConfig.srcDir = resolve(nuxt.options.rootDir, nuxt.options.srcDir, nitroConfig.srcDir!)
nitroConfig.ignore = [...(nitroConfig.ignore || []), ...resolveIgnorePatterns(nitroConfig.srcDir)]
// Add fallback server for `ssr: false` // Add fallback server for `ssr: false`
if (!nuxt.options.ssr) { if (!nuxt.options.ssr) {
@ -405,3 +411,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
nuxt.hook('build:done', () => waitUntilCompile) nuxt.hook('build:done', () => waitUntilCompile)
} }
} }
function relativeWithDot (from: string, to: string) {
return relative(from, to).replace(/^([^.])/, './$1') || '.'
}

View File

@ -16,6 +16,7 @@ import importsModule from '../imports/module'
import { distDir, pkgDir } from '../dirs' import { distDir, pkgDir } from '../dirs'
import { version } from '../../package.json' import { version } from '../../package.json'
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection' import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
import type { UnctxTransformPluginOptions } from './plugins/unctx'
import { UnctxTransformPlugin } from './plugins/unctx' import { UnctxTransformPlugin } from './plugins/unctx'
import type { TreeShakeComposablesPluginOptions } from './plugins/tree-shake' import type { TreeShakeComposablesPluginOptions } from './plugins/tree-shake'
import { TreeShakeComposablesPlugin } from './plugins/tree-shake' import { TreeShakeComposablesPlugin } from './plugins/tree-shake'
@ -94,14 +95,14 @@ async function initNuxt (nuxt: Nuxt) {
if (nuxt.options.experimental.localLayerAliases) { if (nuxt.options.experimental.localLayerAliases) {
// Add layer aliasing support for ~, ~~, @ and @@ aliases // Add layer aliasing support for ~, ~~, @ and @@ aliases
addVitePlugin(() => LayerAliasingPlugin.vite({ addVitePlugin(() => LayerAliasingPlugin.vite({
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
dev: nuxt.options.dev, dev: nuxt.options.dev,
root: nuxt.options.srcDir, root: nuxt.options.srcDir,
// skip top-level layer (user's project) as the aliases will already be correctly resolved // skip top-level layer (user's project) as the aliases will already be correctly resolved
layers: nuxt.options._layers.slice(1) layers: nuxt.options._layers.slice(1)
})) }))
addWebpackPlugin(() => LayerAliasingPlugin.webpack({ addWebpackPlugin(() => LayerAliasingPlugin.webpack({
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
dev: nuxt.options.dev, dev: nuxt.options.dev,
root: nuxt.options.srcDir, root: nuxt.options.srcDir,
// skip top-level layer (user's project) as the aliases will already be correctly resolved // skip top-level layer (user's project) as the aliases will already be correctly resolved
@ -110,18 +111,21 @@ async function initNuxt (nuxt: Nuxt) {
})) }))
} }
nuxt.hook('modules:done', () => { nuxt.hook('modules:done', async () => {
// Add unctx transform // Add unctx transform
const options = { const options = {
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
transformerOptions: nuxt.options.optimization.asyncTransforms transformerOptions: {
} ...nuxt.options.optimization.asyncTransforms,
helperModule: await tryResolveModule('unctx', nuxt.options.modulesDir) ?? 'unctx'
}
} satisfies UnctxTransformPluginOptions
addVitePlugin(() => UnctxTransformPlugin.vite(options)) addVitePlugin(() => UnctxTransformPlugin.vite(options))
addWebpackPlugin(() => UnctxTransformPlugin.webpack(options)) addWebpackPlugin(() => UnctxTransformPlugin.webpack(options))
// Add composable tree-shaking optimisations // Add composable tree-shaking optimisations
const serverTreeShakeOptions: TreeShakeComposablesPluginOptions = { const serverTreeShakeOptions: TreeShakeComposablesPluginOptions = {
sourcemap: nuxt.options.sourcemap.server, sourcemap: !!nuxt.options.sourcemap.server,
composables: nuxt.options.optimization.treeShake.composables.server composables: nuxt.options.optimization.treeShake.composables.server
} }
if (Object.keys(serverTreeShakeOptions.composables).length) { if (Object.keys(serverTreeShakeOptions.composables).length) {
@ -129,7 +133,7 @@ async function initNuxt (nuxt: Nuxt) {
addWebpackPlugin(() => TreeShakeComposablesPlugin.webpack(serverTreeShakeOptions), { client: false }) addWebpackPlugin(() => TreeShakeComposablesPlugin.webpack(serverTreeShakeOptions), { client: false })
} }
const clientTreeShakeOptions: TreeShakeComposablesPluginOptions = { const clientTreeShakeOptions: TreeShakeComposablesPluginOptions = {
sourcemap: nuxt.options.sourcemap.client, sourcemap: !!nuxt.options.sourcemap.client,
composables: nuxt.options.optimization.treeShake.composables.client composables: nuxt.options.optimization.treeShake.composables.client
} }
if (Object.keys(clientTreeShakeOptions.composables).length) { if (Object.keys(clientTreeShakeOptions.composables).length) {
@ -140,8 +144,8 @@ async function initNuxt (nuxt: Nuxt) {
if (!nuxt.options.dev) { if (!nuxt.options.dev) {
// DevOnly component tree-shaking - build time only // DevOnly component tree-shaking - build time only
addVitePlugin(() => DevOnlyPlugin.vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client })) addVitePlugin(() => DevOnlyPlugin.vite({ sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client }))
addWebpackPlugin(() => DevOnlyPlugin.webpack({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client })) addWebpackPlugin(() => DevOnlyPlugin.webpack({ sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client }))
} }
// Transform initial composable call within `<script setup>` to preserve context // Transform initial composable call within `<script setup>` to preserve context

View File

@ -6,7 +6,7 @@ import { isJS, isVue } from '../utils'
const TRANSFORM_MARKER = '/* _processed_nuxt_unctx_transform */\n' const TRANSFORM_MARKER = '/* _processed_nuxt_unctx_transform */\n'
interface UnctxTransformPluginOptions { export interface UnctxTransformPluginOptions {
sourcemap?: boolean sourcemap?: boolean
transformerOptions: TransformerOptions transformerOptions: TransformerOptions
} }

View File

@ -1,8 +1,9 @@
import { joinURL, withQuery } from 'ufo' import { joinURL, withQuery } from 'ufo'
import type { NitroErrorHandler } from 'nitropack' import type { NitroErrorHandler } from 'nitropack'
import type { H3Error } from 'h3' import type { H3Error } from 'h3'
import { getRequestHeaders, setResponseHeader, setResponseStatus } from 'h3' import { getRequestHeaders, send, setResponseHeader, setResponseStatus } from 'h3'
import { useNitroApp, useRuntimeConfig } from '#internal/nitro' import { useRuntimeConfig } from '#internal/nitro'
import { useNitroApp } from '#internal/nitro/app'
import { isJsonRequest, normalizeError } from '#internal/nitro/utils' import { isJsonRequest, normalizeError } from '#internal/nitro/utils'
export default <NitroErrorHandler> async function errorhandler (error: H3Error, event) { export default <NitroErrorHandler> async function errorhandler (error: H3Error, event) {
@ -11,7 +12,7 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
// Create an error object // Create an error object
const errorObject = { const errorObject = {
url: event.node.req.url, url: event.path,
statusCode, statusCode,
statusMessage, statusMessage,
message, message,
@ -41,12 +42,11 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
// JSON response // JSON response
if (isJsonRequest(event)) { if (isJsonRequest(event)) {
setResponseHeader(event, 'Content-Type', 'application/json') setResponseHeader(event, 'Content-Type', 'application/json')
event.node.res.end(JSON.stringify(errorObject)) return send(event, JSON.stringify(errorObject))
return
} }
// HTML response (via SSR) // HTML response (via SSR)
const isErrorPage = event.node.req.url?.startsWith('/__nuxt_error') const isErrorPage = event.path.startsWith('/__nuxt_error')
const res = !isErrorPage const res = !isErrorPage
? await useNitroApp().localFetch(withQuery(joinURL(useRuntimeConfig().app.baseURL, '/__nuxt_error'), errorObject), { ? await useNitroApp().localFetch(withQuery(joinURL(useRuntimeConfig().app.baseURL, '/__nuxt_error'), errorObject), {
headers: getRequestHeaders(event) as Record<string, string>, headers: getRequestHeaders(event) as Record<string, string>,
@ -67,8 +67,7 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
} }
if (event.handled) { return } if (event.handled) { return }
setResponseHeader(event, 'Content-Type', 'text/html;charset=UTF-8') setResponseHeader(event, 'Content-Type', 'text/html;charset=UTF-8')
event.node.res.end(template(errorObject)) return send(event, template(errorObject))
return
} }
const html = await res.text() const html = await res.text()
@ -79,5 +78,5 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
} }
setResponseStatus(event, res.status && res.status !== 200 ? res.status : undefined, res.statusText) setResponseStatus(event, res.status && res.status !== 200 ? res.status : undefined, res.statusText)
event.node.res.end(html) return send(event, html)
} }

View File

@ -9,7 +9,7 @@ import {
import type { RenderResponse } from 'nitropack' import type { RenderResponse } from 'nitropack'
import type { Manifest } from 'vite' import type { Manifest } from 'vite'
import type { H3Event } from 'h3' import type { H3Event } from 'h3'
import { appendResponseHeader, createError, getQuery, readBody, writeEarlyHints } from 'h3' import { appendResponseHeader, createError, getQuery, getResponseStatus, getResponseStatusText, readBody, writeEarlyHints } from 'h3'
import devalue from '@nuxt/devalue' import devalue from '@nuxt/devalue'
import { stringify, uneval } from 'devalue' import { stringify, uneval } from 'devalue'
import destr from 'destr' import destr from 'destr'
@ -170,16 +170,16 @@ const islandPropCache = import.meta.prerender ? useStorage('internal:nuxt:preren
async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> { async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
// TODO: Strict validation for url // TODO: Strict validation for url
let url = event.node.req.url || '' let url = event.path || ''
if (import.meta.prerender && event.node.req.url && await islandPropCache!.hasItem(event.node.req.url)) { if (import.meta.prerender && event.path && await islandPropCache!.hasItem(event.path)) {
// rehydrate props from cache so we can rerender island if cache does not have it any more // rehydrate props from cache so we can rerender island if cache does not have it any more
url = await islandPropCache!.getItem(event.node.req.url) as string url = await islandPropCache!.getItem(event.path) as string
} }
url = url.substring('/__nuxt_island'.length + 1) || '' url = url.substring('/__nuxt_island'.length + 1) || ''
const [componentName, hashId] = url.split('?')[0].split('_') const [componentName, hashId] = url.split('?')[0].split('_')
// TODO: Validate context // TODO: Validate context
const context = event.node.req.method === 'GET' ? getQuery(event) : await readBody(event) const context = event.method === 'GET' ? getQuery(event) : await readBody(event)
const ctx: NuxtIslandContext = { const ctx: NuxtIslandContext = {
url: '/', url: '/',
@ -202,7 +202,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
const nitroApp = useNitroApp() const nitroApp = useNitroApp()
// Whether we're rendering an error page // Whether we're rendering an error page
const ssrError = event.node.req.url?.startsWith('/__nuxt_error') const ssrError = event.path.startsWith('/__nuxt_error')
? getQuery(event) as unknown as Exclude<NuxtPayload['error'], Error> ? getQuery(event) as unknown as Exclude<NuxtPayload['error'], Error>
: null : null
@ -210,7 +210,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
ssrError.statusCode = parseInt(ssrError.statusCode as any) ssrError.statusCode = parseInt(ssrError.statusCode as any)
} }
if (ssrError && event.node.req.socket.readyState !== 'readOnly' /* direct request */) { if (ssrError && !('__unenv__' in event.node.req) /* allow internal fetch */) {
throw createError({ throw createError({
statusCode: 404, statusCode: 404,
statusMessage: 'Page Not Found: /__nuxt_error' statusMessage: 'Page Not Found: /__nuxt_error'
@ -218,21 +218,23 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
} }
// Check for island component rendering // Check for island component rendering
const islandContext = (process.env.NUXT_COMPONENT_ISLANDS && event.node.req.url?.startsWith('/__nuxt_island')) const islandContext = (process.env.NUXT_COMPONENT_ISLANDS && event.path.startsWith('/__nuxt_island'))
? await getIslandContext(event) ? await getIslandContext(event)
: undefined : undefined
if (import.meta.prerender && islandContext && event.node.req.url && await islandCache!.hasItem(event.node.req.url)) { if (import.meta.prerender && islandContext && event.path && await islandCache!.hasItem(event.path)) {
return islandCache!.getItem(event.node.req.url) as Promise<Partial<RenderResponse>> return islandCache!.getItem(event.path) as Promise<Partial<RenderResponse>>
} }
// Request url // Request url
let url = ssrError?.url as string || islandContext?.url || event.node.req.url! let url = ssrError?.url as string || islandContext?.url || event.path
// Whether we are rendering payload route // Whether we are rendering payload route
const isRenderingPayload = PAYLOAD_URL_RE.test(url) && !islandContext const isRenderingPayload = PAYLOAD_URL_RE.test(url) && !islandContext
if (isRenderingPayload) { if (isRenderingPayload) {
url = url.substring(0, url.lastIndexOf('/')) || '/' url = url.substring(0, url.lastIndexOf('/')) || '/'
event._path = url
event.node.req.url = url event.node.req.url = url
if (import.meta.prerender && await payloadCache!.hasItem(url)) { if (import.meta.prerender && await payloadCache!.hasItem(url)) {
return payloadCache!.getItem(url) as Promise<Partial<RenderResponse>> return payloadCache!.getItem(url) as Promise<Partial<RenderResponse>>
@ -435,8 +437,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
const response = { const response = {
body: JSON.stringify(islandResponse, null, 2), body: JSON.stringify(islandResponse, null, 2),
statusCode: event.node.res.statusCode, statusCode: getResponseStatus(event),
statusMessage: event.node.res.statusMessage, statusMessage: getResponseStatusText(event),
headers: { headers: {
'content-type': 'application/json;charset=utf-8', 'content-type': 'application/json;charset=utf-8',
'x-powered-by': 'Nuxt' 'x-powered-by': 'Nuxt'
@ -444,7 +446,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
} satisfies RenderResponse } satisfies RenderResponse
if (import.meta.prerender) { if (import.meta.prerender) {
await islandCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, response) await islandCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, response)
await islandPropCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, event.node.req.url!) await islandPropCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, event.path)
} }
return response return response
} }
@ -452,8 +454,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
// Construct HTML response // Construct HTML response
const response = { const response = {
body: renderHTMLDocument(htmlContext), body: renderHTMLDocument(htmlContext),
statusCode: event.node.res.statusCode, statusCode: getResponseStatus(event),
statusMessage: event.node.res.statusMessage, statusMessage: getResponseStatusText(event),
headers: { headers: {
'content-type': 'text/html;charset=utf-8', 'content-type': 'text/html;charset=utf-8',
'x-powered-by': 'Nuxt' 'x-powered-by': 'Nuxt'
@ -511,8 +513,8 @@ function renderPayloadResponse (ssrContext: NuxtSSRContext) {
body: process.env.NUXT_JSON_PAYLOADS body: process.env.NUXT_JSON_PAYLOADS
? stringify(splitPayload(ssrContext).payload, ssrContext._payloadReducers) ? stringify(splitPayload(ssrContext).payload, ssrContext._payloadReducers)
: `export default ${devalue(splitPayload(ssrContext).payload)}`, : `export default ${devalue(splitPayload(ssrContext).payload)}`,
statusCode: ssrContext.event.node.res.statusCode, statusCode: getResponseStatus(ssrContext.event),
statusMessage: ssrContext.event.node.res.statusMessage, statusMessage: getResponseStatusText(ssrContext.event),
headers: { headers: {
'content-type': process.env.NUXT_JSON_PAYLOADS ? 'application/json;charset=utf-8' : 'text/javascript;charset=utf-8', 'content-type': process.env.NUXT_JSON_PAYLOADS ? 'application/json;charset=utf-8' : 'text/javascript;charset=utf-8',
'x-powered-by': 'Nuxt' 'x-powered-by': 'Nuxt'

View File

@ -1,4 +1,4 @@
import { addTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, resolveAlias, tryResolveModule, updateTemplates, useNuxt } from '@nuxt/kit' import { addTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, isIgnored, resolveAlias, tryResolveModule, updateTemplates, useNuxt } from '@nuxt/kit'
import { isAbsolute, join, normalize, relative, resolve } from 'pathe' import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
import type { Import, Unimport } from 'unimport' import type { Import, Unimport } from 'unimport'
import { createUnimport, scanDirExports } from 'unimport' import { createUnimport, scanDirExports } from 'unimport'
@ -82,8 +82,8 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
nuxt.options.alias['#imports'] = join(nuxt.options.buildDir, 'imports') nuxt.options.alias['#imports'] = join(nuxt.options.buildDir, 'imports')
// Transform to inject imports in production mode // Transform to inject imports in production mode
addVitePlugin(() => TransformPlugin.vite({ ctx, options, sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client })) addVitePlugin(() => TransformPlugin.vite({ ctx, options, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client }))
addWebpackPlugin(() => TransformPlugin.webpack({ ctx, options, sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client })) addWebpackPlugin(() => TransformPlugin.webpack({ ctx, options, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client }))
const priorities = nuxt.options._layers.map((layer, i) => [layer.config.srcDir, -i] as const).sort(([a], [b]) => b.length - a.length) const priorities = nuxt.options._layers.map((layer, i) => [layer.config.srcDir, -i] as const).sort(([a], [b]) => b.length - a.length)
@ -92,7 +92,9 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
// Clear old imports // Clear old imports
imports.length = 0 imports.length = 0
// Scan `composables/` // Scan `composables/`
const composableImports = await scanDirExports(composablesDirs) const composableImports = await scanDirExports(composablesDirs, {
fileFilter: file => !isIgnored(file)
})
for (const i of composableImports) { for (const i of composableImports) {
i.priority = i.priority || priorities.find(([dir]) => i.from.startsWith(dir))?.[1] i.priority = i.priority || priorities.find(([dir]) => i.from.startsWith(dir))?.[1]
} }

View File

@ -9,11 +9,14 @@ import { createRoutesContext } from 'unplugin-vue-router'
import { resolveOptions } from 'unplugin-vue-router/options' import { resolveOptions } from 'unplugin-vue-router/options'
import type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-vue-router' import type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-vue-router'
import type { NitroRouteConfig } from 'nitropack'
import { defu } from 'defu'
import { distDir } from '../dirs' import { distDir } from '../dirs'
import { normalizeRoutes, resolvePagesRoutes } from './utils' import { normalizeRoutes, resolvePagesRoutes } from './utils'
import type { PageMetaPluginOptions } from './page-meta' import { extractRouteRules, getMappedPages } from './route-rules'
import { PageMetaPlugin } from './page-meta' import type { PageMetaPluginOptions } from './plugins/page-meta'
import { RouteInjectionPlugin } from './route-injection' import { PageMetaPlugin } from './plugins/page-meta'
import { RouteInjectionPlugin } from './plugins/route-injection'
const OPTIONAL_PARAM_RE = /^\/?:.*(\?|\(\.\*\)\*)$/ const OPTIONAL_PARAM_RE = /^\/?:.*(\?|\(\.\*\)\*)$/
@ -239,12 +242,73 @@ export default defineNuxtModule({
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') }, { name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },
{ name: 'useLink', as: 'useLink', from: '#vue-router' } { name: 'useLink', as: 'useLink', from: '#vue-router' }
) )
if (nuxt.options.experimental.inlineRouteRules) {
imports.push({ name: 'defineRouteRules', as: 'defineRouteRules', from: resolve(runtimeDir, 'composables') })
}
}) })
if (nuxt.options.experimental.inlineRouteRules) {
// Track mappings of absolute files to globs
let pageToGlobMap = {} as { [absolutePath: string]: string | null }
nuxt.hook('pages:extend', (pages) => { pageToGlobMap = getMappedPages(pages) })
// Extracted route rules defined inline in pages
const inlineRules = {} as { [glob: string]: NitroRouteConfig }
// Allow telling Nitro to reload route rules
let updateRouteConfig: () => void | Promise<void>
nuxt.hook('nitro:init', (nitro) => {
updateRouteConfig = () => nitro.updateConfig({ routeRules: defu(inlineRules, nitro.options._config.routeRules) })
})
async function updatePage (path: string) {
const glob = pageToGlobMap[path]
const code = path in nuxt.vfs ? nuxt.vfs[path] : await readFile(path!, 'utf-8')
try {
const extractedRule = await extractRouteRules(code)
if (extractedRule) {
if (!glob) {
const relativePath = relative(nuxt.options.srcDir, path)
console.error(`[nuxt] Could not set inline route rules in \`~/${relativePath}\` as it could not be mapped to a Nitro route.`)
return
}
inlineRules[glob] = extractedRule
} else if (glob) {
delete inlineRules[glob]
}
} catch (e: any) {
if (e.toString().includes('Error parsing route rules')) {
const relativePath = relative(nuxt.options.srcDir, path)
console.error(`[nuxt] Error parsing route rules within \`~/${relativePath}\`. They should be JSON-serializable.`)
} else {
console.error(e)
}
}
}
nuxt.hook('builder:watch', async (event, relativePath) => {
const path = join(nuxt.options.srcDir, relativePath)
if (!(path in pageToGlobMap)) { return }
if (event === 'unlink') {
delete inlineRules[path]
delete pageToGlobMap[path]
} else {
await updatePage(path)
}
await updateRouteConfig?.()
})
nuxt.hooks.hookOnce('pages:extend', async () => {
for (const page in pageToGlobMap) { await updatePage(page) }
await updateRouteConfig?.()
})
}
// Extract macros from pages // Extract macros from pages
const pageMetaOptions: PageMetaPluginOptions = { const pageMetaOptions: PageMetaPluginOptions = {
dev: nuxt.options.dev, dev: nuxt.options.dev,
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client
} }
nuxt.hook('modules:done', () => { nuxt.hook('modules:done', () => {
addVitePlugin(() => PageMetaPlugin.vite(pageMetaOptions)) addVitePlugin(() => PageMetaPlugin.vite(pageMetaOptions))

View File

@ -1,7 +1,7 @@
import { createUnplugin } from 'unplugin' import { createUnplugin } from 'unplugin'
import MagicString from 'magic-string' import MagicString from 'magic-string'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import { isVue } from '../core/utils' import { isVue } from '../../core/utils'
const INJECTION_RE = /\b_ctx\.\$route\b/g const INJECTION_RE = /\b_ctx\.\$route\b/g
const INJECTION_SINGLE_RE = /\b_ctx\.\$route\b/ const INJECTION_SINGLE_RE = /\b_ctx\.\$route\b/

View File

@ -0,0 +1,60 @@
import { runInNewContext } from 'node:vm'
import type { Node } from 'estree-walker'
import type { CallExpression } from 'estree'
import { walk } from 'estree-walker'
import { transform } from 'esbuild'
import { parse } from 'acorn'
import type { NuxtPage } from '@nuxt/schema'
import type { NitroRouteConfig } from 'nitropack'
import { normalize } from 'pathe'
import { extractScriptContent, pathToNitroGlob } from './utils'
const ROUTE_RULE_RE = /\bdefineRouteRules\(/
const ruleCache: Record<string, NitroRouteConfig | null> = {}
export async function extractRouteRules (code: string): Promise<NitroRouteConfig | null> {
if (code in ruleCache) {
return ruleCache[code]
}
if (!ROUTE_RULE_RE.test(code)) { return null }
code = extractScriptContent(code) || code
let rule: NitroRouteConfig | null = null
const js = await transform(code, { loader: 'ts' })
walk(parse(js.code, {
sourceType: 'module',
ecmaVersion: 'latest'
}) as Node, {
enter (_node) {
if (_node.type !== 'CallExpression' || (_node as CallExpression).callee.type !== 'Identifier') { return }
const node = _node as CallExpression & { start: number, end: number }
const name = 'name' in node.callee && node.callee.name
if (name === 'defineRouteRules') {
const rulesString = js.code.slice(node.start, node.end)
try {
rule = JSON.parse(runInNewContext(rulesString.replace('defineRouteRules', 'JSON.stringify'), {}))
} catch {
throw new Error('[nuxt] Error parsing route rules. They should be JSON-serializable.')
}
}
}
})
ruleCache[code] = rule
return rule
}
export function getMappedPages (pages: NuxtPage[], paths = {} as { [absolutePath: string]: string | null }, prefix = '') {
for (const page of pages) {
if (page.file) {
const filename = normalize(page.file)
paths[filename] = pathToNitroGlob(prefix + page.path)
}
if (page.children) {
getMappedPages(page.children, paths, page.path + '/')
}
}
return paths
}

View File

@ -2,6 +2,7 @@ import type { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
import { getCurrentInstance } from 'vue' import { getCurrentInstance } from 'vue'
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from '#vue-router' import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from '#vue-router'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import type { NitroRouteConfig } from 'nitropack'
import type { NuxtError } from '#app' import type { NuxtError } from '#app'
export interface PageMeta { export interface PageMeta {
@ -64,3 +65,15 @@ export const definePageMeta = (meta: PageMeta): void => {
warnRuntimeUsage('definePageMeta') warnRuntimeUsage('definePageMeta')
} }
} }
/**
* You can define route rules for the current page. Matching route rules will be created, based on the page's _path_.
*
* For example, a rule defined in `~/pages/foo/bar.vue` will be applied to `/foo/bar` requests. A rule in
* `~/pages/foo/[id].vue` will be applied to `/foo/**` requests.
*
* For more control, such as if you are using a custom `path` or `alias` set in the page's `definePageMeta`, you
* should set `routeRules` directly within your `nuxt.config`.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const defineRouteRules = (rules: NitroRouteConfig): void => {}

View File

@ -107,7 +107,7 @@ export async function generateRoutesFromFiles (files: string[], pagesDir: string
} }
const SFC_SCRIPT_RE = /<script\s*[^>]*>([\s\S]*?)<\/script\s*[^>]*>/i const SFC_SCRIPT_RE = /<script\s*[^>]*>([\s\S]*?)<\/script\s*[^>]*>/i
function extractScriptContent (html: string) { export function extractScriptContent (html: string) {
const match = html.match(SFC_SCRIPT_RE) const match = html.match(SFC_SCRIPT_RE)
if (match && match[1]) { if (match && match[1]) {
@ -335,3 +335,15 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
})) }))
} }
} }
export function pathToNitroGlob (path: string) {
if (!path) {
return null
}
// Ignore pages with multiple dynamic parameters.
if (path.indexOf(':') !== path.lastIndexOf(':')) {
return null
}
return path.replace(/\/(?:[^:/]+)?:\w+.*$/, '/**')
}

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import type { NuxtPage } from 'nuxt/schema' import type { NuxtPage } from 'nuxt/schema'
import { generateRoutesFromFiles } from '../src/pages/utils' import { generateRoutesFromFiles, pathToNitroGlob } from '../src/pages/utils'
import { generateRouteKey } from '../src/pages/runtime/utils' import { generateRouteKey } from '../src/pages/runtime/utils'
describe('pages:generateRoutesFromFiles', () => { describe('pages:generateRoutesFromFiles', () => {
@ -442,3 +442,20 @@ describe('pages:generateRouteKey', () => {
}) })
} }
}) })
const pathToNitroGlobTests = {
'/': '/',
'/:id': '/**',
'/:id()': '/**',
'/:id?': '/**',
'/some-:id?': '/**',
'/other/some-:id?': '/other/**',
'/other/some-:id()-more': '/other/**',
'/other/nested': '/other/nested'
}
describe('pages:pathToNitroGlob', () => {
it.each(Object.entries(pathToNitroGlobTests))('should convert %s to %s', (path, expected) => {
expect(pathToNitroGlob(path)).to.equal(expected)
})
})

View File

@ -5,6 +5,7 @@
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"sideEffects": false,
"exports": { "exports": {
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@ -30,14 +31,14 @@
"@types/file-loader": "5.0.1", "@types/file-loader": "5.0.1",
"@types/pug": "2.0.6", "@types/pug": "2.0.6",
"@types/sass-loader": "8.0.5", "@types/sass-loader": "8.0.5",
"@unhead/schema": "1.3.5", "@unhead/schema": "1.3.7",
"@vitejs/plugin-vue": "4.3.1", "@vitejs/plugin-vue": "4.3.3",
"@vitejs/plugin-vue-jsx": "3.0.2", "@vitejs/plugin-vue-jsx": "3.0.2",
"@vue/compiler-core": "3.3.4", "@vue/compiler-core": "3.3.4",
"esbuild-loader": "4.0.1", "esbuild-loader": "4.0.1",
"h3": "1.8.0", "h3": "1.8.0",
"ignore": "5.2.4", "ignore": "5.2.4",
"nitropack": "2.5.2", "nitropack": "2.6.0",
"unbuild": "latest", "unbuild": "latest",
"unctx": "2.3.1", "unctx": "2.3.1",
"vite": "4.4.9", "vite": "4.4.9",
@ -55,9 +56,9 @@
"pathe": "^1.1.1", "pathe": "^1.1.1",
"pkg-types": "^1.0.3", "pkg-types": "^1.0.3",
"postcss-import-resolver": "^2.0.0", "postcss-import-resolver": "^2.0.0",
"std-env": "^3.4.0", "std-env": "^3.4.3",
"ufo": "^1.2.0", "ufo": "^1.3.0",
"unimport": "^3.1.3", "unimport": "^3.2.0",
"untyped": "^1.4.0" "untyped": "^1.4.0"
}, },
"engines": { "engines": {

View File

@ -241,10 +241,10 @@ export default defineUntypedSchema({
* } * }
* </style> * </style>
* ``` * ```
* @type {string | false} * @type {string | boolean}
*/ */
spaLoadingTemplate: { spaLoadingTemplate: {
$resolve: async (val, get) => typeof val === 'string' ? resolve(await get('srcDir'), val) : (val ?? null) $resolve: async (val, get) => typeof val === 'string' ? resolve(await get('srcDir'), val) : (val ?? false)
}, },
/** /**

View File

@ -25,7 +25,7 @@ export default defineUntypedSchema({
/** /**
* Whether to generate sourcemaps. * Whether to generate sourcemaps.
* *
* @type {boolean | { server?: boolean, client?: boolean }} * @type {boolean | { server?: boolean | 'hidden', client?: boolean | 'hidden' }}
*/ */
sourcemap: { sourcemap: {
$resolve: async (val, get) => { $resolve: async (val, get) => {

View File

@ -238,6 +238,18 @@ export default defineUntypedSchema({
* *
* @see https://github.com/nuxt/nuxt/discussions/22632 * @see https://github.com/nuxt/nuxt/discussions/22632
*/ */
headNext: false headNext: false,
/**
* Allow defining `routeRules` directly within your `~/pages` directory using `defineRouteRules`.
*
* Rules are converted (based on the path) and applied for server requests. For example, a rule
* defined in `~/pages/foo/bar.vue` will be applied to `/foo/bar` requests. A rule in `~/pages/foo/[id].vue`
* will be applied to `/foo/**` requests.
*
* For more control, such as if you are using a custom `path` or `alias` set in the page's `definePageMeta`, you
* should set `routeRules` directly within your `nuxt.config`.
*/
inlineRouteRules: false
} }
}) })

View File

@ -59,6 +59,7 @@ export interface Nuxt {
// Private fields. // Private fields.
_version: string _version: string
_ignore?: Ignore _ignore?: Ignore
_ignorePatterns?: string[]
/** The resolved Nuxt configuration. */ /** The resolved Nuxt configuration. */
options: NuxtOptions options: NuxtOptions

View File

@ -28,12 +28,12 @@
"defu": "^6.1.2", "defu": "^6.1.2",
"execa": "^7.2.0", "execa": "^7.2.0",
"get-port-please": "^3.0.1", "get-port-please": "^3.0.1",
"ofetch": "^1.1.1", "ofetch": "^1.3.3",
"pathe": "^1.1.1", "pathe": "^1.1.1",
"ufo": "^1.2.0" "ufo": "^1.3.0"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "29.6.2", "@jest/globals": "29.6.4",
"playwright-core": "1.37.1", "playwright-core": "1.37.1",
"unbuild": "latest", "unbuild": "latest",
"vitest": "0.33.0" "vitest": "0.33.0"

View File

@ -17,11 +17,12 @@ export async function startServer () {
ctx.url = 'http://127.0.0.1:' + port ctx.url = 'http://127.0.0.1:' + port
if (ctx.options.dev) { if (ctx.options.dev) {
const nuxiCLI = await kit.resolvePath('nuxi/cli') const nuxiCLI = await kit.resolvePath('nuxi/cli')
ctx.serverProcess = execa(nuxiCLI, ['dev'], { ctx.serverProcess = execa(nuxiCLI, ['_dev'], {
cwd: ctx.nuxt!.options.rootDir, cwd: ctx.nuxt!.options.rootDir,
stdio: 'inherit', stdio: 'inherit',
env: { env: {
...process.env, ...process.env,
_PORT: String(port),
PORT: String(port), PORT: String(port),
NITRO_PORT: String(port), NITRO_PORT: String(port),
NODE_ENV: 'development' NODE_ENV: 'development'
@ -29,7 +30,7 @@ export async function startServer () {
}) })
await waitForPort(port, { retries: 32 }) await waitForPort(port, { retries: 32 })
let lastError let lastError
for (let i = 0; i < 50; i++) { for (let i = 0; i < 150; i++) {
await new Promise(resolve => setTimeout(resolve, 100)) await new Promise(resolve => setTimeout(resolve, 100))
try { try {
const res = await $fetch(ctx.nuxt!.options.app.baseURL) const res = await $fetch(ctx.nuxt!.options.app.baseURL)

View File

@ -28,7 +28,7 @@
"dependencies": { "dependencies": {
"@nuxt/kit": "workspace:../kit", "@nuxt/kit": "workspace:../kit",
"@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-replace": "^5.0.2",
"@vitejs/plugin-vue": "^4.3.1", "@vitejs/plugin-vue": "^4.3.3",
"@vitejs/plugin-vue-jsx": "^3.0.2", "@vitejs/plugin-vue-jsx": "^3.0.2",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"clear": "^0.1.0", "clear": "^0.1.0",
@ -43,7 +43,7 @@
"get-port-please": "^3.0.1", "get-port-please": "^3.0.1",
"h3": "^1.8.0", "h3": "^1.8.0",
"knitwork": "^1.0.0", "knitwork": "^1.0.0",
"magic-string": "^0.30.2", "magic-string": "^0.30.3",
"mlly": "^1.4.0", "mlly": "^1.4.0",
"ohash": "^1.1.3", "ohash": "^1.1.3",
"pathe": "^1.1.1", "pathe": "^1.1.1",
@ -53,9 +53,9 @@
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"rollup-plugin-visualizer": "^5.9.2", "rollup-plugin-visualizer": "^5.9.2",
"std-env": "^3.4.0", "std-env": "^3.4.3",
"strip-literal": "^1.3.0", "strip-literal": "^1.3.0",
"ufo": "^1.2.0", "ufo": "^1.3.0",
"unplugin": "^1.4.0", "unplugin": "^1.4.0",
"vite": "^4.4.9", "vite": "^4.4.9",
"vite-node": "^0.33.0", "vite-node": "^0.33.0",

View File

@ -35,7 +35,7 @@ export async function buildClient (ctx: ViteBuildContext) {
} }
}, },
css: { css: {
devSourcemap: ctx.nuxt.options.sourcemap.client devSourcemap: !!ctx.nuxt.options.sourcemap.client
}, },
define: { define: {
'process.env.NODE_ENV': JSON.stringify(ctx.config.mode), 'process.env.NODE_ENV': JSON.stringify(ctx.config.mode),
@ -78,12 +78,12 @@ export async function buildClient (ctx: ViteBuildContext) {
buildAssetsURL: joinURL(ctx.nuxt.options.app.baseURL, ctx.nuxt.options.app.buildAssetsDir) buildAssetsURL: joinURL(ctx.nuxt.options.app.baseURL, ctx.nuxt.options.app.buildAssetsDir)
}), }),
runtimePathsPlugin({ runtimePathsPlugin({
sourcemap: ctx.nuxt.options.sourcemap.client sourcemap: !!ctx.nuxt.options.sourcemap.client
}), }),
viteNodePlugin(ctx), viteNodePlugin(ctx),
pureAnnotationsPlugin.vite({ pureAnnotationsPlugin.vite({
sourcemap: ctx.nuxt.options.sourcemap.client, sourcemap: !!ctx.nuxt.options.sourcemap.client,
functions: ['defineComponent', 'defineAsyncComponent', 'defineNuxtLink', 'createClientOnly', 'defineNuxtPlugin', 'defineNuxtRouteMiddleware', 'defineNuxtComponent', 'useRuntimeConfig'] functions: ['defineComponent', 'defineAsyncComponent', 'defineNuxtLink', 'createClientOnly', 'defineNuxtPlugin', 'defineNuxtRouteMiddleware', 'defineNuxtComponent', 'useRuntimeConfig', 'defineRouteRules']
}) })
], ],
appType: 'custom', appType: 'custom',
@ -102,7 +102,7 @@ export async function buildClient (ctx: ViteBuildContext) {
// Emit chunk errors if the user has opted in to `experimental.emitRouteChunkError` // Emit chunk errors if the user has opted in to `experimental.emitRouteChunkError`
if (ctx.nuxt.options.experimental.emitRouteChunkError) { if (ctx.nuxt.options.experimental.emitRouteChunkError) {
clientConfig.plugins!.push(chunkErrorPlugin({ sourcemap: ctx.nuxt.options.sourcemap.client })) clientConfig.plugins!.push(chunkErrorPlugin({ sourcemap: !!ctx.nuxt.options.sourcemap.client }))
} }
// We want to respect users' own rollup output options // We want to respect users' own rollup output options
@ -135,7 +135,7 @@ export async function buildClient (ctx: ViteBuildContext) {
// Add type checking client panel // Add type checking client panel
if (ctx.nuxt.options.typescript.typeCheck && ctx.nuxt.options.dev) { if (ctx.nuxt.options.typescript.typeCheck && ctx.nuxt.options.dev) {
clientConfig.plugins!.push(typeCheckPlugin({ sourcemap: ctx.nuxt.options.sourcemap.client })) clientConfig.plugins!.push(typeCheckPlugin({ sourcemap: !!ctx.nuxt.options.sourcemap.client }))
} }
await ctx.nuxt.callHook('vite:extendConfig', clientConfig, { isClient: true, isServer: false }) await ctx.nuxt.callHook('vite:extendConfig', clientConfig, { isClient: true, isServer: false })
@ -163,18 +163,17 @@ export async function buildClient (ctx: ViteBuildContext) {
}) })
const viteMiddleware = defineEventHandler(async (event) => { const viteMiddleware = defineEventHandler(async (event) => {
// Workaround: vite devmiddleware modifies req.url
const originalURL = event.node.req.url!
const viteRoutes = viteServer.middlewares.stack.map(m => m.route).filter(r => r.length > 1) const viteRoutes = viteServer.middlewares.stack.map(m => m.route).filter(r => r.length > 1)
if (!originalURL.startsWith(clientConfig.base!) && !viteRoutes.some(route => originalURL.startsWith(route))) { if (!event.path.startsWith(clientConfig.base!) && !viteRoutes.some(route => event.path.startsWith(route))) {
// @ts-expect-error _skip_transform is a private property // @ts-expect-error _skip_transform is a private property
event.node.req._skip_transform = true event.node.req._skip_transform = true
} }
// Workaround: vite devmiddleware modifies req.url
const _originalPath = event.node.req.url
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
viteServer.middlewares.handle(event.node.req, event.node.res, (err: Error) => { viteServer.middlewares.handle(event.node.req, event.node.res, (err: Error) => {
event.node.req.url = originalURL event.node.req.url = _originalPath
return err ? reject(err) : resolve(null) return err ? reject(err) : resolve(null)
}) })
}) })

View File

@ -75,11 +75,13 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
source: '' source: ''
}) })
const baseDir = dirname(base)
emitted[file] = this.emitFile({ emitted[file] = this.emitFile({
type: 'asset', type: 'asset',
name: `${filename(file)}-styles.mjs`, name: `${filename(file)}-styles.mjs`,
source: [ source: [
...files.map((css, i) => `import style_${i} from './${relative(dirname(base), this.getFileName(css))}';`), ...files.map((css, i) => `import style_${i} from './${relative(baseDir, this.getFileName(css))}';`),
`export default [${files.map((_, i) => `style_${i}`).join(', ')}]` `export default [${files.map((_, i) => `style_${i}`).join(', ')}]`
].join('\n') ].join('\n')
}) })

View File

@ -15,7 +15,6 @@ import { transpile } from './utils/transpile'
export async function buildServer (ctx: ViteBuildContext) { export async function buildServer (ctx: ViteBuildContext) {
const helper = ctx.nuxt.options.nitro.imports !== false ? '' : 'globalThis.' const helper = ctx.nuxt.options.nitro.imports !== false ? '' : 'globalThis.'
const entry = ctx.nuxt.options.ssr ? ctx.entry : await resolvePath(resolve(ctx.nuxt.options.appDir, 'entry-spa')) const entry = ctx.nuxt.options.ssr ? ctx.entry : await resolvePath(resolve(ctx.nuxt.options.appDir, 'entry-spa'))
const nitroDependencies = await tryResolveModule('nitropack/package.json', ctx.nuxt.options.modulesDir).then(r => import(r!)).then(r => Object.keys(r.dependencies || {})).catch(() => [])
const serverConfig: ViteConfig = vite.mergeConfig(ctx.config, vite.mergeConfig({ const serverConfig: ViteConfig = vite.mergeConfig(ctx.config, vite.mergeConfig({
configFile: false, configFile: false,
base: ctx.nuxt.options.dev base: ctx.nuxt.options.dev
@ -37,7 +36,7 @@ export async function buildServer (ctx: ViteBuildContext) {
} }
}, },
css: { css: {
devSourcemap: ctx.nuxt.options.sourcemap.server devSourcemap: !!ctx.nuxt.options.sourcemap.server
}, },
define: { define: {
'process.server': true, 'process.server': true,
@ -62,11 +61,7 @@ export async function buildServer (ctx: ViteBuildContext) {
}, },
ssr: { ssr: {
external: [ external: [
'#internal/nitro', '#internal/nitro/utils', '#internal/nitro', '#internal/nitro/utils'
// explicit dependencies we use in our ssr renderer - these can be inlined (if necessary) in the nitro build
'unhead', '@unhead/ssr', '@unhead/vue', 'unctx', 'h3', 'devalue', '@nuxt/devalue', 'radix3', 'unstorage', 'hookable',
// dependencies we might share with nitro - these can be inlined (if necessary) in the nitro build
...nitroDependencies
], ],
noExternal: [ noExternal: [
...transpile({ isServer: true, isDev: ctx.nuxt.options.dev }), ...transpile({ isServer: true, isDev: ctx.nuxt.options.dev }),
@ -106,12 +101,23 @@ export async function buildServer (ctx: ViteBuildContext) {
}, },
plugins: [ plugins: [
pureAnnotationsPlugin.vite({ pureAnnotationsPlugin.vite({
sourcemap: ctx.nuxt.options.sourcemap.server, sourcemap: !!ctx.nuxt.options.sourcemap.server,
functions: ['defineComponent', 'defineAsyncComponent', 'defineNuxtLink', 'createClientOnly', 'defineNuxtPlugin', 'defineNuxtRouteMiddleware', 'defineNuxtComponent', 'useRuntimeConfig'] functions: ['defineComponent', 'defineAsyncComponent', 'defineNuxtLink', 'createClientOnly', 'defineNuxtPlugin', 'defineNuxtRouteMiddleware', 'defineNuxtComponent', 'useRuntimeConfig', 'defineRouteRules']
}) })
] ]
} satisfies vite.InlineConfig, ctx.nuxt.options.vite.$server || {})) } satisfies vite.InlineConfig, ctx.nuxt.options.vite.$server || {}))
if (!ctx.nuxt.options.dev) {
const nitroDependencies = await tryResolveModule('nitropack/package.json', ctx.nuxt.options.modulesDir)
.then(r => import(r!)).then(r => Object.keys(r.dependencies || {})).catch(() => [])
serverConfig.ssr!.external!.push(
// explicit dependencies we use in our ssr renderer - these can be inlined (if necessary) in the nitro build
'unhead', '@unhead/ssr', 'unctx', 'h3', 'devalue', '@nuxt/devalue', 'radix3', 'unstorage', 'hookable',
// dependencies we might share with nitro - these can be inlined (if necessary) in the nitro build
...nitroDependencies
)
}
serverConfig.customLogger = createViteLogger(serverConfig) serverConfig.customLogger = createViteLogger(serverConfig)
await ctx.nuxt.callHook('vite:extendConfig', serverConfig, { isClient: false, isServer: true }) await ctx.nuxt.callHook('vite:extendConfig', serverConfig, { isClient: false, isServer: true })

View File

@ -141,7 +141,7 @@ function createViteNodeApp (ctx: ViteBuildContext, invalidates: Set<string> = ne
} }
return eventHandler(async (event) => { return eventHandler(async (event) => {
const moduleId = decodeURI(event.node.req.url!).substring(1) const moduleId = decodeURI(event.path).substring(1)
if (moduleId === '/') { if (moduleId === '/') {
throw createError({ statusCode: 400 }) throw createError({ statusCode: 400 })
} }

View File

@ -99,7 +99,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
}, },
plugins: [ plugins: [
composableKeysPlugin.vite({ composableKeysPlugin.vite({
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
rootDir: nuxt.options.rootDir, rootDir: nuxt.options.rootDir,
composables: nuxt.options.optimization.keyedComposables composables: nuxt.options.optimization.keyedComposables
}), }),

View File

@ -35,7 +35,7 @@
"h3": "^1.8.0", "h3": "^1.8.0",
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"magic-string": "^0.30.2", "magic-string": "^0.30.3",
"memfs": "^4.2.1", "memfs": "^4.2.1",
"mini-css-extract-plugin": "^2.7.6", "mini-css-extract-plugin": "^2.7.6",
"mlly": "^1.4.0", "mlly": "^1.4.0",
@ -47,9 +47,9 @@
"postcss-loader": "^7.3.3", "postcss-loader": "^7.3.3",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"pug-plain-loader": "^1.1.0", "pug-plain-loader": "^1.1.0",
"std-env": "^3.4.0", "std-env": "^3.4.3",
"time-fix-plugin": "^2.0.7", "time-fix-plugin": "^2.0.7",
"ufo": "^1.2.0", "ufo": "^1.3.0",
"unplugin": "^1.4.0", "unplugin": "^1.4.0",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-bundle-renderer": "^2.0.0", "vue-bundle-renderer": "^2.0.0",

View File

@ -30,12 +30,14 @@ function clientDevtool (ctx: WebpackConfigContext) {
return return
} }
const prefix = ctx.nuxt.options.sourcemap.client === 'hidden' ? 'hidden-' : ''
if (!ctx.isDev) { if (!ctx.isDev) {
ctx.config.devtool = 'source-map' ctx.config.devtool = prefix + 'source-map'
return return
} }
ctx.config.devtool = 'eval-cheap-module-source-map' ctx.config.devtool = prefix + 'eval-cheap-module-source-map'
} }
function clientPerformance (ctx: WebpackConfigContext) { function clientPerformance (ctx: WebpackConfigContext) {

View File

@ -27,7 +27,12 @@ export function server (ctx: WebpackConfigContext) {
function serverPreset (ctx: WebpackConfigContext) { function serverPreset (ctx: WebpackConfigContext) {
ctx.config.output!.filename = 'server.mjs' ctx.config.output!.filename = 'server.mjs'
ctx.config.devtool = ctx.nuxt.options.sourcemap.server ? ctx.isDev ? 'cheap-module-source-map' : 'source-map' : false if (ctx.nuxt.options.sourcemap.server) {
const prefix = ctx.nuxt.options.sourcemap.server === 'hidden' ? 'hidden-' : ''
ctx.config.devtool = prefix + ctx.isDev ? 'cheap-module-source-map' : 'source-map'
} else {
ctx.config.devtool = false
}
ctx.config.optimization = { ctx.config.optimization = {
splitChunks: false, splitChunks: false,

View File

@ -39,14 +39,14 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
for (const config of webpackConfigs) { for (const config of webpackConfigs) {
config.plugins!.push(DynamicBasePlugin.webpack({ config.plugins!.push(DynamicBasePlugin.webpack({
sourcemap: nuxt.options.sourcemap[config.name as 'client' | 'server'] sourcemap: !!nuxt.options.sourcemap[config.name as 'client' | 'server']
})) }))
// Emit chunk errors if the user has opted in to `experimental.emitRouteChunkError` // Emit chunk errors if the user has opted in to `experimental.emitRouteChunkError`
if (config.name === 'client' && nuxt.options.experimental.emitRouteChunkError) { if (config.name === 'client' && nuxt.options.experimental.emitRouteChunkError) {
config.plugins!.push(new ChunkErrorPlugin()) config.plugins!.push(new ChunkErrorPlugin())
} }
config.plugins!.push(composableKeysPlugin.webpack({ config.plugins!.push(composableKeysPlugin.webpack({
sourcemap: nuxt.options.sourcemap[config.name as 'client' | 'server'], sourcemap: !!nuxt.options.sourcemap[config.name as 'client' | 'server'],
rootDir: nuxt.options.rootDir, rootDir: nuxt.options.rootDir,
composables: nuxt.options.optimization.keyedComposables composables: nuxt.options.optimization.keyedComposables
})) }))

Some files were not shown because too many files have changed in this diff Show More