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
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
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
- name: Check workflow files
run: |

View File

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

View File

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

View File

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

View File

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

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

@ -7,4 +7,4 @@ head.title: "node_modules/"
# 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).
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:

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
@ -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

View File

@ -12,24 +12,24 @@ Within your pages, components, and plugins you can use useAsyncData to get acces
## Type
```ts [Signature]
function useAsyncData(
function useAsyncData<DataT, DataE>(
handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
options?: AsyncDataOptions<DataT>
): AsyncData<DataT>
function useAsyncData(
): AsyncData<DataT, DataE>
function useAsyncData<DataT, DataE>(
key: string,
handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
options?: AsyncDataOptions<DataT>
): Promise<AsyncData<DataT>>
): Promise<AsyncData<DataT, DataE>
type AsyncDataOptions<DataT> = {
server?: boolean
lazy?: boolean
immediate?: boolean
default?: () => DataT | Ref<DataT> | null
transform?: (input: DataT) => DataT
pick?: string[]
watch?: WatchSource[]
immediate?: boolean
}
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.
* **handler**: an asynchronous function that returns a value
* **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`)
* _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
* _pick_: only pick specified keys in this array from the `handler` function result
* _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.

View File

@ -10,12 +10,12 @@ It automatically generates a key based on URL and fetch options, provides type h
## Type
```ts [Signature]
function useFetch(
function useFetch<DataT, ErrorT>(
url: string | Request | Ref<string | Request> | () => string | Request,
options?: UseFetchOptions<DataT>
): Promise<AsyncData<DataT>>
): Promise<AsyncData<DataT, ErrorT>>
type UseFetchOptions = {
type UseFetchOptions<DataT> = {
key?: string
method?: string
query?: SearchParams
@ -29,7 +29,7 @@ type UseFetchOptions = {
default?: () => DataT
transform?: (input: DataT) => DataT
pick?: string[]
watch?: WatchSource[]
watch?: WatchSource[] | false
}
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.
::
* **Options (from [`useAsyncData`](/docs/api/composables/use-async-data) )**:
* `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.
* `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.
* `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`.
* `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`)
* **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` is used.
* `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
* `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}
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()
// Get the URL
const url = event.node.req.url
const url = event.path
```
::alert{icon=👉}

View File

@ -157,7 +157,7 @@ We recommend using [VS Code](https://code.visualstudio.com/) along with the [ESL
#### 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.

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { existsSync, readFileSync } from 'node:fs'
import ignore from 'ignore'
import { join, relative } from 'pathe'
import { join, relative, resolve } from 'pathe'
import { tryUseNuxt } from './context'
/**
@ -16,14 +16,7 @@ export function isIgnored (pathname: string): boolean {
if (!nuxt._ignore) {
nuxt._ignore = ignore(nuxt.options.ignoreOptions)
const resolvedIgnore = nuxt.options.ignore.flatMap(s => resolveGroupSyntax(s))
nuxt._ignore.add(resolvedIgnore)
const nuxtignoreFile = join(nuxt.options.rootDir, '.nuxtignore')
if (existsSync(nuxtignoreFile)) {
nuxt._ignore.add(readFileSync(nuxtignoreFile, 'utf-8'))
}
nuxt._ignore.add(resolveIgnorePatterns())
}
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))
}
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.
* For example will '**\/*.{spec,test}.{js,ts}' be resolved to:

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import type { Ref } from 'vue'
import { getCurrentInstance, nextTick, onUnmounted, ref, toRaw, watch } from 'vue'
import type { CookieParseOptions, CookieSerializeOptions } 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 destr from 'destr'
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 {
if (import.meta.server) {
return parse(useRequestEvent()?.node.req.headers.cookie || '', opts)
return parse(getRequestHeader(useRequestEvent(), 'cookie') || '', opts)
} else if (import.meta.client) {
return parse(document.cookie, opts)
}

View File

@ -1,5 +1,5 @@
import type { H3Event } from 'h3'
import { setResponseStatus as _setResponseStatus } from 'h3'
import { setResponseStatus as _setResponseStatus, getRequestHeaders } from 'h3'
import type { NuxtApp } 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 (include?: any[]) {
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 }
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 || []
if (nuxt.options.experimental.treeshakeClientOnly && isServer) {
config.plugins.push(TreeShakeTemplatePlugin.vite({
sourcemap: nuxt.options.sourcemap[mode],
sourcemap: !!nuxt.options.sourcemap[mode],
getComponents
}))
}
config.plugins.push(clientFallbackAutoIdPlugin.vite({
sourcemap: nuxt.options.sourcemap[mode],
sourcemap: !!nuxt.options.sourcemap[mode],
rootDir: nuxt.options.rootDir
}))
config.plugins.push(loaderPlugin.vite({
sourcemap: nuxt.options.sourcemap[mode],
sourcemap: !!nuxt.options.sourcemap[mode],
getComponents,
mode,
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 || []
if (nuxt.options.experimental.treeshakeClientOnly && mode === 'server') {
config.plugins.push(TreeShakeTemplatePlugin.webpack({
sourcemap: nuxt.options.sourcemap[mode],
sourcemap: !!nuxt.options.sourcemap[mode],
getComponents
}))
}
config.plugins.push(clientFallbackAutoIdPlugin.webpack({
sourcemap: nuxt.options.sourcemap[mode],
sourcemap: !!nuxt.options.sourcemap[mode],
rootDir: nuxt.options.rootDir
}))
config.plugins.push(loaderPlugin.webpack({
sourcemap: nuxt.options.sourcemap[mode],
sourcemap: !!nuxt.options.sourcemap[mode],
getComponents,
mode,
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)
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) {
const nuxt = useNuxt()
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)
if (resolvedNames.has(componentName + suffix) || resolvedNames.has(componentName)) {
console.warn(`[nuxt] Two component files resolving to the same name \`${componentName}\`:\n` +
`\n - ${filePath}` +
`\n - ${resolvedNames.get(componentName)}`
)
warnAboutDuplicateComponent(componentName, filePath, resolvedNames.get(componentName) || resolvedNames.get(componentName + suffix)!)
continue
}
resolvedNames.set(componentName + suffix, filePath)
@ -136,10 +134,14 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
continue
}
// Ignore component if component is already defined (with same mode)
if (!components.some(c => c.pascalName === component.pascalName && ['all', component.mode].includes(c.mode))) {
components.push(component)
const existingComponent = components.find(c => c.pascalName === component.pascalName && ['all', component.mode].includes(c.mode))
if (existingComponent) {
// Ignore component if component is already defined (with same mode)
warnAboutDuplicateComponent(componentName, filePath, existingComponent.filePath)
continue
}
components.push(component)
}
scannedPaths.push(dir.path)
}
@ -174,3 +176,10 @@ export function resolveComponentName (fileName: string, prefixParts: string[]) {
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 { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } 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 { defu } from 'defu'
import fsExtra from 'fs-extra'
@ -31,11 +31,10 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
: [/node_modules/]
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}\`.`)
}
// @ts-expect-error `typescriptBundlerResolution` coming in next nitro version
const nitroConfig: NitroConfig = defu(_nitroConfig, {
debug: nuxt.options.debug,
rootDir: nuxt.options.rootDir,
@ -44,9 +43,8 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
dev: nuxt.options.dev,
buildDir: nuxt.options.buildDir,
experimental: {
// @ts-expect-error `typescriptBundlerResolution` coming in next nitro version
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
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'
},
imports: {
autoImport: nuxt.options.imports.autoImport as boolean,
@ -91,7 +89,9 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
'#spa-template': () => {
if (!spaLoadingTemplate) { return 'export const template = ""' }
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 {}
return `export const template = ${JSON.stringify(defaultSpaLoadingTemplate({}))}`
}
@ -117,6 +117,11 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
tsConfig: {
include: [
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
nitroConfig.srcDir = resolve(nuxt.options.rootDir, nuxt.options.srcDir, nitroConfig.srcDir!)
nitroConfig.ignore = [...(nitroConfig.ignore || []), ...resolveIgnorePatterns(nitroConfig.srcDir)]
// Add fallback server for `ssr: false`
if (!nuxt.options.ssr) {
@ -405,3 +411,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
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 { version } from '../../package.json'
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
import type { UnctxTransformPluginOptions } from './plugins/unctx'
import { UnctxTransformPlugin } from './plugins/unctx'
import type { TreeShakeComposablesPluginOptions } from './plugins/tree-shake'
import { TreeShakeComposablesPlugin } from './plugins/tree-shake'
@ -94,14 +95,14 @@ async function initNuxt (nuxt: Nuxt) {
if (nuxt.options.experimental.localLayerAliases) {
// Add layer aliasing support for ~, ~~, @ and @@ aliases
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,
root: nuxt.options.srcDir,
// skip top-level layer (user's project) as the aliases will already be correctly resolved
layers: nuxt.options._layers.slice(1)
}))
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,
root: nuxt.options.srcDir,
// 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
const options = {
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client,
transformerOptions: nuxt.options.optimization.asyncTransforms
}
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
transformerOptions: {
...nuxt.options.optimization.asyncTransforms,
helperModule: await tryResolveModule('unctx', nuxt.options.modulesDir) ?? 'unctx'
}
} satisfies UnctxTransformPluginOptions
addVitePlugin(() => UnctxTransformPlugin.vite(options))
addWebpackPlugin(() => UnctxTransformPlugin.webpack(options))
// Add composable tree-shaking optimisations
const serverTreeShakeOptions: TreeShakeComposablesPluginOptions = {
sourcemap: nuxt.options.sourcemap.server,
sourcemap: !!nuxt.options.sourcemap.server,
composables: nuxt.options.optimization.treeShake.composables.server
}
if (Object.keys(serverTreeShakeOptions.composables).length) {
@ -129,7 +133,7 @@ async function initNuxt (nuxt: Nuxt) {
addWebpackPlugin(() => TreeShakeComposablesPlugin.webpack(serverTreeShakeOptions), { client: false })
}
const clientTreeShakeOptions: TreeShakeComposablesPluginOptions = {
sourcemap: nuxt.options.sourcemap.client,
sourcemap: !!nuxt.options.sourcemap.client,
composables: nuxt.options.optimization.treeShake.composables.client
}
if (Object.keys(clientTreeShakeOptions.composables).length) {
@ -140,8 +144,8 @@ async function initNuxt (nuxt: Nuxt) {
if (!nuxt.options.dev) {
// DevOnly component tree-shaking - build time only
addVitePlugin(() => DevOnlyPlugin.vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
addWebpackPlugin(() => DevOnlyPlugin.webpack({ 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 }))
}
// 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'
interface UnctxTransformPluginOptions {
export interface UnctxTransformPluginOptions {
sourcemap?: boolean
transformerOptions: TransformerOptions
}

View File

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

View File

@ -9,7 +9,7 @@ import {
import type { RenderResponse } from 'nitropack'
import type { Manifest } from 'vite'
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 { stringify, uneval } from 'devalue'
import destr from 'destr'
@ -170,16 +170,16 @@ const islandPropCache = import.meta.prerender ? useStorage('internal:nuxt:preren
async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
// TODO: Strict validation for url
let url = event.node.req.url || ''
if (import.meta.prerender && event.node.req.url && await islandPropCache!.hasItem(event.node.req.url)) {
let url = event.path || ''
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
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) || ''
const [componentName, hashId] = url.split('?')[0].split('_')
// 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 = {
url: '/',
@ -202,7 +202,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
const nitroApp = useNitroApp()
// 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>
: null
@ -210,7 +210,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
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({
statusCode: 404,
statusMessage: 'Page Not Found: /__nuxt_error'
@ -218,21 +218,23 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
}
// 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)
: undefined
if (import.meta.prerender && islandContext && event.node.req.url && await islandCache!.hasItem(event.node.req.url)) {
return islandCache!.getItem(event.node.req.url) as Promise<Partial<RenderResponse>>
if (import.meta.prerender && islandContext && event.path && await islandCache!.hasItem(event.path)) {
return islandCache!.getItem(event.path) as Promise<Partial<RenderResponse>>
}
// 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
const isRenderingPayload = PAYLOAD_URL_RE.test(url) && !islandContext
if (isRenderingPayload) {
url = url.substring(0, url.lastIndexOf('/')) || '/'
event._path = url
event.node.req.url = url
if (import.meta.prerender && await payloadCache!.hasItem(url)) {
return payloadCache!.getItem(url) as Promise<Partial<RenderResponse>>
@ -435,8 +437,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
const response = {
body: JSON.stringify(islandResponse, null, 2),
statusCode: event.node.res.statusCode,
statusMessage: event.node.res.statusMessage,
statusCode: getResponseStatus(event),
statusMessage: getResponseStatusText(event),
headers: {
'content-type': 'application/json;charset=utf-8',
'x-powered-by': 'Nuxt'
@ -444,7 +446,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
} satisfies RenderResponse
if (import.meta.prerender) {
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
}
@ -452,8 +454,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
// Construct HTML response
const response = {
body: renderHTMLDocument(htmlContext),
statusCode: event.node.res.statusCode,
statusMessage: event.node.res.statusMessage,
statusCode: getResponseStatus(event),
statusMessage: getResponseStatusText(event),
headers: {
'content-type': 'text/html;charset=utf-8',
'x-powered-by': 'Nuxt'
@ -511,8 +513,8 @@ function renderPayloadResponse (ssrContext: NuxtSSRContext) {
body: process.env.NUXT_JSON_PAYLOADS
? stringify(splitPayload(ssrContext).payload, ssrContext._payloadReducers)
: `export default ${devalue(splitPayload(ssrContext).payload)}`,
statusCode: ssrContext.event.node.res.statusCode,
statusMessage: ssrContext.event.node.res.statusMessage,
statusCode: getResponseStatus(ssrContext.event),
statusMessage: getResponseStatusText(ssrContext.event),
headers: {
'content-type': process.env.NUXT_JSON_PAYLOADS ? 'application/json;charset=utf-8' : 'text/javascript;charset=utf-8',
'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 type { Import, Unimport } 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')
// Transform to inject imports in production mode
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 }))
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 }))
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
imports.length = 0
// Scan `composables/`
const composableImports = await scanDirExports(composablesDirs)
const composableImports = await scanDirExports(composablesDirs, {
fileFilter: file => !isIgnored(file)
})
for (const i of composableImports) {
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 type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-vue-router'
import type { NitroRouteConfig } from 'nitropack'
import { defu } from 'defu'
import { distDir } from '../dirs'
import { normalizeRoutes, resolvePagesRoutes } from './utils'
import type { PageMetaPluginOptions } from './page-meta'
import { PageMetaPlugin } from './page-meta'
import { RouteInjectionPlugin } from './route-injection'
import { extractRouteRules, getMappedPages } from './route-rules'
import type { PageMetaPluginOptions } from './plugins/page-meta'
import { PageMetaPlugin } from './plugins/page-meta'
import { RouteInjectionPlugin } from './plugins/route-injection'
const OPTIONAL_PARAM_RE = /^\/?:.*(\?|\(\.\*\)\*)$/
@ -239,12 +242,73 @@ export default defineNuxtModule({
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },
{ 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
const pageMetaOptions: PageMetaPluginOptions = {
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', () => {
addVitePlugin(() => PageMetaPlugin.vite(pageMetaOptions))

View File

@ -1,7 +1,7 @@
import { createUnplugin } from 'unplugin'
import MagicString from 'magic-string'
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_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 type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from '#vue-router'
import { useRoute } from 'vue-router'
import type { NitroRouteConfig } from 'nitropack'
import type { NuxtError } from '#app'
export interface PageMeta {
@ -64,3 +65,15 @@ export const definePageMeta = (meta: PageMeta): void => {
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
function extractScriptContent (html: string) {
export function extractScriptContent (html: string) {
const match = html.match(SFC_SCRIPT_RE)
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 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'
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",
"type": "module",
"types": "./dist/index.d.ts",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
@ -30,14 +31,14 @@
"@types/file-loader": "5.0.1",
"@types/pug": "2.0.6",
"@types/sass-loader": "8.0.5",
"@unhead/schema": "1.3.5",
"@vitejs/plugin-vue": "4.3.1",
"@unhead/schema": "1.3.7",
"@vitejs/plugin-vue": "4.3.3",
"@vitejs/plugin-vue-jsx": "3.0.2",
"@vue/compiler-core": "3.3.4",
"esbuild-loader": "4.0.1",
"h3": "1.8.0",
"ignore": "5.2.4",
"nitropack": "2.5.2",
"nitropack": "2.6.0",
"unbuild": "latest",
"unctx": "2.3.1",
"vite": "4.4.9",
@ -55,9 +56,9 @@
"pathe": "^1.1.1",
"pkg-types": "^1.0.3",
"postcss-import-resolver": "^2.0.0",
"std-env": "^3.4.0",
"ufo": "^1.2.0",
"unimport": "^3.1.3",
"std-env": "^3.4.3",
"ufo": "^1.3.0",
"unimport": "^3.2.0",
"untyped": "^1.4.0"
},
"engines": {

View File

@ -241,10 +241,10 @@ export default defineUntypedSchema({
* }
* </style>
* ```
* @type {string | false}
* @type {string | boolean}
*/
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.
*
* @type {boolean | { server?: boolean, client?: boolean }}
* @type {boolean | { server?: boolean | 'hidden', client?: boolean | 'hidden' }}
*/
sourcemap: {
$resolve: async (val, get) => {

View File

@ -238,6 +238,18 @@ export default defineUntypedSchema({
*
* @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.
_version: string
_ignore?: Ignore
_ignorePatterns?: string[]
/** The resolved Nuxt configuration. */
options: NuxtOptions

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ export async function buildClient (ctx: ViteBuildContext) {
}
},
css: {
devSourcemap: ctx.nuxt.options.sourcemap.client
devSourcemap: !!ctx.nuxt.options.sourcemap.client
},
define: {
'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)
}),
runtimePathsPlugin({
sourcemap: ctx.nuxt.options.sourcemap.client
sourcemap: !!ctx.nuxt.options.sourcemap.client
}),
viteNodePlugin(ctx),
pureAnnotationsPlugin.vite({
sourcemap: ctx.nuxt.options.sourcemap.client,
functions: ['defineComponent', 'defineAsyncComponent', 'defineNuxtLink', 'createClientOnly', 'defineNuxtPlugin', 'defineNuxtRouteMiddleware', 'defineNuxtComponent', 'useRuntimeConfig']
sourcemap: !!ctx.nuxt.options.sourcemap.client,
functions: ['defineComponent', 'defineAsyncComponent', 'defineNuxtLink', 'createClientOnly', 'defineNuxtPlugin', 'defineNuxtRouteMiddleware', 'defineNuxtComponent', 'useRuntimeConfig', 'defineRouteRules']
})
],
appType: 'custom',
@ -102,7 +102,7 @@ export async function buildClient (ctx: ViteBuildContext) {
// Emit chunk errors if the user has opted in to `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
@ -135,7 +135,7 @@ export async function buildClient (ctx: ViteBuildContext) {
// Add type checking client panel
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 })
@ -163,18 +163,17 @@ export async function buildClient (ctx: ViteBuildContext) {
})
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)
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
event.node.req._skip_transform = true
}
// Workaround: vite devmiddleware modifies req.url
const _originalPath = event.node.req.url
await new Promise((resolve, reject) => {
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)
})
})

View File

@ -75,11 +75,13 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
source: ''
})
const baseDir = dirname(base)
emitted[file] = this.emitFile({
type: 'asset',
name: `${filename(file)}-styles.mjs`,
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(', ')}]`
].join('\n')
})

View File

@ -15,7 +15,6 @@ import { transpile } from './utils/transpile'
export async function buildServer (ctx: ViteBuildContext) {
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 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({
configFile: false,
base: ctx.nuxt.options.dev
@ -37,7 +36,7 @@ export async function buildServer (ctx: ViteBuildContext) {
}
},
css: {
devSourcemap: ctx.nuxt.options.sourcemap.server
devSourcemap: !!ctx.nuxt.options.sourcemap.server
},
define: {
'process.server': true,
@ -62,11 +61,7 @@ export async function buildServer (ctx: ViteBuildContext) {
},
ssr: {
external: [
'#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
'#internal/nitro', '#internal/nitro/utils'
],
noExternal: [
...transpile({ isServer: true, isDev: ctx.nuxt.options.dev }),
@ -106,12 +101,23 @@ export async function buildServer (ctx: ViteBuildContext) {
},
plugins: [
pureAnnotationsPlugin.vite({
sourcemap: ctx.nuxt.options.sourcemap.server,
functions: ['defineComponent', 'defineAsyncComponent', 'defineNuxtLink', 'createClientOnly', 'defineNuxtPlugin', 'defineNuxtRouteMiddleware', 'defineNuxtComponent', 'useRuntimeConfig']
sourcemap: !!ctx.nuxt.options.sourcemap.server,
functions: ['defineComponent', 'defineAsyncComponent', 'defineNuxtLink', 'createClientOnly', 'defineNuxtPlugin', 'defineNuxtRouteMiddleware', 'defineNuxtComponent', 'useRuntimeConfig', 'defineRouteRules']
})
]
} 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)
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) => {
const moduleId = decodeURI(event.node.req.url!).substring(1)
const moduleId = decodeURI(event.path).substring(1)
if (moduleId === '/') {
throw createError({ statusCode: 400 })
}

View File

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

View File

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

View File

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

View File

@ -27,7 +27,12 @@ export function server (ctx: WebpackConfigContext) {
function serverPreset (ctx: WebpackConfigContext) {
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 = {
splitChunks: false,

View File

@ -39,14 +39,14 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
for (const config of webpackConfigs) {
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`
if (config.name === 'client' && nuxt.options.experimental.emitRouteChunkError) {
config.plugins!.push(new ChunkErrorPlugin())
}
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,
composables: nuxt.options.optimization.keyedComposables
}))

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