mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-30 01:17:16 +00:00
Merge branch 'main' into docs/kit
This commit is contained in:
commit
76769924ca
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -112,7 +112,6 @@ 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
|
||||
@ -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
|
||||
|
12
.website/.gitignore
vendored
Executable file
12
.website/.gitignore
vendored
Executable 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
35
.website/README.md
Executable 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
14
.website/app.config.ts
Normal 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
20
.website/nuxt.config.ts
Executable 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
14
.website/package.json
Executable 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
3
.website/tsconfig.json
Executable file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
@ -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.
|
||||
|
@ -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) => {
|
||||
|
@ -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: {
|
||||
|
@ -111,7 +111,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}`)
|
||||
|
@ -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.
|
||||
|
||||
|
@ -67,7 +67,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 />
|
||||
|
@ -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>
|
||||
|
@ -151,7 +151,7 @@ Server routes can use dynamic parameters within brackets in the file name like `
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts [server/api/hello/[name].ts]
|
||||
```ts [server/api/hello/[name\\].ts]
|
||||
export default defineEventHandler((event) => `Hello, ${event.context.params.name}!`)
|
||||
```
|
||||
|
||||
@ -181,11 +181,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 +221,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 +240,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 +284,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()
|
||||
|
@ -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}`)
|
||||
|
50
docs/3.api/2.components/8.nuxt-island.md
Normal file
50
docs/3.api/2.components/8.nuxt-island.md
Normal 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.
|
@ -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}`)
|
||||
|
@ -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/1.getting-started/10.deployment.md#static-hosting).
|
||||
::
|
||||
|
@ -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')
|
||||
|
@ -14,6 +14,7 @@
|
||||
"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",
|
||||
@ -35,7 +36,7 @@
|
||||
"@nuxt/webpack-builder": "workspace:*",
|
||||
"nuxi": "workspace:*",
|
||||
"nuxt": "workspace:*",
|
||||
"vite": "4.4.7",
|
||||
"vite": "4.4.8",
|
||||
"vue": "3.3.4",
|
||||
"magic-string": "^0.30.2"
|
||||
},
|
||||
@ -61,7 +62,7 @@
|
||||
"fs-extra": "11.1.1",
|
||||
"globby": "13.2.2",
|
||||
"h3": "1.7.1",
|
||||
"happy-dom": "10.5.2",
|
||||
"happy-dom": "10.7.0",
|
||||
"jiti": "1.19.1",
|
||||
"markdownlint-cli": "^0.33.0",
|
||||
"nitropack": "2.5.2",
|
||||
@ -76,7 +77,7 @@
|
||||
"std-env": "3.3.3",
|
||||
"typescript": "5.1.6",
|
||||
"ufo": "1.2.0",
|
||||
"vite": "4.4.7",
|
||||
"vite": "4.4.8",
|
||||
"vitest": "0.33.0",
|
||||
"vitest-environment-nuxt": "0.10.2",
|
||||
"vue": "3.3.4",
|
||||
|
@ -36,7 +36,7 @@
|
||||
"semver": "^7.5.4",
|
||||
"ufo": "^1.2.0",
|
||||
"unctx": "^2.3.1",
|
||||
"unimport": "^3.1.0",
|
||||
"unimport": "^3.1.3",
|
||||
"untyped": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -46,7 +46,7 @@
|
||||
"lodash-es": "4.17.21",
|
||||
"nitropack": "2.5.2",
|
||||
"unbuild": "latest",
|
||||
"vite": "4.4.7",
|
||||
"vite": "4.4.8",
|
||||
"vitest": "0.33.0",
|
||||
"webpack": "5.88.2"
|
||||
},
|
||||
|
@ -121,6 +121,8 @@ export async function writeTypes (nuxt: Nuxt) {
|
||||
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,
|
||||
|
@ -32,13 +32,13 @@
|
||||
"consola": "3.2.3",
|
||||
"deep-object-diff": "1.1.9",
|
||||
"defu": "6.1.2",
|
||||
"destr": "2.0.0",
|
||||
"destr": "2.0.1",
|
||||
"execa": "7.2.0",
|
||||
"flat": "5.0.2",
|
||||
"giget": "1.1.2",
|
||||
"h3": "1.7.1",
|
||||
"jiti": "1.19.1",
|
||||
"listhen": "1.1.2",
|
||||
"listhen": "1.2.2",
|
||||
"mlly": "1.4.0",
|
||||
"mri": "1.2.0",
|
||||
"ohash": "1.1.2",
|
||||
|
@ -58,15 +58,15 @@
|
||||
"@nuxt/telemetry": "^2.3.2",
|
||||
"@nuxt/ui-templates": "^1.3.1",
|
||||
"@nuxt/vite-builder": "workspace:../vite",
|
||||
"@unhead/ssr": "^1.1.35",
|
||||
"@unhead/vue": "^1.1.35",
|
||||
"@unhead/ssr": "^1.2.2",
|
||||
"@unhead/vue": "^1.2.2",
|
||||
"@vue/shared": "^3.3.4",
|
||||
"acorn": "8.10.0",
|
||||
"c12": "^1.4.2",
|
||||
"chokidar": "^3.5.3",
|
||||
"cookie-es": "^1.0.0",
|
||||
"defu": "^6.1.2",
|
||||
"destr": "^2.0.0",
|
||||
"destr": "^2.0.1",
|
||||
"devalue": "^4.3.2",
|
||||
"esbuild": "^0.18.17",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
@ -90,13 +90,13 @@
|
||||
"pkg-types": "^1.0.3",
|
||||
"prompts": "^2.4.2",
|
||||
"scule": "^1.0.0",
|
||||
"strip-literal": "^1.0.1",
|
||||
"strip-literal": "^1.3.0",
|
||||
"ufo": "^1.2.0",
|
||||
"ultrahtml": "^1.3.0",
|
||||
"uncrypto": "^0.1.3",
|
||||
"unctx": "^2.3.1",
|
||||
"unenv": "^1.5.2",
|
||||
"unimport": "^3.1.0",
|
||||
"unenv": "^1.6.1",
|
||||
"unimport": "^3.1.3",
|
||||
"unplugin": "^1.4.0",
|
||||
"unplugin-vue-router": "^0.6.4",
|
||||
"untyped": "^1.4.0",
|
||||
@ -112,7 +112,7 @@
|
||||
"@types/prompts": "2.4.4",
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"unbuild": "latest",
|
||||
"vite": "4.4.7",
|
||||
"vite": "4.4.8",
|
||||
"vitest": "0.33.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -18,7 +18,7 @@ export default defineComponent({
|
||||
if (!component) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: `Island component not found: ${JSON.stringify(component)}`
|
||||
statusMessage: `Island component not found: ${props.context.name}`
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -53,8 +53,8 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
|
||||
}
|
||||
|
||||
if (opts.watch) {
|
||||
watch(cookie, (newVal, oldVal) => {
|
||||
if (watchPaused || isEqual(newVal, oldVal)) { return }
|
||||
watch(cookie, () => {
|
||||
if (watchPaused) { return }
|
||||
callback()
|
||||
},
|
||||
{ deep: opts.watch !== 'shallow' })
|
||||
|
@ -273,6 +273,12 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
// Init nitro
|
||||
const nitro = await createNitro(nitroConfig)
|
||||
|
||||
// Set prerender-only options
|
||||
nitro.options._config.storage ||= {}
|
||||
nitro.options._config.storage['internal:nuxt:prerender'] = { driver: 'memory' }
|
||||
nitro.options._config.storage['internal:nuxt:prerender:island'] = { driver: 'lruCache', max: 1000 }
|
||||
nitro.options._config.storage['internal:nuxt:prerender:payload'] = { driver: 'lruCache', max: 1000 }
|
||||
|
||||
// Expose nitro to modules and kit
|
||||
nuxt._nitro = nitro
|
||||
await nuxt.callHook('nitro:init', nitro)
|
||||
|
@ -17,7 +17,7 @@ import { renderToString as _renderToString } from 'vue/server-renderer'
|
||||
import { hash } from 'ohash'
|
||||
import { renderSSRHead } from '@unhead/ssr'
|
||||
|
||||
import { defineRenderHandler, getRouteRules, useRuntimeConfig } from '#internal/nitro'
|
||||
import { defineRenderHandler, getRouteRules, useRuntimeConfig, useStorage } from '#internal/nitro'
|
||||
import { useNitroApp } from '#internal/nitro/app'
|
||||
|
||||
import type { Link, Script } from '@unhead/vue'
|
||||
@ -155,9 +155,18 @@ const getSPARenderer = lazyCachedFunction(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
const payloadCache = process.env.prerender ? useStorage('internal:nuxt:prerender:payload') : null
|
||||
const islandCache = process.env.prerender ? useStorage('internal:nuxt:prerender:island') : null
|
||||
const islandPropCache = process.env.prerender ? useStorage('internal:nuxt:prerender:island-props') : null
|
||||
|
||||
async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
|
||||
// TODO: Strict validation for url
|
||||
const url = event.node.req.url?.substring('/__nuxt_island'.length + 1) || ''
|
||||
let url = event.node.req.url || ''
|
||||
if (process.env.prerender && event.node.req.url && await islandPropCache!.hasItem(event.node.req.url)) {
|
||||
// rehydrate props from cache so we can rerender island if cache does not have it any more
|
||||
url = await islandPropCache!.getItem(event.node.req.url) as string
|
||||
}
|
||||
url = url.substring('/__nuxt_island'.length + 1) || ''
|
||||
const [componentName, hashId] = url.split('?')[0].split('_')
|
||||
|
||||
// TODO: Validate context
|
||||
@ -175,8 +184,6 @@ async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
|
||||
return ctx
|
||||
}
|
||||
|
||||
const PAYLOAD_CACHE = (process.env.NUXT_PAYLOAD_EXTRACTION && process.env.prerender) ? new Map() : null // TODO: Use LRU cache
|
||||
const ISLAND_CACHE = (process.env.NUXT_COMPONENT_ISLANDS && process.env.prerender) ? new Map() : null // TODO: Use LRU cache
|
||||
const PAYLOAD_URL_RE = process.env.NUXT_JSON_PAYLOADS ? /\/_payload(\.[a-zA-Z0-9]+)?.json(\?.*)?$/ : /\/_payload(\.[a-zA-Z0-9]+)?.js(\?.*)?$/
|
||||
const ROOT_NODE_REGEX = new RegExp(`^<${appRootTag} id="${appRootId}">([\\s\\S]*)</${appRootTag}>$`)
|
||||
|
||||
@ -206,8 +213,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
? await getIslandContext(event)
|
||||
: undefined
|
||||
|
||||
if (process.env.prerender && islandContext && ISLAND_CACHE!.has(event.node.req.url)) {
|
||||
return ISLAND_CACHE!.get(event.node.req.url)
|
||||
if (process.env.prerender && islandContext && event.node.req.url && await islandCache!.hasItem(event.node.req.url)) {
|
||||
return islandCache!.getItem(event.node.req.url) as Promise<Partial<RenderResponse>>
|
||||
}
|
||||
|
||||
// Request url
|
||||
@ -218,8 +225,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
if (isRenderingPayload) {
|
||||
url = url.substring(0, url.lastIndexOf('/')) || '/'
|
||||
event.node.req.url = url
|
||||
if (process.env.prerender && PAYLOAD_CACHE!.has(url)) {
|
||||
return PAYLOAD_CACHE!.get(url)
|
||||
if (process.env.prerender && await payloadCache!.hasItem(url)) {
|
||||
return payloadCache!.getItem(url) as Promise<Partial<RenderResponse>>
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,7 +292,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
if (isRenderingPayload) {
|
||||
const response = renderPayloadResponse(ssrContext)
|
||||
if (process.env.prerender) {
|
||||
PAYLOAD_CACHE!.set(url, response)
|
||||
await payloadCache!.setItem(url, response)
|
||||
}
|
||||
return response
|
||||
}
|
||||
@ -294,7 +301,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
// Hint nitro to prerender payload for this route
|
||||
appendResponseHeader(event, 'x-nitro-prerender', joinURL(url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js'))
|
||||
// Use same ssr context to generate payload for this route
|
||||
PAYLOAD_CACHE!.set(withoutTrailingSlash(url), renderPayloadResponse(ssrContext))
|
||||
await payloadCache!.setItem(withoutTrailingSlash(url), renderPayloadResponse(ssrContext))
|
||||
}
|
||||
|
||||
if (process.env.NUXT_INLINE_STYLES && !islandContext) {
|
||||
@ -422,7 +429,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
}
|
||||
} satisfies RenderResponse
|
||||
if (process.env.prerender) {
|
||||
ISLAND_CACHE!.set(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, response)
|
||||
await islandCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, response)
|
||||
await islandPropCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, event.node.req.url!)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
@ -54,6 +54,10 @@ export default defineNuxtModule({
|
||||
addPlugin({ src: resolve(runtimeDir, 'plugins/vueuse-head-polyfill') })
|
||||
}
|
||||
|
||||
if (nuxt.options.experimental.headCapoPlugin) {
|
||||
addPlugin({ src: resolve(runtimeDir, 'plugins/capo') })
|
||||
}
|
||||
|
||||
// Add library-specific plugin
|
||||
addPlugin({ src: resolve(runtimeDir, 'plugins/unhead') })
|
||||
}
|
||||
|
9
packages/nuxt/src/head/runtime/plugins/capo.ts
Normal file
9
packages/nuxt/src/head/runtime/plugins/capo.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { CapoPlugin } from '@unhead/vue'
|
||||
import { defineNuxtPlugin } from '#app/nuxt'
|
||||
|
||||
export default defineNuxtPlugin({
|
||||
name: 'nuxt:head:capo',
|
||||
setup (nuxtApp) {
|
||||
nuxtApp.vueApp._context.provides.usehead.use(CapoPlugin({ track: true }))
|
||||
}
|
||||
})
|
@ -30,17 +30,17 @@
|
||||
"@types/file-loader": "5.0.1",
|
||||
"@types/pug": "2.0.6",
|
||||
"@types/sass-loader": "8.0.5",
|
||||
"@unhead/schema": "1.1.35",
|
||||
"@unhead/schema": "1.2.2",
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "3.0.1",
|
||||
"@vue/compiler-core": "3.3.4",
|
||||
"esbuild-loader": "3.0.1",
|
||||
"esbuild-loader": "3.1.0",
|
||||
"h3": "1.7.1",
|
||||
"ignore": "5.2.4",
|
||||
"nitropack": "2.5.2",
|
||||
"unbuild": "latest",
|
||||
"unctx": "2.3.1",
|
||||
"vite": "4.4.7",
|
||||
"vite": "4.4.8",
|
||||
"vue": "3.3.4",
|
||||
"vue-bundle-renderer": "2.0.0",
|
||||
"vue-loader": "17.2.2",
|
||||
@ -57,7 +57,7 @@
|
||||
"postcss-import-resolver": "^2.0.0",
|
||||
"std-env": "^3.3.3",
|
||||
"ufo": "^1.2.0",
|
||||
"unimport": "^3.1.0",
|
||||
"unimport": "^3.1.3",
|
||||
"untyped": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -19,7 +19,7 @@ export default defineUntypedSchema({
|
||||
*/
|
||||
reactivityTransform: false,
|
||||
|
||||
// TODO: Remove in v3.6 when nitro has support for mocking traced dependencies
|
||||
// TODO: Remove in v3.8 when nitro has support for mocking traced dependencies
|
||||
// https://github.com/unjs/nitro/issues/1118
|
||||
/**
|
||||
* Externalize `vue`, `@vue/*` and `vue-router` when building.
|
||||
@ -207,6 +207,13 @@ export default defineUntypedSchema({
|
||||
* @see https://github.com/parcel-bundler/watcher
|
||||
* @type {'chokidar' | 'parcel' | 'chokidar-granular'}
|
||||
*/
|
||||
watcher: 'chokidar-granular'
|
||||
watcher: 'chokidar-granular',
|
||||
|
||||
/**
|
||||
* Add the capo.js head plugin in order to render tags in of the head in a more performant way.
|
||||
*
|
||||
* @see https://rviscomi.github.io/capo.js/user/rules/
|
||||
*/
|
||||
headCapoPlugin: false
|
||||
}
|
||||
})
|
||||
|
@ -54,10 +54,10 @@
|
||||
"postcss-url": "^10.1.3",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"std-env": "^3.3.3",
|
||||
"strip-literal": "^1.0.1",
|
||||
"strip-literal": "^1.3.0",
|
||||
"ufo": "^1.2.0",
|
||||
"unplugin": "^1.4.0",
|
||||
"vite": "^4.4.7",
|
||||
"vite": "^4.4.8",
|
||||
"vite-node": "^0.33.0",
|
||||
"vite-plugin-checker": "^0.6.1",
|
||||
"vue-bundle-renderer": "^2.0.0"
|
||||
|
@ -26,7 +26,7 @@
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"cssnano": "^6.0.1",
|
||||
"defu": "^6.1.2",
|
||||
"esbuild-loader": "^3.0.1",
|
||||
"esbuild-loader": "^3.1.0",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
|
2211
pnpm-lock.yaml
2211
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -2,3 +2,4 @@ packages:
|
||||
- "packages/**"
|
||||
- "playground"
|
||||
- "test/fixtures/*"
|
||||
- ".website"
|
||||
|
@ -35,7 +35,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"64.4k"')
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2330k"')
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2346k"')
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
@ -95,7 +95,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"370k"')
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"591k"')
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"608k"')
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
|
8
test/fixtures/basic-types/nuxt.config.ts
vendored
8
test/fixtures/basic-types/nuxt.config.ts
vendored
@ -28,14 +28,6 @@ export default defineNuxtConfig({
|
||||
}
|
||||
},
|
||||
modules: [
|
||||
function (_, nuxt) {
|
||||
// TODO: remove in v3.7
|
||||
if (process.env.TS_BASE_URL === 'without-base-url') {
|
||||
nuxt.hook('prepare:types', ({ tsConfig }) => {
|
||||
delete tsConfig.compilerOptions!.baseUrl
|
||||
})
|
||||
}
|
||||
},
|
||||
function () {
|
||||
addTypeTemplate({
|
||||
filename: 'test.d.ts',
|
||||
|
3
test/fixtures/basic/nuxt.config.ts
vendored
3
test/fixtures/basic/nuxt.config.ts
vendored
@ -187,7 +187,8 @@ export default defineNuxtConfig({
|
||||
componentIslands: true,
|
||||
reactivityTransform: true,
|
||||
treeshakeClientOnly: true,
|
||||
payloadExtraction: true
|
||||
payloadExtraction: true,
|
||||
headCapoPlugin: true
|
||||
},
|
||||
appConfig: {
|
||||
fromNuxtConfig: true,
|
||||
|
Loading…
Reference in New Issue
Block a user