This commit is contained in:
julien huang 2023-08-28 23:58:45 +02:00
commit d8cd415f41
270 changed files with 6712 additions and 5085 deletions

View File

@ -1,5 +0,0 @@
dist
node_modules
schema
**/*.tmpl.*
sw.js

View File

@ -1,13 +1,17 @@
{
"ignorePatterns": [
"dist",
"node_modules",
"packages/schema/schema",
"**/*.tmpl.*",
"sw.js"
],
"$schema": "https://json.schemastore.org/eslintrc",
"globals": {
"NodeJS": true,
"$fetch": true
},
"plugins": [
"jsdoc",
"no-only-tests"
],
"plugins": ["jsdoc", "no-only-tests"],
"extends": [
"plugin:jsdoc/recommended",
"@nuxtjs/eslint-config-typescript",
@ -96,10 +100,28 @@
"ignoreRestSiblings": true
}
],
"jsdoc/check-tag-names": ["error", {
"definedTags": ["__NO_SIDE_EFFECTS__"]
}]
"jsdoc/check-tag-names": [
"error",
{
"definedTags": ["__NO_SIDE_EFFECTS__"]
}
]
},
"overrides": [
{
"files": ["packages/schema/**"],
"rules": {
"jsdoc/no-undefined-types": "off",
"jsdoc/valid-types": "off",
"jsdoc/check-tag-names": [
"error",
{
"definedTags": ["experimental"]
}
]
}
}
],
"settings": {
"jsdoc": {
"ignoreInternal": true,

View File

@ -10,7 +10,7 @@ body:
Please use a template below to create a minimal reproduction
👉 https://stackblitz.com/github/nuxt/starter/tree/v3-stackblitz
👉 https://codesandbox.io/p/github/nuxt/starter/v3-codesandbox
👉 https://codesandbox.io/s/github/nuxt/starter/v3-codesandbox
- type: textarea
id: bug-env
attributes:

View File

@ -10,7 +10,7 @@ body:
Please use a template below to create a minimal reproduction
👉 https://stackblitz.com/github/nuxt/starter/tree/v2
👉 https://codesandbox.io/p/github/nuxt/starter/v2
👉 https://codesandbox.io/s/github/nuxt/starter/v2
- type: textarea
id: bug-env
attributes:

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="39.4" height="32" viewBox="0 0 1231.051 1000"><path fill="#A1A1AA" d="M1231.051 118.453q-51.422 76.487-126.173 130.403q.738 14.46.738 32.687q0 101.273-29.53 202.791q-29.53 101.519-90.215 194.343q-60.685 92.824-144.574 164.468q-83.889 71.644-201.677 114.25q-117.788 42.606-252.474 42.606q-210.2 0-387.147-113.493q31.406 3.495 60.242 3.495q175.605 0 313.687-108.177q-81.877-1.501-146.654-50.409q-64.777-48.907-89.156-124.988q24.097 4.59 47.566 4.59q33.782 0 66.482-8.812q-87.378-17.5-144.975-87.04q-57.595-69.539-57.595-160.523v-3.126q53.633 29.696 114.416 31.592q-51.762-34.508-82.079-89.999q-30.319-55.491-30.319-120.102q0-68.143 34.151-126.908q95.022 116.607 230.278 186.392q135.258 69.786 290.212 77.514q-6.609-27.543-6.621-57.485q0-104.546 73.994-178.534Q747.623 0 852.169 0q109.456 0 184.392 79.711q85.618-16.959 160.333-61.349q-28.785 90.59-110.933 139.768q75.502-8.972 145.088-39.677z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g><path fill="#A1A1AA" d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"></path></g></svg>

Before

Width:  |  Height:  |  Size: 947 B

After

Width:  |  Height:  |  Size: 285 B

View File

@ -0,0 +1,32 @@
Would you be able to provide a [reproduction](https://nuxt.com/docs/community/reporting-bugs/#create-a-minimal-reproduction)? 🙏
<details>
<summary>More info</summary>
### Why do I need to provide a reproduction?
Reproductions make it possible for us to triage and fix issues quickly with a relatively small team. It helps us discover the source of the problem, and also can reveal assumptions you or we might be making.
### What will happen?
If you've provided a reproduction, we'll remove the label and try to reproduce the issue. If we can, we'll mark it as a bug and prioritise it based on its severity and how many people we think it might affect.
If `needs reproduction` labeled issues don't receive any substantial activity (e.g., new comments featuring a reproduction link), we'll close them. That's not because we don't care! At any point, feel free to comment with a reproduction and we'll reopen it.
### How can I create a reproduction?
We have a couple of templates for starting with a minimal reproduction:
👉 https://stackblitz.com/github/nuxt/starter/tree/v3-stackblitz
👉 https://codesandbox.io/s/github/nuxt/starter/v3-codesandbox
A public GitHub repository is also perfect. 👌
Please ensure that the reproduction is as **minimal** as possible. See more details [in our guide](https://nuxt.com/docs/community/reporting-bugs/#create-a-minimal-reproduction).
You might also find these other articles interesting and/or helpful:
- [The Importance of Reproductions](https://antfu.me/posts/why-reproductions-are-required)
- [How to Generate a Minimal, Complete, and Verifiable Example](https://stackoverflow.com/help/mcve)
</details>

View File

@ -17,9 +17,9 @@ 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@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"

View File

@ -13,9 +13,9 @@ 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@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"

View File

@ -19,11 +19,11 @@ 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
- uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"

View File

@ -38,9 +38,9 @@ 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@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"
@ -75,9 +75,9 @@ jobs:
- build
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable
- uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"
@ -86,7 +86,7 @@ jobs:
run: pnpm install
- name: Initialize CodeQL
uses: github/codeql-action/init@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # v2.21.0
uses: github/codeql-action/init@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4
with:
languages: javascript
queries: +security-and-quality
@ -98,7 +98,7 @@ jobs:
path: packages
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # v2.21.0
uses: github/codeql-action/analyze@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4
with:
category: "/language:javascript"
@ -112,12 +112,11 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest]
module: ['bundler', 'node']
base: ['with-base-url', 'without-base-url']
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- run: corepack enable
- uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"
@ -135,7 +134,6 @@ jobs:
run: pnpm test:types
env:
MODULE_RESOLUTION: ${{ matrix.module }}
TS_BASE_URL: ${{ matrix.base }}
lint:
# autofix workflow will be triggered instead for PRs
@ -144,9 +142,9 @@ 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@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"
@ -171,18 +169,18 @@ jobs:
os: [ubuntu-latest, windows-latest]
env: ['dev', 'built']
builder: ['vite', 'webpack']
payload: ['json', 'js']
context: ['async', 'default']
node: [16]
exclude:
- env: 'dev'
builder: 'webpack'
timeout-minutes: 10
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@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: ${{ matrix.node }}
cache: "pnpm"
@ -233,8 +231,8 @@ jobs:
env:
TEST_ENV: ${{ matrix.env }}
TEST_BUILDER: ${{ matrix.builder }}
TEST_PAYLOAD: ${{ matrix.payload }}
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.payload == 'js' || runner.os == 'Windows' }}
TEST_CONTEXT: ${{ matrix.context }}
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || runner.os == 'Windows' }}
build-release:
permissions:
@ -252,11 +250,11 @@ 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
- uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"
@ -291,11 +289,11 @@ 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
- uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"

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@1360a344ccb0ab6e9475edef90ad2f46bf8003b1 # v3.0.6
uses: actions/dependency-review-action@f6fff72a3217f580d5afd49a46826795305b63c7 # v3.0.8

View File

@ -21,9 +21,9 @@ 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@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
cache: "pnpm"

View File

@ -20,9 +20,9 @@ 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@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"

View File

@ -21,9 +21,9 @@ 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: |
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/590d3bd9dde0c91f7a66071d40eb84716526e5a6/scripts/download-actionlint.bash)
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/590d3bd9dde0c91f7a66071d40eb84716526e5a6/scripts/download-actionlint.bash) 1.6.25
./actionlint -color -shellcheck=""

View File

@ -21,13 +21,13 @@ 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
- name: fetch tags
run: git fetch --depth=1 origin "+refs/tags/*:refs/tags/*"
- uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 16
registry-url: 'https://registry.npmjs.org'

View File

@ -29,13 +29,13 @@ 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
- run: corepack enable
- uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: 20
cache: "pnpm"

24
.github/workflows/reproduire-close.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Close incomplete issues
on:
workflow_dispatch:
schedule:
- cron: '30 1 * * *' # run every day
permissions:
issues: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale.
only-labels: 'needs reproduction' # Only process these issues
days-before-issue-close: 7
ignore-updates: true
remove-stale-when-updated: false
close-issue-message: This issue was closed because it was open for 7 days without a reproduction.
close-issue-label: closed-by-bot
operations-per-run: 300 #default 30

16
.github/workflows/reproduire.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Reproduire
on:
issues:
types: [labeled]
permissions:
issues: write
jobs:
reproduire:
runs-on: ubuntu-latest
steps:
- 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
@ -66,6 +66,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # v2.21.0
uses: github/codeql-action/upload-sarif@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4
with:
sarif_file: results.sarif

View File

@ -15,7 +15,7 @@ jobs:
permissions:
pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
if: github.repository == 'nuxt/nuxt'
if: github.repository == 'nuxt/nuxt' && !startsWith(github.head_ref, 'v')
runs-on: ubuntu-latest
name: Semantic pull request
steps:

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"
}
}

12
.website/.gitignore vendored Executable file
View File

@ -0,0 +1,12 @@
node_modules
*.iml
.idea
*.log*
.nuxt
.vscode
.DS_Store
coverage
dist
sw.*
.env
.output

35
.website/README.md Executable file
View File

@ -0,0 +1,35 @@
# Nuxt Docs Website
This is a temporary directory until we open source the repository for nuxt.com.
The goal is to simplify the contribution in the meantine to the documentation by having the possibility to preview the changes locally.
## Setup
Install dependencies in the root of the `nuxt` folder:
```bash
pnpm i
```
Then stub the dependencies:
```bash
pnpm build:stub
```
## Development
In the root of the `nuxt` folder, run:
```bash
pnpm docs:dev
```
Then open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
Update the documentation within the `docs` folder.
---
For a detailed explanation of how things work, check out [Docus](https://docus.dev).

14
.website/app.config.ts Normal file
View File

@ -0,0 +1,14 @@
export default defineAppConfig({
docus: {
title: 'Nuxt Docs [dev]',
description: 'The best place to start your documentation.',
socials: {
twitter: 'nuxt_js',
github: 'nuxt/nuxt'
},
aside: {
level: 1,
collapsed: false,
},
}
})

20
.website/nuxt.config.ts Executable file
View File

@ -0,0 +1,20 @@
import { createResolver } from 'nuxt/kit'
const { resolve } = createResolver(import.meta.url)
export default defineNuxtConfig({
// https://github.com/nuxt-themes/docus
extends: '@nuxt-themes/docus',
content: {
sources: {
docs: {
driver: 'fs',
prefix: '/',
base: resolve('../docs')
}
}
},
experimental: {
renderJsonPayloads: false
}
})

14
.website/package.json Executable file
View File

@ -0,0 +1,14 @@
{
"name": "docus-starter",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "nuxt dev",
"build": "nuxt build",
"generate": "nuxt generate",
"preview": "nuxt preview"
},
"devDependencies": {
"@nuxt-themes/docus": "1.14.6"
}
}

3
.website/tsconfig.json Executable file
View File

@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}

View File

@ -81,52 +81,61 @@ There are two ways to deploy a Nuxt application to any static hosting services:
### Crawl-based Pre-rendering
Use the [`nuxi generate` command](/docs/api/commands/generate) to build your application. For every page, Nuxt uses a crawler to generate a corresponding HTML and payload files. The built files will be generated in the `.output/public` directory.
Use the [`nuxi generate` command](/docs/api/commands/generate) to build and pre-render your application using the [Nitro](/docs/guide/concepts/server-engine) crawler. This command is similar to `nuxt build` with the `nitro.static` option set to `true`, or running `nuxt build --prerender`.
```bash
npx nuxi generate
```
You can enable crawl-based pre-rendering when using `nuxt build` in the `nuxt.config` file:
That's it! You can now deploy the `.output/public` directory to any static hosting service or preview it locally with `npx serve .output/public`.
```ts [nuxt.config.ts|js]
defineNuxtConfig({
nitro: {
prerender: {
crawlLinks: true
}
}
})
```
Working of the Nitro crawler:
1. Load the HTML of your application's root route (`/`), any non-dynamic pages in your `~/pages` directory, and any other routes in the `nitro.prerender.routes` array.
2. Save the HTML and `payload.json` to the `~/.output/public/` directory to be served statically.
3. Find all anchor tags (`<a href="...">`) in the HTML to navigate to other routes.
4. Repeat steps 1-3 for each anchor tag found until there are no more anchor tags to crawl.
This is important to understand since pages that are not linked to a discoverable page can't be pre-rendered automatically.
::alert{type=info}
Read more about the [`nuxi generate` command](/docs/api/commands/generate#nuxi-generate).
::
### Selective Pre-rendering
You can manually specify routes that [Nitro](/docs/guide/concepts/server-engine) will fetch and pre-render during the build.
You can manually specify routes that [Nitro](/docs/guide/concepts/server-engine) will fetch and pre-render during the build or ignore routes that you don't want to pre-render like `/dynamic` in the `nuxt.config` file:
```ts [nuxt.config.ts|js]
defineNuxtConfig({
nitro: {
prerender: {
routes: ['/user/1', '/user/2']
ignore: ['/dynamic']
}
}
})
```
When using this option with `nuxi build`, static payloads won't be generated by default at build time. For now, selective payload generation is under an experimental flag.
You can combine this with the `crawLinks` option to pre-render a set of routes that the crawler can't discover like your `/sitemap.xml` or `/robots.txt`:
```ts [nuxt.config.ts|js]
defineNuxtConfig({
/* The /dynamic route won't be crawled */
nitro: {
prerender: { crawlLinks: true, ignore: ['/dynamic'] }
},
experimental: {
payloadExtraction: true
prerender: {
crawlLinks: true,
routes: ['/sitemap.xml', '/robots.txt']
}
}
})
```
Setting `nitro.prerender` to `true` is similar to `nitro.prerender.crawlLinks` to `true`.
::alert{type=info}
Read more about [pre-rendering](https://nitro.unjs.io/config#prerender) in the Nitro documentation.
::
### Client-side Only Rendering
If you don't want to pre-render your routes, another way of using static hosting is to set the `ssr` property to `false` in the `nuxt.config` file. The `nuxi generate` command will then output an `.output/public/index.html` entrypoint and JavaScript bundles like a classic client-side Vue.js application.

View File

@ -31,7 +31,7 @@ Start with one of our starters and themes directly by opening [nuxt.new](https:/
::details
:summary[Additional notes for an optimal setup:]
- **Node.js**: Make sure to use an even numbered version (18, 20, etc)
- **Nuxtr**: Install the community-developed [Nuxtr extension](https://marketplace.visualstudio.com/items?itemName=Nuxtr.nuxtr-vscode)
- **Volar**: Either enable [**Take Over Mode**](https://vuejs.org/guide/typescript/overview.html#volar-takeover-mode) (recommended) or add the [TypeScript Vue Plugin](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin)
If you have enabled **Take Over Mode** or installed the **TypeScript Vue Plugin (Volar)**, you can disable generating the shim for `*.vue` files in your [`nuxt.config.ts`](/docs/guide/directory-structure/nuxt.config) file:
@ -59,6 +59,10 @@ npx nuxi@latest init <project-name>
pnpm dlx nuxi@latest init <project-name>
```
```bash [bun]
bunx nuxi@latest init <project-name>
```
::
Open your project folder in Visual Studio Code:
@ -84,6 +88,10 @@ npm install
pnpm install
```
```bash [bun]
bun install
```
::
## Development Server
@ -104,6 +112,10 @@ npm run dev -- -o
pnpm dev -o
```
```bash [bun]
bun run dev -o
```
::
::alert{type=success icon=✨ .font-bold}

View File

@ -71,7 +71,7 @@ When a `<NuxtLink>` enters the viewport on the client side, Nuxt will automatica
The `useRoute()` composable can be used in a `<script setup>` block or a `setup()` method of a Vue component to access the current route details.
```vue [pages/posts/[id].vue]
```vue [pages/posts/[id\\].vue]
<script setup lang="ts">
const route = useRoute()
@ -133,7 +133,7 @@ The `validate` property accepts the `route` as an argument. You can return a boo
If you have a more complex use case, then you can use anonymous route middleware instead.
```vue [pages/posts/[id].vue]
```vue [pages/posts/[id\\].vue]
<script setup lang="ts">
definePageMeta({
validate: async (route) => {

View File

@ -318,7 +318,7 @@ To apply dynamic transitions using conditional logic, you can leverage inline [m
::code-group
```html [pages/[id].vue]
```html [pages/[id\\].vue]
<script setup lang="ts">
definePageMeta({
pageTransition: {

View File

@ -149,7 +149,9 @@ const { pending, data: posts } = useLazyFetch('/api/posts')
### Client-only fetching
By default, data fetching composables will perform their asynchronous function on both client and server environments. Set the `server` option to `false` to only perform the call on the client-side. Combined with the `lazy` option, this can be useful for data that are not needed on the first render (for example, non-SEO sensitive data).
By default, data fetching composables will perform their asynchronous function on both client and server environments. Set the `server` option to `false` to only perform the call on the client-side. On initial load, the data will not be fetched before hydration is complete so you have to handle a pending state, though on subsequent client-side navigation the data will be awaited before loading the page.
Combined with the `lazy` option, this can be useful for data that is not needed on the first render (for example, non-SEO sensitive data).
```ts
/* This call will only be performed on the client */
@ -159,6 +161,8 @@ const { pending, data: posts } = useFetch('/api/comments', {
})
```
The `useFetch` composable is meant to be invoked in setup method or called directly at the top level of a function in lifecycle hooks, otherwise you should use `$fetch` method.
### Minimize payload size
The `pick` option helps you to minimize the payload size stored in your HTML document by only selecting the fields that you want returned from the composables.

View File

@ -57,9 +57,36 @@ You can change this behavior by setting `experimental.emitRouteChunkError` to `f
## Rendering an Error Page
When Nuxt encounters a fatal error, whether during the server lifecycle, or when rendering your Vue application (both SSR and SPA), it will either render a JSON response (if requested with `Accept: application/json` header) or an HTML error page.
When Nuxt encounters a fatal error (any unhandled error on the server, or an error created with `fatal: true` on the client) it will either render a JSON response (if requested with `Accept: application/json` header) or trigger a full-screen error page.
You can customize this error page by adding `~/error.vue` in the source directory of your application, alongside `app.vue`. This page has a single prop - `error` which contains an error for you to handle.
This error may occur during the server lifecycle when:
* processing your Nuxt plugins
* rendering your Vue app into HTML
* a server API route throws an error
An error can also occur on the client side when:
* processing your Nuxt plugins
* before mounting the application (`app:beforeMount` hook)
* mounting your app if the error was not handled with `onErrorCaptured` or `vue:error` hook
* the Vue app is initialized and mounted in browser (`app:mounted`).
The lifecycle hooks are listed [here](/docs/api/advanced/hooks).
You can customize this error page by adding `~/error.vue` in the source directory of your application, alongside `app.vue`. Although it is called an 'error page' it's not a route and shouldn't be placed in your `~/pages` directory. For the same reason, you shouldn't use `definePageMeta` within this page.
The error page has a single prop - `error` which contains an error for you to handle.
The `error` object provides the fields: `url`, `statusCode`, `statusMessage`, `message`, `description` and `data`. If you have an error with custom fields they will be lost; you should assign them to `data` instead. For custom errors we highly recommend to use `onErrorCaptured` composable that can be called in a page/component setup function or `vue:error` runtime nuxt hook that can be configured in a nuxt plugin.
```ts
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.hook('vue:error', (err) => {
//
})
})
```
When you are ready to remove the error page, you can call the `clearError` helper function, which takes an optional path to redirect to (for example, if you want to navigate to a 'safe' page).
@ -111,7 +138,7 @@ If you throw an error created with `createError`:
### Example
```vue [pages/movies/[slug].vue]
```vue [pages/movies/[slug\\].vue]
<script setup lang="ts">
const route = useRoute()
const { data } = await useFetch(`/api/movies/${route.params.slug}`)

View File

@ -140,3 +140,26 @@ export default defineNuxtConfig({
}
})
```
## Auto-import from third-party packages
Nuxt also allows auto-importing from third-party packages.
::alert
If you are using the Nuxt module for that package, it is likely that the module has already configured auto-imports for that package.
::
For example, you could enable the auto-import of the `useI18n` composable from the `vue-i18n` package like this:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
imports: {
presets: [
{
from: 'vue-i18n',
imports: ['useI18n']
}
]
}
})
```

View File

@ -90,7 +90,7 @@ export default defineNuxtConfig({
// Homepage pre-rendered at build time
'/': { prerender: true },
// Product page generated on-demand, revalidates in background
'/products/**': { swr: true },
'/products/**': { swr: 3600 },
// Blog post generated on-demand once until next deploy
'/blog/**': { isr: true },
// Admin dashboard renders only on client-side
@ -108,8 +108,8 @@ The different properties you can use are the following:
- `ssr: boolean`{lang=ts} - Disables server-side rendering for sections of your app and make them SPA-only with `ssr: false`
- `cors: boolean`{lang=ts} - Automatically adds cors headers with `cors: true` - you can customize the output by overriding with `headers`
- `headers: object`{lang=ts} - Add specific headers to sections of your site - for example, your assets
- `swr: number`{lang=ts} - Add cache headers to the server response and cache it on the server or reverse proxy for a configurable TTL (time to live). The `node-server` preset of Nitro is able to cache the full response. When the TTL expired, the cached response will be sent while the page will be regenerated in the background.
- `isr: boolean`{lang=ts} - The behavior is the same as `swr` except that we are able to add the response to the CDN cache on platforms that support this (currently Netlify or Vercel)
- `swr: number|boolean`{lang=ts} - Add cache headers to the server response and cache it on the server or reverse proxy for a configurable TTL (time to live). The `node-server` preset of Nitro is able to cache the full response. When the TTL expired, the cached response will be sent while the page will be regenerated in the background. If true is used, a `stale-while-revalidate` header is added without a MaxAge.
- `isr: number|boolean`{lang=ts} - The behavior is the same as `swr` except that we are able to add the response to the CDN cache on platforms that support this (currently Netlify or Vercel). If `true` is used, the content persists until the next deploy inside the CDN.
- `prerender:boolean`{lang=ts} - Prerenders routes at build time and includes them in your build as static assets
- `experimentalNoScripts: boolean`{lang=ts} - Disables rendering of Nuxt scripts and JS resource hints for sections of your site.

View File

@ -34,7 +34,20 @@ By default, only the `~/components` directory is scanned. If you want to add oth
```ts [nuxt.config.ts]
export default defineNuxtConfig({
components: [
// ~/calendar-module/components/event/Update.vue => <EventUpdate />
{ path: '~/calendar-module/components' },
// ~/user-module/components/account/UserDeleteDialog.vue => <UserDeleteDialog />
{ path: '~/user-module/components', pathPrefix: false },
// ~/components/special-components/Btn.vue => <SpecialBtn />
{ path: '~/components/special-components', prefix: 'Special' },
// It's important that this comes last if you have overrides you wish to apply
// to sub-directories of `~/components`.
//
// ~/components/Btn.vue => <Btn />
// ~/components/base/Btn.vue => <BaseBtn />
'~/components'
]
})
@ -299,6 +312,8 @@ Now you can register server-only components with the `.server` suffix and use th
</template>
```
Server-only components use `<NuxtIsland>` under the hood, meaning that `lazy` prop and `#fallback` slot are both passed down to `<NuxtIsland>`.
::alert{type=info}
Slots can be interactive and are wrapped within a `<div>` with `display: contents;`
::

View File

@ -38,6 +38,10 @@ Install the `@nuxt/content` module in your project:
pnpm add -D @nuxt/content
```
```bash [bun]
bun add -D @nuxt/content
```
::
Then, add `@nuxt/content` to the `modules` section of `nuxt.config.ts`:
@ -67,7 +71,7 @@ The module automatically loads and parses them.
To render content pages, add a [catch-all route](/docs/guide/directory-structure/pages/#catch-all-route) using the `ContentDoc` component:
```vue [pages/[...slug].vue]
```vue [pages/[...slug\\].vue]
<template>
<main>
<ContentDoc />

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

@ -110,7 +110,7 @@ If you want a parameter to be _optional_, you must enclose it in double square b
Given the example above, you can access group/id within your component via the `$route` object:
```vue [pages/users-[group]/[id].vue]
```vue [pages/users-[group\\]/[id\\].vue]
<template>
<p>{{ $route.params.group }} - {{ $route.params.id }}</p>
</template>
@ -138,7 +138,7 @@ if (route.params.group === 'admins' && !route.params.id) {
If you need a catch-all route, you create it by using a file named like `[...slug].vue`. This will match _all_ routes under that path.
```vue [pages/[...slug].vue]
```vue [pages/[...slug\\].vue]
<template>
<p>{{ $route.params.slug }}</p>
</template>

View File

@ -223,4 +223,8 @@ export default defineNuxtPlugin((nuxtApp) => {
})
```
::alert{type=warning}
If you register a Vue directive, you _must_ register it on both client and server side unless you are only using it when rendering one side. If the directive only makes sense from a client side, you can always move it to `~/plugins/my-directive.client.ts` and provide a 'stub' directive for the server in `~/plugins/my-directive.server.ts`.
::
:ReadMore{link="https://vuejs.org/guide/reusability/custom-directives.html"}

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:
@ -151,8 +151,11 @@ Server routes can use dynamic parameters within brackets in the file name like `
**Example:**
```ts [server/api/hello/[name].ts]
export default defineEventHandler((event) => `Hello, ${event.context.params.name}!`)
```ts [server/api/hello/[name\\].ts]
export default defineEventHandler((event) => {
const name = getRouterParam(event, 'name')
return `Hello, ${name}!`
})
```
You can now universally call this API using `await $fetch('/api/hello/nuxt')` and get `Hello, nuxt!`.
@ -181,11 +184,11 @@ Catch-all routes are helpful for fallback route handling. For example, creating
**Examples:**
```ts [server/api/foo/[...].ts]
```ts [server/api/foo/[...\\].ts]
export default defineEventHandler(() => `Default foo handler`)
```
```ts [server/api/[...].ts]
```ts [server/api/[...\\].ts]
export default defineEventHandler(() => `Default api handler`)
```
@ -221,7 +224,7 @@ If no errors are thrown, a status code of `200 OK` will be returned. Any uncaugh
To return other error codes, throw an exception with `createError`
```ts [server/api/validation/[id].ts]
```ts [server/api/validation/[id\\].ts]
export default defineEventHandler((event) => {
const id = parseInt(event.context.params.id) as number
if (!Number.isInteger(id)) {
@ -240,7 +243,7 @@ To return other status codes, you can use the `setResponseStatus` utility.
For example, to return `202 Accepted`
```ts [server/api/validation/[id].ts]
```ts [server/api/validation/[id\\].ts]
export default defineEventHandler((event) => {
setResponseStatus(event, 202)
})
@ -284,7 +287,7 @@ export default defineNuxtConfig({
### Using a Nested Router
```ts [server/api/hello/[...slug].ts]
```ts [server/api/hello/[...slug\\].ts]
import { createRouter, defineEventHandler, useBase } from 'h3'
const router = createRouter()

View File

@ -29,6 +29,7 @@ export defineNuxtConfig({ experimental: { reactivityTransform: true } })
```
::ReadMore{link="docs/getting-started/configuration#enabling-experimental-vue-features"}
::
## externalVue
@ -143,6 +144,7 @@ export defineNuxtConfig({ experimental: { viewTransition: true } })
```
::ReadMore{link="docs/getting-started/transitions#view-transitions-api-experimental"}
::
## writeEarlyHints
@ -161,6 +163,7 @@ export defineNuxtConfig({ experimental: { componentIslands: true } })
```
::ReadMore{link="docs/guide/directory-structure/components#server-components"}
::
You can follow the server components roadmap on [GitHub](https://github.com/nuxt/nuxt/issues/19772).

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

@ -375,6 +375,40 @@ export default defineNuxtModule({
})
```
#### Injecting Server Routes With `addServerHandler`
```ts
import { defineNuxtModule, addServerHandler, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
addServerHandler({
route: '/api/hello',
handler: resolver.resolve('./runtime/server/api/hello/index.get.ts')
})
}
})
```
You can also add a dynamic server route:
```ts
import { defineNuxtModule, addServerHandler, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
addServerHandler({
route: '/api/hello/:name',
handler: resolver.resolve('./runtime/server/api/hello/[name].get.ts')
})
}
})
```
#### Injecting Other Assets
If your module should provide other kinds of assets, they can also be injected. Here's a simple example module injecting a stylesheet through Nuxt's `css` array.

View File

@ -42,6 +42,7 @@ You may need to update the config below with a path to your web browser. For mor
"type": "node",
"request": "launch",
"name": "server: nuxt",
"outputCapture": "std",
"program": "${workspaceFolder}/node_modules/nuxi/bin/nuxi.mjs",
"args": [
"dev"
@ -60,6 +61,12 @@ You may need to update the config below with a path to your web browser. For mor
}
```
If you prefer your usual browser extensions, add this to the _chrome_ configuration above:
```json5
"userDataDir": false,
```
### Example JetBrains IDEs Debug Configuration
You can also debug your Nuxt app in JetBrains IDEs such as IntelliJ IDEA, WebStorm, or PhpStorm.

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> = {
@ -38,11 +38,14 @@ type AsyncData<DataT, ErrorT> = {
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
error: Ref<ErrorT | null>
status: Ref<AsyncDataRequestStatus>
}
interface AsyncDataExecuteOptions {
dedupe?: boolean
}
type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
```
## Params
@ -60,14 +63,15 @@ interface AsyncDataExecuteOptions {
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`.
@ -90,8 +94,10 @@ If you have not fetched data on the server (for example, with `server: false`),
## Example
```ts
const { data, pending, error, refresh } = await useFetch('https://api.nuxtjs.dev/mountains',{
pick: ['title']
const route = useRoute()
const { data, pending, error, refresh } = await useFetch(`https://api.nuxtjs.dev/mountains/${route.params.slug}`, {
pick: ['title']
})
```
@ -101,8 +107,8 @@ Using the `query` option, you can add search parameters to your query. This opti
```ts
const param1 = ref('value1')
const { data, pending, error, refresh } = await useFetch('https://api.nuxtjs.dev/mountains',{
query: { param1, param2: 'value2' }
const { data, pending, error, refresh } = await useFetch('https://api.nuxtjs.dev/mountains', {
query: { param1, param2: 'value2' }
})
```

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

@ -13,7 +13,7 @@ Within the template of a Vue component, you can access the route using `$route`.
In the following example, we call an API via [`useFetch`](/docs/api/composables/use-fetch) using a dynamic page parameter - `slug` - as part of the URL.
```html [~/pages/[slug].vue]
```html [~/pages/[slug\\].vue]
<script setup lang="ts">
const route = useRoute()
const { data: mountain } = await useFetch(`https://api.nuxtjs.dev/mountains/${route.params.slug}`)

View File

@ -41,6 +41,4 @@ useSeoMeta({
## Parameters
There are over 100+ parameters.
Full list of [`parameters`](https://github.com/harlan-zw/zhead/blob/main/src/metaFlat.ts)
There are over 100 parameters. See the [full list of parameters in the source code](https://github.com/harlan-zw/zhead/blob/main/src/metaFlat.ts).

View File

@ -0,0 +1,50 @@
---
title: "<NuxtIsland>"
description: "Nuxt provides `<NuxtIsland>` component to render a non-interactive component without any client JS"
---
# `<NuxtIsland>`
Nuxt provide `<NuxtIsland>` to render components only server side.
When rendering an island component, the content of the island component is static, thus no JS is downloaded client-side.
Changing the island component props triggers a refetch of the island component to re-render it again.
::alert{type=warning}
This component is experimental and in order to use it you must enable the `experimental.componentsIsland` option in your `nuxt.config`.
::
::alert{type=info}
Global styles of your application are sent with the response
::
::alert{type=info}
Server only components use `<NuxtIsland>` under the hood
::
## Props
- **name** : Name of the component to render.
- **type**: `string`
- **required**
- **lazy**: Make the component non-blocking.
- **type**: `boolean`
- **default**: `false`
- **props**: Props to send to the component to render.
- **type**: `Record<string, any>`
- **source**: Remote source to call the island to render.
- **type**: `string`
::alert{type=warning}
Remote islands need `experimental.componentsIsland` to be `'local+remote'` in your `nuxt.config`.
::
## `Slots`
Slots can be passed to an island component if declared.
Every slot is interactive since the parent component is the one providing it.
Some slots are reserved to `NuxtIsland` for special cases.
- **fallback**: Specify the content to be rendered before the island loads (if the component is lazy) or if `NuxtIsland` fails to fetch the component.

View File

@ -19,7 +19,7 @@ If you throw an error created with `createError`:
### Example
```vue [pages/movies/[slug].vue]
```vue [pages/movies/[slug\\].vue]
<script setup lang="ts">
const route = useRoute()
const { data } = await useFetch(`/api/movies/${route.params.slug}`)

View File

@ -99,7 +99,7 @@ interface PageMeta {
Validate whether a given route can validly be rendered with this page. Return true if it is valid, or false if not. If another match can't be found, this will mean a 404. You can also directly return an object with `statusCode`/`statusMessage` to respond immediately with an error (other matches will not be checked).
**`scrollTopTop`**
**`scrollToTop`**
- **Type**: `boolean | (to: RouteLocationNormalized, from: RouteLocationNormalized) => boolean`

View File

@ -15,3 +15,7 @@ Option | Default | Description
-------------------------|-----------------|------------------
`rootDir` | `.` | The root directory of the application to generate
`--dotenv` | `.` | Point to another `.env` file to load, **relative** to the root directory.
::alert{type=info}
Read more about [pre-rendering and static hosting](/docs/getting-started/deployment#static-hosting).
::

View File

@ -33,16 +33,16 @@ If your issue concerns Vue 3 or Vite, please try to reproduce it first with the
**Nuxt 3**:
:button-link[Nuxt 3 on StackBlitz]{href="https://stackblitz.com/github/nuxt/starter/tree/v3-stackblitz" blank .mr-2}
:button-link[Nuxt 3 on CodeSandbox]{href="https://codesandbox.io/p/github/nuxt/starter/v3-codesandbox" blank}
:button-link[Nuxt 3 on CodeSandbox]{href="https://codesandbox.io/s/github/nuxt/starter/v3-codesandbox" blank}
**Nuxt Bridge**:
:button-link[Nuxt Bridge on CodeSandbox]{href="https://codesandbox.io/p/github/nuxt/starter/v2-bridge-codesandbox" blank}
:button-link[Nuxt Bridge on CodeSandbox]{href="https://codesandbox.io/s/github/nuxt/starter/v2-bridge-codesandbox" blank}
**Vue 3**:
:button-link[Vue 3 SSR on StackBlitz]{href="https://stackblitz.com/github/nuxt-contrib/vue3-ssr-starter/tree/main?terminal=dev" blank .mr-2}
:button-link[Vue 3 SSR on CodeSandbox]{href="https://codesandbox.io/p/github/nuxt-contrib/vue3-ssr-starter/main" blank .mr-2}
:button-link[Vue 3 SSR on CodeSandbox]{href="https://codesandbox.io/s/github/nuxt-contrib/vue3-ssr-starter/main" blank .mr-2}
:button-link[Vue 3 SSR Template]{href="https://github.com/nuxt-contrib/vue3-ssr-starter/generate" blank}
Once you've reproduced the issue, remove as much code from your reproduction as you can (while still recreating the bug). The time spent making the reproduction as minimal as possible will make a huge difference to whoever sets out to fix the issue.

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

@ -114,7 +114,7 @@ See [layout migration](/docs/migration/pages-and-layouts).
The validate hook in Nuxt 3 only accepts a single argument, the `route`. Just as in Nuxt 2, you can return a boolean value. If you return false and another match can't be found, this will mean a 404. You can also directly return an object with `statusCode`/`statusMessage` to respond immediately with an error (other matches will not be checked).
```diff [pages/users/[id].vue]
```diff [pages/users/[id\\].vue]
- <script>
- export default {
- async validate({ params }) {
@ -135,7 +135,7 @@ The validate hook in Nuxt 3 only accepts a single argument, the `route`. Just as
This is not supported in Nuxt 3. Instead, you can directly use a watcher to trigger refetching data.
```vue [pages/users/[id].vue]
```vue [pages/users/[id\\].vue]
<script setup lang="ts">
const route = useRoute()
const { data, refresh } = await useFetch('/api/user')

View File

@ -14,12 +14,12 @@
"lint:docs": "markdownlint ./docs && case-police 'docs/**/*.md' *.md",
"lint:docs:fix": "markdownlint ./docs --fix && case-police 'docs/**/*.md' *.md --fix",
"lint:knip": "pnpx knip",
"docs:dev": "nuxi dev .website",
"play": "nuxi dev playground",
"play:build": "nuxi build playground",
"play:preview": "nuxi preview playground",
"test": "pnpm test:fixtures && pnpm test:fixtures:payload && pnpm test:fixtures:dev && pnpm test:fixtures:webpack && pnpm test:unit && pnpm typecheck",
"test:fixtures": "nuxi prepare test/fixtures/basic && nuxi prepare test/fixtures/runtime-compiler && vitest run --dir test",
"test:fixtures:payload": "TEST_PAYLOAD=js pnpm test:fixtures",
"test:fixtures:dev": "TEST_ENV=dev pnpm test:fixtures",
"test:fixtures:webpack": "TEST_BUILDER=webpack pnpm test:fixtures",
"test:runtime": "vitest -c vitest.nuxt.config.ts",
@ -33,11 +33,10 @@
"@nuxt/test-utils": "workspace:*",
"@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*",
"nuxi": "workspace:*",
"nuxt": "workspace:*",
"vite": "4.4.7",
"vite": "4.4.9",
"vue": "3.3.4",
"magic-string": "^0.30.1"
"magic-string": "^0.30.3"
},
"devDependencies": {
"@actions/core": "1.10.0",
@ -45,46 +44,46 @@
"@nuxt/webpack-builder": "workspace:*",
"@nuxtjs/eslint-config-typescript": "12.0.0",
"@types/fs-extra": "11.0.1",
"@types/node": "18.17.0",
"@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",
"eslint": "8.45.0",
"eslint-plugin-import": "2.27.5",
"eslint": "8.48.0",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-jsdoc": "41.1.2",
"eslint-plugin-no-only-tests": "3.1.0",
"execa": "7.1.1",
"execa": "7.2.0",
"fs-extra": "11.1.1",
"globby": "13.2.2",
"h3": "1.7.1",
"happy-dom": "10.5.2",
"jiti": "1.19.1",
"h3": "1.8.0",
"happy-dom": "10.11.0",
"jiti": "1.19.3",
"markdownlint-cli": "^0.33.0",
"nitropack": "2.5.2",
"nuxi": "workspace:*",
"nitropack": "2.6.1",
"nuxi": "3.7.0",
"nuxt": "workspace:*",
"nuxt-vitest": "0.10.2",
"ofetch": "1.1.1",
"ofetch": "1.3.3",
"pathe": "1.1.1",
"playwright-core": "1.36.1",
"playwright-core": "1.37.1",
"rimraf": "5.0.1",
"semver": "7.5.4",
"std-env": "3.3.3",
"typescript": "5.1.6",
"ufo": "1.2.0",
"vite": "4.4.7",
"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",
"vue": "3.3.4",
"vue-eslint-parser": "9.3.1",
"vue-router": "4.2.4",
"vue-tsc": "1.8.6"
"vue-tsc": "1.8.8"
},
"packageManager": "pnpm@8.6.10",
"packageManager": "pnpm@8.6.12",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}

View File

@ -1,6 +1,6 @@
{
"name": "@nuxt/kit",
"version": "3.6.5",
"version": "3.7.0",
"repository": "nuxt/nuxt",
"license": "MIT",
"type": "module",
@ -27,15 +27,16 @@
"globby": "^13.2.2",
"hash-sum": "^2.0.0",
"ignore": "^5.2.4",
"jiti": "^1.19.1",
"jiti": "^1.19.3",
"knitwork": "^1.0.0",
"mlly": "^1.4.0",
"mlly": "^1.4.1",
"pathe": "^1.1.1",
"pkg-types": "^1.0.3",
"scule": "^1.0.0",
"semver": "^7.5.4",
"ufo": "^1.3.0",
"unctx": "^2.3.1",
"unimport": "^3.1.0",
"unimport": "^3.2.0",
"untyped": "^1.4.0"
},
"devDependencies": {
@ -43,9 +44,9 @@
"@types/lodash-es": "4.17.8",
"@types/semver": "7.5.0",
"lodash-es": "4.17.21",
"nitropack": "2.5.2",
"nitropack": "2.6.1",
"unbuild": "latest",
"vite": "4.4.7",
"vite": "4.4.9",
"vitest": "0.33.0",
"webpack": "5.88.2"
},

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'

View File

@ -60,15 +60,19 @@ function getRequireCacheItem (id: string) {
}
}
export function getModulePaths (paths?: string[] | string) {
return ([] as Array<string | undefined>).concat(
global.__NUXT_PREPATHS__,
paths || [],
process.cwd(),
global.__NUXT_PATHS__
).filter(Boolean) as string[]
}
/** @deprecated Do not use CJS utils */
export function resolveModule (id: string, opts: ResolveModuleOptions = {}) {
return normalize(_require.resolve(id, {
paths: ([] as Array<string | undefined>).concat(
global.__NUXT_PREPATHS__,
opts.paths || [],
process.cwd(),
global.__NUXT_PATHS__
).filter(Boolean) as string[]
paths: getModulePaths(opts.paths)
}))
}

View File

@ -1,42 +0,0 @@
export function sequence<T, R> (
tasks: T[],
fn: (task: T) => R
) {
return tasks.reduce(
(promise, task): any => promise.then(() => fn(task)),
Promise.resolve()
)
}
export function parallel<T, R> (
tasks: T[],
fn: (task: T) => R
) {
return Promise.all(tasks.map(fn))
}
export function chainFn<Fn> (base: Fn, fn: Fn): Fn {
if (typeof fn !== 'function') {
return base
}
return function (this: any, ...args: any[]) {
if (typeof base !== 'function') {
return fn.apply(this, args)
}
let baseResult = base.apply(this, args)
// Allow function to mutate the first argument instead of returning the result
if (baseResult === undefined) {
[baseResult] = args
}
const fnResult = fn.call(
this,
baseResult,
...Array.prototype.slice.call(args, 1)
)
// Return mutated argument if no result was returned
if (fnResult === undefined) {
return baseResult
}
return fnResult
} as unknown as Fn
}

View File

@ -6,6 +6,7 @@ import { genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
import type { NuxtTemplate } from '@nuxt/schema'
/** @deprecated */
// TODO: Remove support for compiling ejs templates in v4
export async function compileTemplate (template: NuxtTemplate, ctx: any) {
const data = { ...ctx, options: template.options }
if (template.src) {

View File

@ -25,6 +25,10 @@ export async function installModule (moduleToInstall: string | NuxtModule, inlin
if (typeof moduleToInstall === 'string') {
nuxt.options.build.transpile.push(normalizeModuleTranspilePath(moduleToInstall))
const directory = getDirectory(moduleToInstall)
if (directory !== moduleToInstall) {
nuxt.options.modulesDir.push(getDirectory(moduleToInstall))
}
}
nuxt.options._installedModules = nuxt.options._installedModules || []
@ -37,15 +41,19 @@ export async function installModule (moduleToInstall: string | NuxtModule, inlin
// --- Internal ---
export const normalizeModuleTranspilePath = (p: string) => {
function getDirectory (p: string) {
try {
// we need to target directories instead of module file paths themselves
// /home/user/project/node_modules/module/index.js -> /home/user/project/node_modules/module
p = isAbsolute(p) && lstatSync(p).isFile() ? dirname(p) : p
return isAbsolute(p) && lstatSync(p).isFile() ? dirname(p) : p
} catch (e) {
// maybe the path is absolute but does not exist, allow this to bubble up
}
return p.split('node_modules/').pop() as string
return p
}
export const normalizeModuleTranspilePath = (p: string) => {
return getDirectory(p).split('node_modules/').pop() as string
}
export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, nuxt: Nuxt = useNuxt()) {

View File

@ -1,8 +1,15 @@
import { existsSync } from 'node:fs'
import { basename, parse, resolve } from 'pathe'
import { existsSync, promises as fsp } from 'node:fs'
import { basename, isAbsolute, join, parse, relative, resolve } from 'pathe'
import hash from 'hash-sum'
import type { NuxtTemplate, ResolvedNuxtTemplate } from '@nuxt/schema'
import type { Nuxt, NuxtTemplate, ResolvedNuxtTemplate, TSReference } from '@nuxt/schema'
import { withTrailingSlash } from 'ufo'
import { defu } from 'defu'
import type { TSConfig } from 'pkg-types'
import { readPackageJSON } from 'pkg-types'
import { tryResolveModule } from './internal/esm'
import { tryUseNuxt, useNuxt } from './context'
import { getModulePaths } from './internal/cjs'
/**
* Renders given template using lodash template during build into the project buildDir
@ -101,3 +108,161 @@ export function normalizeTemplate (template: NuxtTemplate<any> | string): Resolv
export async function updateTemplates (options?: { filter?: (template: ResolvedNuxtTemplate<any>) => boolean }) {
return await tryUseNuxt()?.hooks.callHook('builder:generateApp', options)
}
export async function writeTypes (nuxt: Nuxt) {
const modulePaths = getModulePaths(nuxt.options.modulesDir)
const rootDirWithSlash = withTrailingSlash(nuxt.options.rootDir)
const tsConfig: TSConfig = defu(nuxt.options.typescript?.tsConfig, {
compilerOptions: {
forceConsistentCasingInFileNames: true,
jsx: 'preserve',
target: 'ESNext',
module: 'ESNext',
moduleResolution: nuxt.options.experimental?.typescriptBundlerResolution ? 'Bundler' : 'Node',
skipLibCheck: true,
isolatedModules: true,
useDefineForClassFields: true,
strict: nuxt.options.typescript?.strict ?? true,
allowJs: true,
noEmit: true,
resolveJsonModule: true,
allowSyntheticDefaultImports: true,
paths: {}
},
include: [
'./nuxt.d.ts',
join(relativeWithDot(nuxt.options.buildDir, nuxt.options.rootDir), '**/*'),
...nuxt.options.srcDir !== nuxt.options.rootDir ? [join(relative(nuxt.options.buildDir, nuxt.options.srcDir), '**/*')] : [],
...nuxt.options._layers.map(layer => layer.config.srcDir ?? layer.cwd)
.filter(srcOrCwd => !srcOrCwd.startsWith(rootDirWithSlash) || srcOrCwd.includes('node_modules'))
.map(srcOrCwd => join(relative(nuxt.options.buildDir, srcOrCwd), '**/*')),
...nuxt.options.typescript.includeWorkspace && nuxt.options.workspaceDir !== nuxt.options.rootDir ? [join(relative(nuxt.options.buildDir, nuxt.options.workspaceDir), '**/*')] : []
],
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'))
]
} satisfies TSConfig)
const aliases: Record<string, string> = {
...nuxt.options.alias,
'#build': nuxt.options.buildDir
}
// Exclude bridge alias types to support Volar
const excludedAlias = [/^@vue\/.*$/]
const basePath = tsConfig.compilerOptions!.baseUrl ? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl) : nuxt.options.buildDir
tsConfig.compilerOptions = tsConfig.compilerOptions || {}
tsConfig.include = tsConfig.include || []
for (const alias in aliases) {
if (excludedAlias.some(re => re.test(alias))) {
continue
}
let absolutePath = resolve(basePath, aliases[alias])
let stats = await fsp.stat(absolutePath).catch(() => null /* file does not exist */)
if (!stats) {
const resolvedModule = await tryResolveModule(aliases[alias], nuxt.options.modulesDir)
if (resolvedModule) {
absolutePath = resolvedModule
stats = await fsp.stat(resolvedModule).catch(() => null)
}
}
const relativePath = relativeWithDot(nuxt.options.buildDir, absolutePath)
if (stats?.isDirectory()) {
tsConfig.compilerOptions.paths[alias] = [relativePath]
tsConfig.compilerOptions.paths[`${alias}/*`] = [`${relativePath}/*`]
if (!absolutePath.startsWith(rootDirWithSlash)) {
tsConfig.include.push(relativePath)
}
} else {
const path = stats?.isFile()
// remove extension
? relativePath.replace(/(?<=\w)\.\w+$/g, '')
// non-existent file probably shouldn't be resolved
: aliases[alias]
tsConfig.compilerOptions.paths[alias] = [path]
if (!absolutePath.startsWith(rootDirWithSlash)) {
tsConfig.include.push(path)
}
}
}
const references: TSReference[] = await Promise.all([
...nuxt.options.modules,
...nuxt.options._modules
]
.filter(f => typeof f === 'string')
.map(async id => ({ types: (await readPackageJSON(id, { url: modulePaths }).catch(() => null))?.name || id })))
if (nuxt.options.experimental?.reactivityTransform) {
references.push({ types: 'vue/macros-global' })
}
const declarations: string[] = []
await nuxt.callHook('prepare:types', { references, declarations, tsConfig })
for (const alias in tsConfig.compilerOptions!.paths) {
const paths = tsConfig.compilerOptions!.paths[alias]
tsConfig.compilerOptions!.paths[alias] = await Promise.all(paths.map(async (path: string) => {
if (!isAbsolute(path)) { return path }
const stats = await fsp.stat(path).catch(() => null /* file does not exist */)
return relativeWithDot(nuxt.options.buildDir, stats?.isFile() ? path.replace(/(?<=\w)\.\w+$/g, '') /* remove extension */ : path)
}))
}
tsConfig.include = [...new Set(tsConfig.include.map(p => isAbsolute(p) ? relativeWithDot(nuxt.options.buildDir, p) : p))]
tsConfig.exclude = [...new Set(tsConfig.exclude!.map(p => isAbsolute(p) ? relativeWithDot(nuxt.options.buildDir, p) : p))]
const declaration = [
...references.map((ref) => {
if ('path' in ref && isAbsolute(ref.path)) {
ref.path = relative(nuxt.options.buildDir, ref.path)
}
return `/// <reference ${renderAttrs(ref)} />`
}),
...declarations,
'',
'export {}',
''
].join('\n')
async function writeFile () {
const GeneratedBy = '// Generated by nuxi'
const tsConfigPath = resolve(nuxt.options.buildDir, 'tsconfig.json')
await fsp.mkdir(nuxt.options.buildDir, { recursive: true })
await fsp.writeFile(tsConfigPath, GeneratedBy + '\n' + JSON.stringify(tsConfig, null, 2))
const declarationPath = resolve(nuxt.options.buildDir, 'nuxt.d.ts')
await fsp.writeFile(declarationPath, GeneratedBy + '\n' + declaration)
}
// This is needed for Nuxt 2 which clears the build directory again before building
// https://github.com/nuxt/nuxt/blob/2.x/packages/builder/src/builder.js#L144
// @ts-expect-error TODO: Nuxt 2 hook
nuxt.hook('builder:prepared', writeFile)
await writeFile()
}
function renderAttrs (obj: Record<string, string>) {
return Object.entries(obj).map(e => renderAttr(e[0], e[1])).join(' ')
}
function renderAttr (key: string, value: string) {
return value ? `${key}="${value}"` : ''
}
function relativeWithDot (from: string, to: string) {
return relative(from, to).replace(/^([^.])/, './$1') || '.'
}

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,59 +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.0",
"execa": "7.1.1",
"flat": "5.0.2",
"giget": "1.1.2",
"h3": "1.7.1",
"jiti": "1.19.1",
"listhen": "1.1.2",
"mlly": "1.4.0",
"mri": "1.2.0",
"ohash": "1.1.2",
"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,63 +0,0 @@
import { relative, resolve } from 'pathe'
import { consola } from 'consola'
import { writeTypes } from '../utils/prepare'
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 } = 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 || {})
}
})
// Use ? for backward compatibility for Nuxt <= RC.10
const nitro = useNitro?.()
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,189 +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'
import { showBanner, showVersions } from '../utils/banner'
import { writeTypes } from '../utils/prepare'
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 } = 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(() => {})
if (reason) {
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()
distWatcher = chokidar.watch(resolve(currentNuxt.options.buildDir, 'dist'), { ignoreInitial: true, depth: 0 })
distWatcher.on('unlinkDir', () => {
dLoad(true, '.nuxt/dist directory has been removed')
})
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)
])
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,33 +0,0 @@
import { relative, resolve } from 'pathe'
import { consola } from 'consola'
import { clearBuildDir } from '../utils/fs'
import { loadKit } from '../utils/kit'
import { writeTypes } from '../utils/prepare'
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 } = 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,42 +0,0 @@
import { execa } from 'execa'
import { resolve } from 'pathe'
import { tryResolveModule } from '../utils/esm'
import { loadKit } from '../utils/kit'
import { writeTypes } from '../utils/prepare'
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 } = 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,35 +0,0 @@
import { createRequire } from 'node:module'
import { dirname, normalize } from 'pathe'
export 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 }
}
export function getNearestPackage (id: string, paths?: string | string[]) {
while (dirname(id) !== id) {
try { return requireModule(id + '/package.json', paths) } catch {}
id = dirname(id)
}
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)
}

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