Merge branch 'main' into docs/new-structure

This commit is contained in:
Sébastien Chopin 2023-04-12 12:26:47 +02:00
commit 1fc0d09721
174 changed files with 2550 additions and 1617 deletions

View File

@ -12,6 +12,12 @@
"plugin:import/typescript"
],
"rules": {
"sort-imports": [
"error",
{
"ignoreDeclarationSort": true
}
],
"unicorn/prefer-node-protocol": "error",
"no-console": "off",
"vue/multi-word-component-names": "off",
@ -28,6 +34,10 @@
"error",
{
"pathGroups": [
{
"pattern": "@nuxt/test-utils/experimental",
"group": "external"
},
{
"pattern": "@nuxt/test-utils",
"group": "external"

View File

@ -24,7 +24,7 @@ jobs:
run: pnpm install
- name: Dedupe dependencies
if: ${{ contains(github.ref_name, 'renovate') }}
if: ${{ contains(github.head_ref, 'renovate') }}
run: pnpm dedupe
- name: Build (stub)

View File

@ -104,6 +104,9 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Build (stub)
run: pnpm build:stub
- name: Lint
run: pnpm lint
@ -118,6 +121,7 @@ jobs:
os: [ubuntu-latest, windows-latest]
env: ['dev', 'built']
builder: ['vite', 'webpack']
payload: ['json', 'js']
node: [16]
exclude:
- env: 'dev'
@ -177,6 +181,7 @@ jobs:
env:
TEST_ENV: ${{ matrix.env }}
TEST_BUILDER: ${{ matrix.builder }}
TEST_PAYLOAD: ${{ matrix.payload }}
build-release:
if: |

View File

@ -54,7 +54,7 @@ When you are using the built-in Composition API composables provided by Vue and
During a component lifecycle, Vue tracks the temporary instance of the current component (and similarly, Nuxt tracks a temporary instance of `nuxtApp`) via a global variable, and then unsets it in same tick. This is essential when server rendering, both to avoid cross-request state pollution (leaking a shared reference between two users) and to avoid leakage between different components.
That means that (with very few exceptions) you cannot use them outside a Nuxt plugin, Nuxt route middleware or Vue setup function. On top of that, you must use them synchronously - that is, you cannot use `await` before calling a composable, except within `<script setup>` blocks, in `defineNuxtPlugin` or in `defineNuxtRouteMiddleware`, where we perform a transform to keep the synchronous context even after the `await`.
That means that (with very few exceptions) you cannot use them outside a Nuxt plugin, Nuxt route middleware or Vue setup function. On top of that, you must use them synchronously - that is, you cannot use `await` before calling a composable, except within `<script setup>` blocks, within the setup function of a component declared with `defineNuxtComponent`, in `defineNuxtPlugin` or in `defineNuxtRouteMiddleware`, where we perform a transform to keep the synchronous context even after the `await`.
If you get an error message like `Nuxt instance is unavailable` then it probably means you are calling a Nuxt composable in the wrong place in the Vue or Nuxt lifecycle.

View File

@ -100,6 +100,7 @@ Nuxt 3 includes route rules and hybrid rendering support. Using route rules you
- `swr` - Add cache headers to the server response and cache it in the server or reverse proxy for a configurable TTL. The `node-server` preset of Nitro is able to cache the full response. For Netlify and Vercel, the response is also added to the CDN layer.
- `static` - The behavior is the same as `swr` except that there is no TTL; the response is cached until the next deployment. On Netlify and Vercel, it enables full incremental static generation.
- `prerender` - Prerenders routes at build time and includes them in your build as static assets
- `experimentalNoScripts` - Disables rendering of Nuxt scripts and JS resource hints for sections of your site.
**Examples:**

View File

@ -8,7 +8,7 @@ Nuxt 3 is fully typed and provides helpful shortcuts to ensure you have access t
## Type-checking
By default, Nuxt doesn't check types when you run `nuxi dev` or `nuxi build`, for performance reasons. However, you can enable type-checking at build or development time by installing `@types/node`, `vue-tsc` and `typescript` as devDependencies and either enabling [the `typescript.typeCheck` option in your `nuxt.config` file](/docs/api/configuration/nuxt-config#typescript) or [manually checking your types with nuxi](/docs/api/commands/typecheck).
By default, Nuxt doesn't check types when you run `nuxi dev` or `nuxi build`, for performance reasons. However, you can enable type-checking at build or development time by installing `vue-tsc` and `typescript` as devDependencies and either enabling [the `typescript.typeCheck` option in your `nuxt.config` file](/docs/api/configuration/nuxt-config#typescript) or [manually checking your types with nuxi](/docs/api/commands/typecheck).
```bash
yarn nuxi typecheck

View File

@ -408,3 +408,35 @@ When `<NuxtPage />` is used in `app.vue`, transition-props can be passed directl
::alert{type="warning"}
Remember, this page transition cannot be overridden with `definePageMeta` on individual pages.
::
## View Transitions API (experimental)
Nuxt ships with an experimental implementation of the [**View Transitions API**](https://developer.chrome.com/docs/web-platform/view-transitions/) (see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)). This is an exciting new way to implement native browser transitions which (among other things) have the ability to transition between unrelated elements on different pages.
The Nuxt integration is under active development, but can be enabled with the `experimental.viewTransition` option in your configuration file:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
viewTransition: true
}
})
```
If you are also using Vue transitions like `pageTransition` and `layoutTransition` (see above) to achieve the same result as the new View Transitions API, then you may wish to _disable_ Vue transitions if the user's browser supports the newer, native web API. You can do this by creating `~/middleware/disable-vue-transitions.global.ts` with the following contents:
```js
export default defineNuxtRouteMiddleware(to => {
if (!document.startViewTransition) { return }
// Disable built-in Vue transitions
to.meta.pageTransition = false
to.meta.layoutTransition = false
})
```
### Known issues
- View transitions may not work as expected with nested pages/layouts/async components owing to this upstream Vue bug: <https://github.com/vuejs/core/issues/5513>. If you make use of this pattern, you may need to delay adopting this experimental feature or implement it yourself. Feedback is very welcome.
- If you perform data fetching within your page setup functions, that you may wish to reconsider using this feature for the moment. (By design, View Transitions completely freeze DOM updates whilst they are taking place.) We're looking at restrict the View Transition to the final moments before `<Suspense>` resolves, but in the interim you may want to consider carefully whether to adopt this feature if this describes you.

View File

@ -263,6 +263,8 @@ Be very careful before proxying headers to an external API and just include head
If you want to pass on/proxy cookies in the other direction, from an internal request back to the client, you will need to handle this yourself.
```ts [composables/fetch.ts]
import { appendHeader, H3Event } from 'h3'
export const fetchWithCookie = async (event: H3Event, url: string) => {
const res = await $fetch.raw(url)
const cookies = (res.headers.get('set-cookie') || '').split(',')
@ -356,7 +358,7 @@ const { data } = await useFetch('/api/price')
## Using Async Setup
If you are using `async setup()`, the current component instance will be lost after the first `await`. (This is a Vue 3 limitation.) If you want to use multiple async operations, such as multiple calls to `useFetch`, you will need to use `<script setup>` or await them together at the end of setup.
If you are using `async setup()`, the current component instance will be lost after the first `await`. (This is a Vue 3 limitation.) If you want to use multiple async operations, such as multiple calls to `useFetch`, you will need to use `<script setup>`, await them together at the end of setup, or alternatively use `defineNuxtComponent` (which applies a custom transform to the setup function).
::alert{icon=👉}
Using `<script setup>` is recommended, as it removes the limitation of using top-level await. [Read more](https://vuejs.org/api/sfc-script-setup.html#top-level-await)

View File

@ -39,6 +39,31 @@ export default defineNuxtPlugin(nuxtApp => {
})
```
### Object Syntax Plugins
It is also possible to define a plugin using an object syntax, for more advanced use cases. For example:
```ts
export default defineNuxtPlugin({
name: 'my-plugin',
enforce: 'pre', // or 'post'
async setup (nuxtApp) {
// this is the equivalent of a normal functional plugin
},
hooks: {
// You can directly register Nuxt app hooks here
'app:created'() {
const nuxtApp = useNuxtApp()
//
}
}
})
```
::alert
If you are using an object-syntax plugin, the properties may be statically analyzed in future to produce a more optimized build. So you should not define them at runtime. For example, setting `enforce: process.server ? 'pre' : 'post'` would defeat any future optimization Nuxt is able to do for your plugins.
::
## Plugin Registration Order
You can control the order in which plugins are registered by prefixing a number to the file names.

View File

@ -18,7 +18,7 @@ type UseFetchOptions = {
query?: SearchParams
params?: SearchParams
body?: RequestInit['body'] | Record<string, any>
headers?: { key: string, value: string }[]
headers?: Record<string, string> | [key: string, value: string][] | Headers
baseURL?: string
server?: boolean
lazy?: boolean

View File

@ -84,9 +84,7 @@ await nuxtApp.callHook('my-plugin:init')
### `payload`
`payload` exposes data and state variables from server side to client side and makes them available in the `window.__NUXT__` object that is accessible from the browser.
`payload` exposes the following keys on the client side after they are stringified and passed from the server side:
`payload` exposes data and state variables from server side to client side. The following keys will be available on the client after they have been passed from the server side:
- **serverRendered** (boolean) - Indicates if response is server-side-rendered.
- **data** (object) - When you fetch the data from an API endpoint using either `useFetch` or `useAsyncData`, resulting payload can be accessed from the `payload.data`. This data is cached and helps you prevent fetching the same data in case an identical request is made more than once.
@ -115,6 +113,24 @@ export default defineNuxtPlugin((nuxtApp) => {
})
```
::alert
Normally `payload` must contain only plain JavaScript objects. But by setting `experimental.renderJsonPayloads`, it is possible to use more advanced types, such as `ref`, `reactive`, `shallowRef`, `shallowReactive` and `NuxtError`.
You can also add your own types, with a special plugin helper:
```ts [plugins/custom-payload.ts]
/**
* This kind of plugin runs very early in the Nuxt lifecycle, before we revive the payload.
* You will not have access to the router or other Nuxt-injected properties.
*/
export default definePayloadPlugin((nuxtApp) => {
definePayloadReducer('BlinkingText', data => data === '<blink>' && '_')
definePayloadReviver('BlinkingText', () => '<blink>')
})
```
::
### `isHydrating`
Use `nuxtApp.isHydrating` (boolean) to check if the Nuxt app is hydrating on the client side.

View File

@ -18,5 +18,5 @@ Option | Default | Description
This command sets `process.env.NODE_ENV` to `production`. To override, define `NODE_ENV` in a `.env` file or as a command-line argument.
::alert
You can also enable type-checking at build or development time by installing `@types/node`, `typescript` and `vue-tsc` as devDependencies and enabling [the `typescript.typeCheck` option in your `nuxt.config` file](/docs/api/configuration/nuxt-config#typescript).
You can also enable type-checking at build or development time by installing `typescript` and `vue-tsc` as devDependencies and enabling [the `typescript.typeCheck` option in your `nuxt.config` file](/docs/api/configuration/nuxt-config#typescript).
::

View File

@ -136,7 +136,7 @@ Nuxt no longer provides a Vuex integration. Instead, the official Vue recommenda
A simple way to provide global state management with pinia would be:
Install the [@nuxt/pinia](https://nuxt.com/modules/pinia) module:
Install the [@pinia/nuxt](https://nuxt.com/modules/pinia) module:
```bash
yarn add pinia @pinia/nuxt

View File

@ -1,4 +1,4 @@
import { getQuery, defineEventHandler } from 'h3'
import { defineEventHandler, getQuery } from 'h3'
export default defineEventHandler((event) => {
if ('api' in getQuery(event)) {

View File

@ -15,19 +15,19 @@
"lint:fix": "eslint --ext .vue,.ts,.js,.mjs . --fix",
"lint:docs": "markdownlint ./docs && case-police 'docs/**/*.md'",
"lint:docs:fix": "markdownlint ./docs --fix && case-police 'docs/**/*.md' --fix",
"nuxi": "JITI_ESM_RESOLVE=1 nuxi",
"nuxt": "JITI_ESM_RESOLVE=1 nuxi",
"play": "pnpm nuxi dev playground",
"play:build": "pnpm nuxi build playground",
"play:preview": "pnpm nuxi preview playground",
"test:fixtures": "pnpm nuxi prepare test/fixtures/basic && JITI_ESM_RESOLVE=1 vitest run --dir test",
"play": "nuxi dev playground",
"play:build": "nuxi build playground",
"play:preview": "nuxi preview playground",
"test:fixtures": "nuxi prepare test/fixtures/basic && nuxi prepare test/fixtures/runtime-compiler && vitest run --dir test",
"text: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:types": "pnpm nuxi prepare test/fixtures/basic && cd test/fixtures/basic && npx vue-tsc --noEmit",
"test:unit": "JITI_ESM_RESOLVE=1 vitest run --dir packages",
"test:types": "nuxi prepare test/fixtures/basic && cd test/fixtures/basic && npx vue-tsc --noEmit",
"test:unit": "vitest run --dir packages",
"typecheck": "tsc --noEmit"
},
"resolutions": {
"pnpm": {
"overrides": {
"@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*",
"@nuxt/test-utils": "workspace:*",
@ -40,6 +40,7 @@
"vite": "^4.2.1",
"vue": "3.2.47",
"magic-string": "^0.30.0"
}
},
"devDependencies": {
"@actions/core": "^1.10.0",
@ -53,32 +54,32 @@
"@types/node": "^18.15.11",
"@types/rimraf": "^3.0.2",
"@types/semver": "^7.3.13",
"@unocss/reset": "^0.50.6",
"@unocss/reset": "^0.51.4",
"case-police": "^0.5.14",
"changelogen": "^0.5.2",
"crawler": "^1.4.0",
"eslint": "^8.37.0",
"eslint-plugin-jsdoc": "^40.1.1",
"eslint": "^8.38.0",
"eslint-plugin-jsdoc": "^41.1.0",
"execa": "^7.1.1",
"expect-type": "^0.15.0",
"globby": "^13.1.3",
"globby": "^13.1.4",
"jiti": "^1.18.2",
"markdownlint-cli": "^0.33.0",
"nuxi": "workspace:*",
"nuxt": "workspace:*",
"ofetch": "^1.0.1",
"pathe": "^1.1.0",
"rimraf": "^4.4.1",
"semver": "^7.3.8",
"rimraf": "^5.0.0",
"semver": "^7.4.0",
"std-env": "^3.3.2",
"typescript": "^5.0.3",
"typescript": "^5.0.4",
"ufo": "^1.1.1",
"unbuild": "^1.2.0",
"vite": "^4.2.1",
"vitest": "^0.29.8",
"vitest": "^0.30.1",
"vue-tsc": "^1.2.0"
},
"packageManager": "pnpm@8.1.1",
"packageManager": "pnpm@8.2.0",
"engines": {
"node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}

View File

@ -1,6 +1,6 @@
{
"name": "@nuxt/kit",
"version": "3.3.3",
"version": "3.4.0",
"repository": "nuxt/nuxt",
"license": "MIT",
"type": "module",
@ -22,9 +22,9 @@
"dependencies": {
"@nuxt/schema": "workspace:../schema",
"c12": "^1.2.0",
"consola": "^2.15.3",
"consola": "^3.0.1",
"defu": "^6.1.2",
"globby": "^13.1.3",
"globby": "^13.1.4",
"hash-sum": "^2.0.0",
"ignore": "^5.2.4",
"jiti": "^1.18.2",
@ -34,9 +34,9 @@
"pathe": "^1.1.0",
"pkg-types": "^1.0.2",
"scule": "^1.0.0",
"semver": "^7.3.8",
"unctx": "^2.1.2",
"unimport": "^3.0.5",
"semver": "^7.4.0",
"unctx": "^2.2.0",
"unimport": "^3.0.6",
"untyped": "^1.3.2"
},
"devDependencies": {

View File

@ -1,5 +1,5 @@
import type { WebpackPluginInstance, Configuration as WebpackConfig } from 'webpack'
import type { Plugin as VitePlugin, UserConfig as ViteConfig } from 'vite'
import type { Configuration as WebpackConfig, WebpackPluginInstance } from 'webpack'
import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite'
import { useNuxt } from './context'
export interface ExtendConfigOptions {

View File

@ -1,5 +1,5 @@
import { pascalCase, kebabCase } from 'scule'
import type { ComponentsDir, Component } from '@nuxt/schema'
import { kebabCase, pascalCase } from 'scule'
import type { Component, ComponentsDir } from '@nuxt/schema'
import { useNuxt } from './context'
import { assertNuxtCompatibility } from './compatibility'

View File

@ -1,4 +1,4 @@
import { expect, it, describe } from 'vitest'
import { describe, expect, it } from 'vitest'
import { resolveGroupSyntax } from './ignore.js'
describe('resolveGroupSyntax', () => {

View File

@ -1,6 +1,6 @@
import { promises as fsp } from 'node:fs'
import lodashTemplate from 'lodash.template'
import { genSafeVariableName, genDynamicImport, genImport } from 'knitwork'
import { genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
import type { NuxtTemplate } from '@nuxt/schema'

View File

@ -2,7 +2,7 @@ import { resolve } from 'pathe'
import { applyDefaults } from 'untyped'
import type { LoadConfigOptions } from 'c12'
import { loadConfig } from 'c12'
import type { NuxtOptions, NuxtConfig } from '@nuxt/schema'
import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
import { NuxtConfigSchema } from '@nuxt/schema'
export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}

View File

@ -1,7 +1,7 @@
import consola from 'consola'
import { consola } from 'consola'
export const logger = consola
export function useLogger (scope?: string) {
return scope ? logger.withScope(scope) : logger
export function useLogger (tag?: string) {
return tag ? logger.withTag(tag) : logger
}

View File

@ -3,11 +3,11 @@ import { performance } from 'node:perf_hooks'
import { defu } from 'defu'
import { applyDefaults } from 'untyped'
import { dirname } from 'pathe'
import type { Nuxt, NuxtModule, ModuleOptions, ModuleSetupReturn, ModuleDefinition, NuxtOptions, ResolvedNuxtTemplate } from '@nuxt/schema'
import type { ModuleDefinition, ModuleOptions, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedNuxtTemplate } from '@nuxt/schema'
import { logger } from '../logger'
import { useNuxt, nuxtCtx, tryUseNuxt } from '../context'
import { isNuxt2, checkNuxtCompatibility } from '../compatibility'
import { templateUtils, compileTemplate } from '../internal/template'
import { nuxtCtx, tryUseNuxt, useNuxt } from '../context'
import { checkNuxtCompatibility, isNuxt2 } from '../compatibility'
import { compileTemplate, templateUtils } from '../internal/template'
/**
* Define a Nuxt module, automatically merging defaults with user provided options, installing

View File

@ -1,7 +1,7 @@
import type { Nuxt, NuxtModule } from '@nuxt/schema'
import { isNuxt2 } from '../compatibility'
import { useNuxt } from '../context'
import { resolveModule, requireModule } from '../internal/cjs'
import { requireModule, resolveModule } from '../internal/cjs'
import { importModule } from '../internal/esm'
import { resolveAlias } from '../resolve'

View File

@ -1,4 +1,4 @@
import type { NitroEventHandler, NitroDevEventHandler, Nitro } from 'nitropack'
import type { Nitro, NitroDevEventHandler, NitroEventHandler } from 'nitropack'
import { normalize } from 'pathe'
import { useNuxt } from './context'

View File

@ -1,6 +1,6 @@
import { promises as fsp, existsSync } from 'node:fs'
import { existsSync, promises as fsp } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { basename, dirname, resolve, join, normalize, isAbsolute } from 'pathe'
import { basename, dirname, isAbsolute, join, normalize, resolve } from 'pathe'
import { globby } from 'globby'
import { resolveAlias as _resolveAlias } from 'pathe/utils'
import { tryUseNuxt } from './context'

View File

@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'
import { basename, parse, resolve } from 'pathe'
import hash from 'hash-sum'
import type { NuxtTemplate, ResolvedNuxtTemplate } from '@nuxt/schema'
import { useNuxt, tryUseNuxt } from './context'
import { tryUseNuxt, useNuxt } from './context'
/**
* Renders given template using lodash template during build into the project buildDir

View File

@ -1,6 +1,6 @@
{
"name": "nuxi",
"version": "3.3.3",
"version": "3.4.0",
"repository": "nuxt/nuxt",
"license": "MIT",
"type": "module",
@ -29,7 +29,7 @@
"clear": "^0.1.0",
"clipboardy": "^3.0.0",
"colorette": "^2.0.19",
"consola": "^2.15.3",
"consola": "^3.0.1",
"deep-object-diff": "^1.1.9",
"destr": "^1.2.2",
"execa": "^7.1.1",
@ -44,7 +44,7 @@
"perfect-debounce": "^0.1.3",
"pkg-types": "^1.0.2",
"scule": "^1.0.0",
"semver": "^7.3.8",
"semver": "^7.4.0",
"unbuild": "latest"
},
"optionalDependencies": {

View File

@ -1,7 +1,7 @@
import mri from 'mri'
import { red } from 'colorette'
import type { ConsolaReporter } from 'consola'
import consola from 'consola'
import { consola } from 'consola'
import { checkEngines } from './utils/engines'
import type { Command, NuxtCommand } from './commands'
import { commands } from './commands'
@ -45,7 +45,7 @@ consola.wrapAll()
// Filter out unwanted logs
// TODO: Use better API from consola for intercepting logs
const wrapReporter = (reporter: ConsolaReporter) => <ConsolaReporter> {
const wrapReporter = (reporter: ConsolaReporter) => ({
log (logObj, ctx) {
if (!logObj.args || !logObj.args.length) { return }
const msg = logObj.args[0]
@ -54,6 +54,11 @@ const wrapReporter = (reporter: ConsolaReporter) => <ConsolaReporter> {
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
@ -61,9 +66,9 @@ const wrapReporter = (reporter: ConsolaReporter) => <ConsolaReporter> {
}
return reporter.log(logObj, ctx)
}
}
// @ts-expect-error
consola._reporters = consola._reporters.map(wrapReporter)
}) 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))

View File

@ -1,6 +1,6 @@
import { existsSync, promises as fsp } from 'node:fs'
import { resolve, dirname } from 'pathe'
import consola from 'consola'
import { dirname, resolve } from 'pathe'
import { consola } from 'consola'
import { loadKit } from '../utils/kit'
import { templates } from '../utils/templates'
import { defineNuxtCommand } from './index'

View File

@ -1,5 +1,5 @@
import { execa } from 'execa'
import consola from 'consola'
import { consola } from 'consola'
import { resolve } from 'pathe'
import { tryResolveModule } from '../utils/esm'
import { defineNuxtCommand } from './index'

View File

@ -1,5 +1,5 @@
import { relative, resolve } from 'pathe'
import consola from 'consola'
import { consola } from 'consola'
import { writeTypes } from '../utils/prepare'
import { loadKit } from '../utils/kit'
import { clearDir } from '../utils/fs'

View File

@ -1,10 +1,10 @@
import type { AddressInfo } from 'node:net'
import type { RequestListener } from 'node:http'
import { resolve, relative } from 'pathe'
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 { consola } from 'consola'
import { withTrailingSlash } from 'ufo'
import { setupDotenv } from 'c12'
import { showBanner, showVersions } from '../utils/banner'
@ -12,7 +12,7 @@ import { writeTypes } from '../utils/prepare'
import { loadKit } from '../utils/kit'
import { importModule } from '../utils/esm'
import { overrideEnv } from '../utils/env'
import { writeNuxtManifest, loadNuxtManifest, cleanupNuxtDirs } from '../utils/nuxt'
import { cleanupNuxtDirs, loadNuxtManifest, writeNuxtManifest } from '../utils/nuxt'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({

View File

@ -19,7 +19,6 @@ export default defineNuxtCommand({
process.exit(1)
}
// Defer to feature setup
await execa('npx', ['@nuxt/devtools@latest', command, rootDir], { stdio: 'inherit', cwd: rootDir })
await execa('npx', ['@nuxt/devtools-wizard', command, rootDir], { stdio: 'inherit', cwd: rootDir })
}
})

View File

@ -1,7 +1,7 @@
import { writeFile } from 'node:fs/promises'
import { downloadTemplate, startShell } from 'giget'
import { relative } from 'pathe'
import consola from 'consola'
import { consola } from 'consola'
import { defineNuxtCommand } from './index'
const rpath = (p: string) => relative(process.cwd(), p)

View File

@ -1,6 +1,6 @@
import { buildNuxt } from '@nuxt/kit'
import { relative, resolve } from 'pathe'
import consola from 'consola'
import { consola } from 'consola'
import { clearDir } from '../utils/fs'
import { loadKit } from '../utils/kit'
import { writeTypes } from '../utils/prepare'

View File

@ -3,7 +3,8 @@ import { dirname, relative } from 'node:path'
import { execa } from 'execa'
import { setupDotenv } from 'c12'
import { resolve } from 'pathe'
import consola from 'consola'
import { consola } from 'consola'
import { loadKit } from '../utils/kit'
import { defineNuxtCommand } from './index'
@ -16,8 +17,13 @@ export default defineNuxtCommand({
async invoke (args) {
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 })
const nitroJSONPaths = ['.output/nitro.json', 'nitro.json'].map(p => resolve(rootDir, p))
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)

View File

@ -1,5 +1,5 @@
import { execSync } from 'node:child_process'
import consola from 'consola'
import { consola } from 'consola'
import { resolve } from 'pathe'
import { readPackageJSON } from 'pkg-types'
import { getPackageManager, packageManagerLocks } from '../utils/packageManagers'
@ -7,7 +7,7 @@ import { rmRecursive, touchFile } from '../utils/fs'
import { cleanupNuxtDirs, nuxtVersionToGitIdentifier } from '../utils/nuxt'
import { defineNuxtCommand } from './index'
async function getNuxtVersion (path: string): Promise<string|null> {
async function getNuxtVersion (path: string): Promise<string | null> {
try {
const pkg = await readPackageJSON('nuxt', { url: path })
if (!pkg.version) {

View File

@ -1,5 +1,5 @@
import { createRequire } from 'node:module'
import { normalize, dirname } from 'pathe'
import { dirname, normalize } from 'pathe'
export function getModulePaths (paths?: string | string[]): string[] {
return ([] as Array<string | undefined>)

View File

@ -1,6 +1,6 @@
import flatten from 'flat'
import { detailedDiff } from 'deep-object-diff'
import { green, red, blue, cyan } from 'colorette'
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>)

View File

@ -1,6 +1,6 @@
import { promises as fsp } from 'node:fs'
import { dirname } from 'pathe'
import consola from 'consola'
import { consola } from 'consola'
// Check if a file exists
export async function exists (path: string) {

View File

@ -1,6 +1,6 @@
import { promises as fsp } from 'node:fs'
import { resolve, dirname } from 'pathe'
import consola from 'consola'
import { dirname, resolve } from 'pathe'
import { consola } from 'consola'
import { hash } from 'ohash'
import type { Nuxt } from '@nuxt/schema'
import { rmRecursive } from './fs'

View File

@ -1,6 +1,6 @@
{
"name": "nuxt",
"version": "3.3.3",
"version": "3.4.0",
"repository": "nuxt/nuxt",
"license": "MIT",
"type": "module",
@ -38,10 +38,6 @@
"types": "./dist/app/index.d.ts",
"import": "./dist/app/index.js"
},
"#head": {
"types": "./dist/head/runtime/index.d.ts",
"import": "./dist/head/runtime/index.js"
},
"#pages": {
"types": "./dist/pages/runtime/index.d.ts",
"import": "./dist/pages/runtime/index.js"
@ -63,7 +59,7 @@
"@nuxt/devalue": "^2.0.0",
"@nuxt/kit": "workspace:../kit",
"@nuxt/schema": "workspace:../schema",
"@nuxt/telemetry": "^2.1.10",
"@nuxt/telemetry": "^2.2.0",
"@nuxt/ui-templates": "^1.1.1",
"@nuxt/vite-builder": "workspace:../vite",
"@unhead/ssr": "^1.1.25",
@ -74,29 +70,33 @@
"cookie-es": "^0.5.0",
"defu": "^6.1.2",
"destr": "^1.2.2",
"devalue": "^4.3.0",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"fs-extra": "^11.1.1",
"globby": "^13.1.3",
"globby": "^13.1.4",
"h3": "^1.6.4",
"hash-sum": "^2.0.0",
"hookable": "^5.5.3",
"jiti": "^1.18.2",
"knitwork": "^1.0.0",
"local-pkg": "^0.4.3",
"magic-string": "^0.30.0",
"mlly": "^1.2.0",
"nitropack": "^2.3.2",
"nitropack": "^2.3.3",
"nuxi": "workspace:../nuxi",
"nypm": "^0.1.0",
"ofetch": "^1.0.1",
"ohash": "^1.0.0",
"pathe": "^1.1.0",
"perfect-debounce": "^0.1.3",
"prompts": "^2.4.2",
"scule": "^1.0.0",
"strip-literal": "^1.0.1",
"ufo": "^1.1.1",
"unctx": "^2.1.2",
"unenv": "^1.2.2",
"unimport": "^3.0.5",
"unctx": "^2.2.0",
"unenv": "^1.3.1",
"unimport": "^3.0.6",
"unplugin": "^1.3.1",
"untyped": "^1.3.2",
"vue": "^3.2.47",
@ -107,8 +107,12 @@
"devDependencies": {
"@types/fs-extra": "^11.0.1",
"@types/hash-sum": "^1.0.0",
"@types/prompts": "^2.4.4",
"unbuild": "latest"
},
"peerDependencies": {
"@types/node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"engines": {
"node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}

View File

@ -1,4 +1,4 @@
import { defineComponent, createElementBlock } from 'vue'
import { createElementBlock, defineComponent } from 'vue'
export default defineComponent({
name: 'NuxtClientFallback',

View File

@ -1,5 +1,5 @@
import { defineComponent, getCurrentInstance, onErrorCaptured } from 'vue'
import { ssrRenderVNode, ssrRenderAttrs, ssrRenderSlot } from 'vue/server-renderer'
import { ssrRenderAttrs, ssrRenderSlot, ssrRenderVNode } from 'vue/server-renderer'
import { createBuffer } from './utils'
const NuxtClientFallbackServer = defineComponent({

View File

@ -1,4 +1,4 @@
import { ref, onMounted, defineComponent, createElementBlock, h, createElementVNode } from 'vue'
import { createElementBlock, createElementVNode, defineComponent, h, mergeProps, onMounted, ref } from 'vue'
export default defineComponent({
name: 'ClientOnly',
@ -37,7 +37,7 @@ export function createClientOnly (component) {
? createElementVNode(res.type, res.props, res.children, res.patchFlag, res.dynamicProps, res.shapeFlag)
: h(res)
} else {
return h('div', ctx.$attrs ?? ctx._.attrs)
return h('div', mergeProps(ctx.$attrs ?? ctx._.attrs, { key: 'placeholder-key' }))
}
}
} else if (clone.template) {
@ -63,7 +63,7 @@ export function createClientOnly (component) {
? createElementVNode(res.type, res.props, res.children, res.patchFlag, res.dynamicProps, res.shapeFlag)
: h(res)
} else {
return h('div', ctx.attrs)
return h('div', mergeProps(ctx.attrs, { key: 'placeholder-key' }))
}
}
})

View File

@ -1,5 +1,5 @@
import type { defineAsyncComponent } from 'vue'
import { defineComponent, createVNode } from 'vue'
import { createVNode, defineComponent } from 'vue'
// @ts-ignore
import * as islandComponents from '#build/components.islands.mjs'

View File

@ -1,5 +1,5 @@
import type { Ref, VNode } from 'vue'
import { computed, defineComponent, h, inject, nextTick, onMounted, Transition, unref } from 'vue'
import { Transition, computed, defineComponent, h, inject, nextTick, onMounted, unref } from 'vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { _wrapIf } from './utils'
import { useRoute } from '#app/composables/router'

View File

@ -1,4 +1,4 @@
import { defineComponent, ref, onErrorCaptured } from 'vue'
import { defineComponent, onErrorCaptured, ref } from 'vue'
import { useNuxtApp } from '#app/nuxt'
export default defineComponent({

View File

@ -1,4 +1,4 @@
import { defineComponent, createStaticVNode, computed, ref, watch, getCurrentInstance } from 'vue'
import { computed, createStaticVNode, defineComponent, getCurrentInstance, ref, watch } from 'vue'
import { debounce } from 'perfect-debounce'
import { hash } from 'ohash'
import { appendHeader } from 'h3'

View File

@ -1,7 +1,7 @@
import type { PropType, DefineComponent, ComputedRef } from 'vue'
import { defineComponent, h, ref, resolveComponent, computed, onMounted, onBeforeUnmount } from 'vue'
import type { ComputedRef, DefineComponent, PropType } from 'vue'
import { computed, defineComponent, h, onBeforeUnmount, onMounted, ref, resolveComponent } from 'vue'
import type { RouteLocation, RouteLocationRaw } from 'vue-router'
import { hasProtocol, parseQuery, parseURL, withoutTrailingSlash, withTrailingSlash } from 'ufo'
import { hasProtocol, parseQuery, parseURL, withTrailingSlash, withoutTrailingSlash } from 'ufo'
import { preloadRouteComponents } from '../composables/preload'
import { onNuxtReady } from '../composables/ready'

View File

@ -2,6 +2,7 @@
<Suspense @resolve="onResolve">
<ErrorComponent v-if="error" :error="error" />
<IslandRenderer v-else-if="islandContext" :context="islandContext" />
<component :is="SingleRenderer" v-else-if="SingleRenderer" />
<AppComponent v-else />
</Suspense>
</template>
@ -21,6 +22,10 @@ const IslandRenderer = process.server
const nuxtApp = useNuxtApp()
const onResolve = nuxtApp.deferHydration()
const url = process.server ? nuxtApp.ssrContext.url : window.location.pathname
const SingleRenderer = process.dev && process.server && url.startsWith('/__nuxt_component_test__/') && defineAsyncComponent(() => import('#build/test-component-wrapper.mjs')
.then(r => r.default(process.server ? url : window.location.href)))
// Inject default route (outside of pages) as active route
provide('_route', useRoute())

View File

@ -1,4 +1,4 @@
import { defineComponent, createElementBlock } from 'vue'
import { createElementBlock, defineComponent } from 'vue'
export default defineComponent({
name: 'ServerPlaceholder',

View File

@ -0,0 +1,19 @@
import { parseURL } from 'ufo'
import { defineComponent, h } from 'vue'
import { parseQuery } from 'vue-router'
export default (url:string) => defineComponent({
name: 'NuxtTestComponentWrapper',
async setup (props, { attrs }) {
const query = parseQuery(parseURL(url).search)
const urlProps = query.props ? JSON.parse(query.props as string) : {}
const comp = await import(/* @vite-ignore */ query.path as string).then(r => r.default)
return () => [
h('div', 'Component Test Wrapper for ' + query.path),
h('div', { id: 'nuxt-component-root' }, [
h(comp, { ...attrs, ...props, ...urlProps })
])
]
}
})

View File

@ -1,4 +1,4 @@
import { onBeforeMount, onServerPrefetch, onUnmounted, ref, getCurrentInstance, watch, unref, toRef } from 'vue'
import { getCurrentInstance, onBeforeMount, onServerPrefetch, onUnmounted, ref, toRef, unref, watch } from 'vue'
import type { Ref, WatchSource } from 'vue'
import type { NuxtApp } from '../nuxt'
import { useNuxtApp } from '../nuxt'
@ -118,7 +118,7 @@ export function useAsyncData<
nuxt._asyncData[key] = {
data: ref(getCachedData() ?? options.default?.() ?? null),
pending: ref(!hasCachedData()),
error: ref(nuxt.payload._errors[key] ? createError(nuxt.payload._errors[key]) : null)
error: toRef(nuxt.payload._errors, key)
}
}
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher

View File

@ -2,7 +2,7 @@ import { getCurrentInstance, reactive, toRefs } from 'vue'
import type { DefineComponent, defineComponent } from 'vue'
import { useHead } from '@unhead/vue'
import type { NuxtApp } from '../nuxt'
import { useNuxtApp } from '../nuxt'
import { callWithNuxt, useNuxtApp } from '../nuxt'
import { useAsyncData } from './asyncData'
import { useRoute } from './router'
@ -14,7 +14,7 @@ async function runLegacyAsyncData (res: Record<string, any> | Promise<Record<str
const vm = getCurrentInstance()!
const { fetchKey } = vm.proxy!.$options
const key = typeof fetchKey === 'function' ? fetchKey(() => '') : fetchKey || route.fullPath
const { data } = await useAsyncData(`options:asyncdata:${key}`, () => fn(nuxt))
const { data } = await useAsyncData(`options:asyncdata:${key}`, () => callWithNuxt(nuxt, fn, [nuxt]))
if (data.value && typeof data.value === 'object') {
Object.assign(await res, toRefs(reactive(data.value)))
} else if (process.dev) {
@ -38,7 +38,8 @@ export const defineNuxtComponent: typeof defineComponent =
[NuxtComponentIndicator]: true,
...options,
setup (props, ctx) {
const res = setup?.(props, ctx) || {}
const nuxtApp = useNuxtApp()
const res = setup ? Promise.resolve(callWithNuxt(nuxtApp, setup, [props, ctx])).then(r => r || {}) : {}
const promises: Promise<any>[] = []
if (options.asyncData) {

View File

@ -27,7 +27,7 @@ const CookieDefaults: CookieOptions<any> = {
encode: val => encodeURIComponent(typeof val === 'string' ? val : JSON.stringify(val))
}
export function useCookie <T = string | null> (name: string, _opts?: CookieOptions<T>): CookieRef<T> {
export function useCookie <T = string | null | undefined> (name: string, _opts?: CookieOptions<T>): CookieRef<T> {
const opts = { ...CookieDefaults, ..._opts }
const cookies = readRawCookies(opts) || {}

View File

@ -1,10 +1,10 @@
import type { FetchError } from 'ofetch'
import type { TypedInternalResponse, NitroFetchOptions, NitroFetchRequest, AvailableRouterMethod } from 'nitropack'
import type { AvailableRouterMethod, NitroFetchOptions, NitroFetchRequest, TypedInternalResponse } from 'nitropack'
import type { Ref } from 'vue'
import { computed, unref, reactive } from 'vue'
import { computed, reactive, unref } from 'vue'
import { hash } from 'ohash'
import { useRequestFetch } from './ssr'
import type { AsyncDataOptions, _Transform, KeysOf, AsyncData, PickFrom, MultiWatchSources } from './asyncData'
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom, _Transform } from './asyncData'
import { useAsyncData } from './asyncData'
export type FetchResult<ReqT extends NitroFetchRequest, M extends AvailableRouterMethod<ReqT>> = TypedInternalResponse<ReqT, unknown, M>

View File

@ -28,6 +28,6 @@ export { onNuxtReady } from './ready'
export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter } from './router'
export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router'
export { preloadComponents, prefetchComponents, preloadRouteComponents } from './preload'
export { isPrerendered, loadPayload, preloadPayload } from './payload'
export { isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver } from './payload'
export type { ReloadNuxtAppOptions } from './chunk'
export { reloadNuxtApp } from './chunk'

View File

@ -1,7 +1,12 @@
import { joinURL, hasProtocol } from 'ufo'
import { hasProtocol, joinURL } from 'ufo'
import { parse } from 'devalue'
import { useHead } from '@unhead/vue'
import { getCurrentInstance } from 'vue'
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
// @ts-expect-error virtual import
import { renderJsonPayloads } from '#build/nuxt.config.mjs'
interface LoadPayloadOptions {
fresh?: boolean
hash?: string
@ -36,6 +41,7 @@ export function preloadPayload (url: string, opts: LoadPayloadOptions = {}) {
// --- Internal ---
const extension = renderJsonPayloads ? 'json' : 'js'
function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
const u = new URL(url, 'http://localhost')
if (u.search) {
@ -45,15 +51,19 @@ function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
throw new Error('Payload URL must not include hostname: ' + url)
}
const hash = opts.hash || (opts.fresh ? Date.now() : '')
return joinURL(useRuntimeConfig().app.baseURL, u.pathname, hash ? `_payload.${hash}.js` : '_payload.js')
return joinURL(useRuntimeConfig().app.baseURL, u.pathname, hash ? `_payload.${hash}.${extension}` : `_payload.${extension}`)
}
async function _importPayload (payloadURL: string) {
if (process.server) { return null }
const res = await import(/* webpackIgnore: true */ /* @vite-ignore */ payloadURL).catch((err) => {
try {
return renderJsonPayloads
? parsePayload(await fetch(payloadURL).then(res => res.text()))
: await import(/* webpackIgnore: true */ /* @vite-ignore */ payloadURL).then(r => r.default || r)
} catch (err) {
console.warn('[nuxt] Cannot load payload ', payloadURL, err)
})
return res?.default || null
}
return null
}
export function isPrerendered () {
@ -61,3 +71,63 @@ export function isPrerendered () {
const nuxtApp = useNuxtApp()
return !!nuxtApp.payload.prerenderedAt
}
let payloadCache: any = null
export async function getNuxtClientPayload () {
if (process.server) {
return
}
if (payloadCache) {
return payloadCache
}
const el = document.getElementById('__NUXT_DATA__')
if (!el) {
return {}
}
const inlineData = parsePayload(el.textContent || '')
const externalData = el.dataset.src ? await _importPayload(el.dataset.src) : undefined
payloadCache = {
...inlineData,
...externalData,
...window.__NUXT__
}
return payloadCache
}
export function parsePayload (payload: string) {
return parse(payload, useNuxtApp()._payloadRevivers)
}
/**
* This is an experimental function for configuring passing rich data from server -> client.
*/
export function definePayloadReducer (
name: string,
reduce: (data: any) => any
) {
if (process.server) {
useNuxtApp().ssrContext!._payloadReducers[name] = reduce
}
}
/**
* This is an experimental function for configuring passing rich data from server -> client.
*
* This function _must_ be called in a Nuxt plugin that is `unshift`ed to the beginning of the Nuxt plugins array.
*/
export function definePayloadReviver (
name: string,
revive: (data: string) => any | undefined
) {
if (process.dev && getCurrentInstance()) {
console.warn('[nuxt] [definePayloadReviver] This function must be called in a Nuxt plugin that is `unshift`ed to the beginning of the Nuxt plugins array.')
}
if (process.client) {
useNuxtApp()._payloadRevivers[name] = revive
}
}

View File

@ -1,6 +1,6 @@
import { getCurrentInstance, inject, onUnmounted } from 'vue'
import type { Ref } from 'vue'
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, NavigationFailure, RouteLocationPathRaw } from 'vue-router'
import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteLocationPathRaw, RouteLocationRaw, Router } from 'vue-router'
import { sendRedirect } from 'h3'
import { hasProtocol, joinURL, parseURL } from 'ufo'

View File

@ -1,10 +1,10 @@
// We set __webpack_public_path via this import with webpack builder
import { createSSRApp, createApp, nextTick } from 'vue'
import { createApp, createSSRApp, nextTick } from 'vue'
import { $fetch } from 'ofetch'
// @ts-ignore
import { baseURL } from '#build/paths.mjs'
import type { CreateOptions } from '#app'
import { createNuxtApp, applyPlugins, normalizePlugins } from '#app/nuxt'
import { applyPlugins, createNuxtApp, normalizePlugins } from '#app/nuxt'
import '#build/css'
// @ts-ignore
import _plugins from '#build/plugins'
@ -52,7 +52,10 @@ if (process.client) {
}
entry = async function initApp () {
const isSSR = Boolean(window.__NUXT__?.serverRendered)
const isSSR = Boolean(
window.__NUXT__?.serverRendered ||
document.getElementById('__NUXT_DATA__')?.dataset.ssr === 'true'
)
const vueApp = isSSR ? createSSRApp(RootComponent) : createApp(RootComponent)
const nuxt = createNuxtApp({ vueApp })

View File

@ -1,17 +1,18 @@
/* eslint-disable no-use-before-define */
import { getCurrentInstance, reactive } from 'vue'
import type { App, onErrorCaptured, VNode, Ref } from 'vue'
import type { App, Ref, VNode, onErrorCaptured } from 'vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import type { Hookable, HookCallback } from 'hookable'
import type { HookCallback, Hookable } from 'hookable'
import { createHooks } from 'hookable'
import { getContext } from 'unctx'
import type { SSRContext } from 'vue-bundle-renderer/runtime'
import type { H3Event } from 'h3'
import type { RuntimeConfig, AppConfigInput, AppConfig } from 'nuxt/schema'
import type { AppConfig, AppConfigInput, RuntimeConfig } from 'nuxt/schema'
// eslint-disable-next-line import/no-restricted-paths
import type { NuxtIslandContext } from '../core/runtime/nitro/renderer'
import type { RouteMiddleware } from '../../app'
import type { NuxtError } from '../app/composables/error'
const nuxtAppCtx = /* #__PURE__ */ getContext<NuxtApp>('nuxt-app')
@ -41,6 +42,7 @@ export interface RuntimeNuxtHooks {
'link:prefetch': (link: string) => HookResult
'page:start': (Component?: VNode) => HookResult
'page:finish': (Component?: VNode) => HookResult
'page:transition:start': () => HookResult
'page:transition:finish': (Component?: VNode) => HookResult
'vue:setup': () => void
'vue:error': (...args: Parameters<Parameters<typeof onErrorCaptured>[0]>) => HookResult
@ -58,6 +60,8 @@ export interface NuxtSSRContext extends SSRContext {
teleports?: Record<string, string>
renderMeta?: () => Promise<NuxtMeta> | NuxtMeta
islandContext?: NuxtIslandContext
/** @internal */
_payloadReducers: Record<string, (data: any) => any>
}
interface _NuxtApp {
@ -99,6 +103,9 @@ interface _NuxtApp {
/** @internal */
_islandPromises?: Record<string, Promise<any>>
/** @internal */
_payloadRevivers: Record<string, (data: any) => any>
// Nuxt injections
$config: RuntimeConfig
@ -111,7 +118,6 @@ interface _NuxtApp {
prerenderedAt?: number
data: Record<string, any>
state: Record<string, any>
rendered?: Function
error?: Error | {
url: string
statusCode: number
@ -120,6 +126,7 @@ interface _NuxtApp {
description: string
data?: any
} | null
_errors: Record<string, NuxtError | undefined>
[key: string]: any
}
static: {
@ -132,9 +139,31 @@ interface _NuxtApp {
export interface NuxtApp extends _NuxtApp {}
export const NuxtPluginIndicator = '__nuxt_plugin'
export interface PluginMeta {
name?: string
enforce?: 'pre' | 'default' | 'post'
/**
* This allows more granular control over plugin order and should only be used by advanced users.
* It overrides the value of `enforce` and is used to sort plugins.
*/
order?: number
}
export interface ResolvedPluginMeta {
name?: string
order: number
}
export interface Plugin<Injections extends Record<string, unknown> = Record<string, unknown>> {
(nuxt: _NuxtApp): Promise<void> | Promise<{ provide?: Injections }> | void | { provide?: Injections }
[NuxtPluginIndicator]?: true
meta?: ResolvedPluginMeta
}
export interface ObjectPluginInput<Injections extends Record<string, unknown> = Record<string, unknown>> extends PluginMeta {
hooks?: Partial<RuntimeNuxtHooks>
setup?: Plugin<Injections>
}
export interface CreateOptions {
@ -156,7 +185,7 @@ export function createNuxtApp (options: CreateOptions) {
data: {},
state: {},
_errors: {},
...(process.client ? window.__NUXT__ : { serverRendered: true })
...(process.client ? window.__NUXT__ ?? {} : { serverRendered: true })
}),
static: {
data: {}
@ -182,6 +211,7 @@ export function createNuxtApp (options: CreateOptions) {
},
_asyncDataPromises: {},
_asyncData: {},
_payloadRevivers: {},
...options
} as any as NuxtApp
@ -217,7 +247,11 @@ export function createNuxtApp (options: CreateOptions) {
if (nuxtApp.ssrContext) {
nuxtApp.ssrContext.nuxt = nuxtApp
}
// Expose to server renderer to create window.__NUXT__
// Expose payload types
if (nuxtApp.ssrContext) {
nuxtApp.ssrContext._payloadReducers = {}
}
// Expose to server renderer to create payload
nuxtApp.ssrContext = nuxtApp.ssrContext || {} as any
if (nuxtApp.ssrContext!.payload) {
Object.assign(nuxtApp.payload, nuxtApp.ssrContext!.payload)
@ -225,7 +259,7 @@ export function createNuxtApp (options: CreateOptions) {
nuxtApp.ssrContext!.payload = nuxtApp.payload
// Expose client runtime-config to the payload
nuxtApp.payload.config = {
nuxtApp.ssrContext!.config = {
public: options.ssrContext!.runtimeConfig.public,
app: options.ssrContext!.runtimeConfig.app
}
@ -244,17 +278,19 @@ export function createNuxtApp (options: CreateOptions) {
}
// Expose runtime config
const runtimeConfig = process.server
? options.ssrContext!.runtimeConfig
: reactive(nuxtApp.payload.config)
const runtimeConfig = process.server ? options.ssrContext!.runtimeConfig : reactive(nuxtApp.payload.config)
// TODO: remove in v3.5
// Backward compatibility following #4254
const compatibilityConfig = new Proxy(runtimeConfig, {
get (target, prop) {
if (prop === 'public') {
return target.public
get (target, prop: string) {
if (prop in target) {
return target[prop]
}
return target[prop] ?? target.public[prop]
if (process.dev && prop in target.public) {
console.warn(`[nuxt] [runtimeConfig] You are trying to access a public runtime config value (\`${prop}\`) directly from the top level. This currently works (for backward compatibility with Nuxt 2) but this compatibility layer will be removed in v3.5. Instead, you can update \`config['${prop}']\` to \`config.public['${prop}']\`.`)
}
return target.public[prop]
},
set (target, prop, value) {
if (process.server || prop === 'public' || prop === 'app') {
@ -292,25 +328,30 @@ export function normalizePlugins (_plugins: Plugin[]) {
const legacyInjectPlugins: Plugin[] = []
const invalidPlugins: Plugin[] = []
const plugins = _plugins.map((plugin) => {
const plugins: Plugin[] = []
for (const plugin of _plugins) {
if (typeof plugin !== 'function') {
invalidPlugins.push(plugin)
return null
if (process.dev) { invalidPlugins.push(plugin) }
continue
}
// TODO: Skip invalid plugins in next releases
let _plugin = plugin
if (plugin.length > 1) {
legacyInjectPlugins.push(plugin)
// Allow usage without wrapper but warn
// TODO: Skip invalid in next releases
// @ts-ignore
return (nuxtApp: NuxtApp) => plugin(nuxtApp, nuxtApp.provide)
// return null
if (process.dev) { legacyInjectPlugins.push(plugin) }
// @ts-expect-error deliberate invalid second argument
_plugin = (nuxtApp: NuxtApp) => plugin(nuxtApp, nuxtApp.provide)
}
if (!isNuxtPlugin(plugin)) {
unwrappedPlugins.push(plugin)
// Allow usage without wrapper but warn
if (process.dev && !isNuxtPlugin(_plugin)) { unwrappedPlugins.push(_plugin) }
plugins.push(_plugin)
}
return plugin
}).filter(Boolean)
plugins.sort((a, b) => (a.meta?.order || orderMap.default) - (b.meta?.order || orderMap.default))
if (process.dev && legacyInjectPlugins.length) {
console.warn('[warn] [nuxt] You are using a plugin with legacy Nuxt 2 format (context, inject) which is likely to be broken. In the future they will be ignored:', legacyInjectPlugins.map(p => p.name || p).join(','))
@ -322,12 +363,53 @@ export function normalizePlugins (_plugins: Plugin[]) {
console.warn('[warn] [nuxt] You are using a plugin that has not been wrapped in `defineNuxtPlugin`. It is advised to wrap your plugins as in the future this may enable enhancements:', unwrappedPlugins.map(p => p.name || p).join(','))
}
return plugins as Plugin[]
return plugins
}
export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T>) {
plugin[NuxtPluginIndicator] = true
return plugin
// -50: pre-all (nuxt)
// -40: custom payload revivers (user)
// -30: payload reviving (nuxt)
// -20: pre (user) <-- pre mapped to this
// -10: default (nuxt)
// 0: default (user) <-- default behavior
// +10: post (nuxt)
// +20: post (user) <-- post mapped to this
// +30: post-all (nuxt)
const orderMap: Record<NonNullable<ObjectPluginInput['enforce']>, number> = {
pre: -20,
default: 0,
post: 20
}
export function definePayloadPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPluginInput<T>) {
return defineNuxtPlugin(plugin, { order: -40 })
}
export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPluginInput<T>, meta?: PluginMeta): Plugin<T> {
if (typeof plugin === 'function') { return defineNuxtPlugin({ setup: plugin }, meta) }
const wrapper: Plugin<T> = (nuxtApp) => {
if (plugin.hooks) {
nuxtApp.hooks.addHooks(plugin.hooks)
}
if (plugin.setup) {
return plugin.setup(nuxtApp)
}
}
wrapper.meta = {
name: meta?.name || plugin.name || plugin.setup?.name,
order:
meta?.order ||
plugin.order ||
orderMap[plugin.enforce || 'default'] ||
orderMap.default
}
wrapper[NuxtPluginIndicator] = true
return wrapper
}
export function isNuxtPlugin (plugin: unknown) {

View File

@ -3,7 +3,9 @@ import { defineNuxtPlugin, useRuntimeConfig } from '#app/nuxt'
import { useRouter } from '#app/composables/router'
import { reloadNuxtApp } from '#app/composables/chunk'
export default defineNuxtPlugin((nuxtApp) => {
export default defineNuxtPlugin({
name: 'nuxt:chunk-reload',
setup (nuxtApp) {
const router = useRouter()
const config = useRuntimeConfig()
@ -19,4 +21,5 @@ export default defineNuxtPlugin((nuxtApp) => {
reloadNuxtApp({ path, persistState: true })
}
})
}
})

View File

@ -3,7 +3,9 @@ import { parseURL } from 'ufo'
import { useHead } from '@unhead/vue'
import { defineNuxtPlugin } from '#app/nuxt'
export default defineNuxtPlugin((nuxtApp) => {
export default defineNuxtPlugin({
name: 'nuxt:cross-origin-prefetch',
setup (nuxtApp) {
const externalURLs = ref(new Set<string>())
function generateRules () {
return {
@ -32,4 +34,5 @@ export default defineNuxtPlugin((nuxtApp) => {
})
}
})
}
})

View File

@ -1,6 +1,10 @@
import { createDebugger } from 'hookable'
import { defineNuxtPlugin } from '#app/nuxt'
export default defineNuxtPlugin((nuxtApp) => {
export default defineNuxtPlugin({
name: 'nuxt:debug',
enforce: 'pre',
setup (nuxtApp) {
createDebugger(nuxtApp.hooks, { tag: 'nuxt-app' })
}
})

View File

@ -1,9 +1,11 @@
import { parseURL } from 'ufo'
import { defineNuxtPlugin } from '#app/nuxt'
import { loadPayload, isPrerendered } from '#app/composables/payload'
import { isPrerendered, loadPayload } from '#app/composables/payload'
import { useRouter } from '#app/composables/router'
export default defineNuxtPlugin((nuxtApp) => {
export default defineNuxtPlugin({
name: 'nuxt:payload',
setup (nuxtApp) {
// Only enable behavior if initial page is prerendered
// TODO: Support hybrid and dev
if (!isPrerendered()) {
@ -24,4 +26,5 @@ export default defineNuxtPlugin((nuxtApp) => {
if (!payload) { return }
Object.assign(nuxtApp.static.data, payload.data)
})
}
})

View File

@ -1,6 +1,8 @@
import { defineNuxtPlugin } from '#app/nuxt'
export default defineNuxtPlugin((nuxtApp) => {
export default defineNuxtPlugin({
name: 'nuxt:webpack-preload',
setup (nuxtApp) {
nuxtApp.vueApp.mixin({
beforeCreate () {
const { _registeredComponents } = this.$nuxt.ssrContext
@ -8,4 +10,5 @@ export default defineNuxtPlugin((nuxtApp) => {
_registeredComponents.add(__moduleIdentifier)
}
})
}
})

View File

@ -1,7 +1,10 @@
import { defineNuxtPlugin } from '#app/nuxt'
import { defineNuxtPlugin, useNuxtApp } from '#app/nuxt'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:mounted', () => {
export default defineNuxtPlugin({
name: 'nuxt:restore-state',
hooks: {
'app:mounted' () {
const nuxtApp = useNuxtApp()
try {
const state = sessionStorage.getItem('nuxt:reload:state')
if (state) {
@ -9,5 +12,6 @@ export default defineNuxtPlugin((nuxtApp) => {
Object.assign(nuxtApp.payload.state, JSON.parse(state)?.state)
}
} catch {}
})
}
}
})

View File

@ -0,0 +1,27 @@
import { reactive, ref, shallowReactive, shallowRef } from 'vue'
import { definePayloadReviver, getNuxtClientPayload } from '#app/composables/payload'
import { createError } from '#app/composables/error'
import { callWithNuxt, defineNuxtPlugin } from '#app/nuxt'
const revivers = {
NuxtError: (data: any) => createError(data),
EmptyShallowRef: (data: any) => shallowRef(JSON.parse(data)),
EmptyRef: (data: any) => ref(JSON.parse(data)),
ShallowRef: (data: any) => shallowRef(data),
ShallowReactive: (data: any) => shallowReactive(data),
Ref: (data: any) => ref(data),
Reactive: (data: any) => reactive(data)
}
export default defineNuxtPlugin({
name: 'nuxt:revive-payload:client',
order: -30,
async setup (nuxtApp) {
for (const reviver in revivers) {
definePayloadReviver(reviver, revivers[reviver as keyof typeof revivers])
}
Object.assign(nuxtApp.payload, await callWithNuxt(nuxtApp, getNuxtClientPayload, []))
// For backwards compatibility - TODO: remove later
window.__NUXT__ = nuxtApp.payload
}
})

View File

@ -0,0 +1,24 @@
import { isReactive, isRef, isShallow, toRaw } from 'vue'
import { definePayloadReducer } from '#app/composables/payload'
import { isNuxtError } from '#app/composables/error'
import { defineNuxtPlugin } from '#app/nuxt'
/* Defining a plugin that will be used by the Nuxt framework. */
const reducers = {
NuxtError: (data: any) => isNuxtError(data) && data.toJSON(),
EmptyShallowRef: (data: any) => isRef(data) && isShallow(data) && !data.value && JSON.stringify(data.value),
EmptyRef: (data: any) => isRef(data) && !data.value && JSON.stringify(data.value),
ShallowRef: (data: any) => isRef(data) && isShallow(data) && data.value,
ShallowReactive: (data: any) => isReactive(data) && isShallow(data) && toRaw(data),
Ref: (data: any) => isRef(data) && data.value,
Reactive: (data: any) => isReactive(data) && toRaw(data)
}
export default defineNuxtPlugin({
name: 'nuxt:revive-payload:server',
setup () {
for (const reducer in reducers) {
definePayloadReducer(reducer, reducers[reducer as keyof typeof reducers])
}
}
})

View File

@ -1,5 +1,5 @@
import { reactive, h, isReadonly } from 'vue'
import { parseURL, stringifyParsedURL, parseQuery, stringifyQuery, withoutBase, isEqual, joinURL } from 'ufo'
import { h, isReadonly, reactive } from 'vue'
import { isEqual, joinURL, parseQuery, parseURL, stringifyParsedURL, stringifyQuery, withoutBase } from 'ufo'
import { createError } from 'h3'
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig } from '../nuxt'
import { clearError, showError } from '../composables/error'
@ -96,7 +96,10 @@ interface Router {
removeRoute: (name: string) => void
}
export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
export default defineNuxtPlugin<{ route: Route, router: Router }>({
name: 'nuxt:router',
enforce: 'pre',
setup (nuxtApp) {
const initialURL = process.client
? withoutBase(window.location.pathname, useRuntimeConfig().app.baseURL) + window.location.search + window.location.hash
: nuxtApp.ssrContext!.url
@ -110,7 +113,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
error: []
}
const registerHook = <T extends keyof RouterHooks>(hook: T, guard: RouterHooks[T]) => {
const registerHook = <T extends keyof RouterHooks> (hook: T, guard: RouterHooks[T]) => {
hooks[hook].push(guard)
return () => hooks[hook].splice(hooks[hook].indexOf(guard), 1)
}
@ -268,4 +271,5 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
router
}
}
}
})

View File

@ -0,0 +1,55 @@
import { useRouter } from '#app/composables/router'
import { defineNuxtPlugin } from '#app/nuxt'
export default defineNuxtPlugin((nuxtApp) => {
if (!document.startViewTransition) { return }
let finishTransition: undefined | (() => void)
let abortTransition: undefined | (() => void)
const router = useRouter()
router.beforeResolve((to) => {
if (to.meta.pageTransition === false) { return }
const promise = new Promise<void>((resolve, reject) => {
finishTransition = resolve
abortTransition = reject
})
let changeRoute: () => void
const ready = new Promise<void>(resolve => (changeRoute = resolve))
const transition = document.startViewTransition!(() => {
changeRoute()
return promise
})
transition.finished.then(() => {
abortTransition = undefined
finishTransition = undefined
})
return ready
})
nuxtApp.hook('vue:error', () => {
abortTransition?.()
abortTransition = undefined
})
nuxtApp.hook('page:finish', () => {
finishTransition?.()
finishTransition = undefined
})
})
declare global {
interface Document {
startViewTransition?: (callback: () => Promise<void> | void) => {
finished: Promise<void>
updateCallbackDone: Promise<void>
ready: Promise<void>
}
}
}

View File

@ -1,11 +1,11 @@
import { statSync } from 'node:fs'
import { relative, resolve } from 'pathe'
import { defineNuxtModule, resolveAlias, addTemplate, addPluginTemplate, updateTemplates } from '@nuxt/kit'
import { addPluginTemplate, addTemplate, defineNuxtModule, resolveAlias, updateTemplates } from '@nuxt/kit'
import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema'
import { distDir } from '../dirs'
import { clientFallbackAutoIdPlugin } from './client-fallback-auto-id'
import { componentsPluginTemplate, componentsTemplate, componentsIslandsTemplate, componentsTypeTemplate } from './templates'
import { componentsIslandsTemplate, componentsPluginTemplate, componentsTemplate, componentsTypeTemplate } from './templates'
import { scanComponents } from './scan'
import { loaderPlugin } from './loader'
import { TreeShakeTemplatePlugin } from './tree-shake'

View File

@ -1,4 +1,4 @@
import { defineComponent, createStaticVNode, computed, h, watch } from 'vue'
import { computed, createStaticVNode, defineComponent, h, watch } from 'vue'
import { debounce } from 'perfect-debounce'
import { hash } from 'ohash'
import { appendHeader } from 'h3'

View File

@ -1,4 +1,4 @@
import { basename, extname, join, dirname, relative } from 'pathe'
import { basename, dirname, extname, join, relative } from 'pathe'
import { globby } from 'globby'
import { pascalCase, splitByCase } from 'scule'
import { isIgnored } from '@nuxt/kit'

View File

@ -40,11 +40,14 @@ const components = ${genObjectFromRawEntries(globalComponents.map((c) => {
return [c.pascalName, `defineAsyncComponent(${genDynamicImport(c.filePath, { comment })}.then(c => ${exp}))`]
}))}
export default defineNuxtPlugin(nuxtApp => {
export default defineNuxtPlugin({
name: 'nuxt:global-components',
setup (nuxtApp) {
for (const name in components) {
nuxtApp.vueApp.component(name, components[name])
nuxtApp.vueApp.component('Lazy' + name, components[name])
}
}
})
`
}

View File

@ -2,7 +2,7 @@ import { pathToFileURL } from 'node:url'
import { parseURL } from 'ufo'
import MagicString from 'magic-string'
import { walk } from 'estree-walker'
import type { CallExpression, Property, Identifier, MemberExpression, Literal, ReturnStatement, VariableDeclaration, ObjectExpression, Node, Pattern, AssignmentProperty, Program } from 'estree'
import type { AssignmentProperty, CallExpression, Identifier, Literal, MemberExpression, Node, ObjectExpression, Pattern, Program, Property, ReturnStatement, VariableDeclaration } from 'estree'
import { createUnplugin } from 'unplugin'
import type { Component } from '@nuxt/schema'
import { resolve } from 'pathe'

View File

@ -1,7 +1,7 @@
import { promises as fsp } from 'node:fs'
import { dirname, resolve, join } from 'pathe'
import { dirname, join, resolve } from 'pathe'
import { defu } from 'defu'
import { findPath, resolveFiles, normalizePlugin, normalizeTemplate, compileTemplate, templateUtils, tryResolveModule, resolvePath, resolveAlias } from '@nuxt/kit'
import { compileTemplate, findPath, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath, templateUtils, tryResolveModule } from '@nuxt/kit'
import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from 'nuxt/schema'
import * as defaultTemplates from './templates'

View File

@ -5,7 +5,7 @@ import { debounce } from 'perfect-debounce'
import { normalize } from 'pathe'
import type { Nuxt } from 'nuxt/schema'
import { createApp, generateApp as _generateApp } from './app'
import { generateApp as _generateApp, createApp } from './app'
export async function build (nuxt: Nuxt) {
const app = createApp(nuxt)
@ -44,6 +44,10 @@ export async function build (nuxt: Nuxt) {
}
function watch (nuxt: Nuxt) {
if (nuxt.options.debug) {
console.time('[nuxt] builder:chokidar:watch')
}
const watcher = chokidar.watch(nuxt.options._layers.map(i => i.config.srcDir as string).filter(Boolean), {
...nuxt.options.watchers.chokidar,
cwd: nuxt.options.srcDir,
@ -55,6 +59,10 @@ function watch (nuxt: Nuxt) {
]
})
if (nuxt.options.debug) {
watcher.on('ready', () => console.timeEnd('[nuxt] builder:chokidar:watch'))
}
watcher.on('all', (event, path) => nuxt.callHook('builder:watch', event, normalize(path)))
nuxt.hook('close', () => watcher.close())
return watcher

View File

@ -0,0 +1,36 @@
import { addDependency } from 'nypm'
import { isPackageExists } from 'local-pkg'
import { logger } from '@nuxt/kit'
import prompts from 'prompts'
export async function ensurePackageInstalled (rootDir: string, name: string, searchPaths?: string[]) {
if (isPackageExists(name, { paths: searchPaths })) {
return true
}
logger.info(`Package ${name} is missing`)
const { confirm } = await prompts({
type: 'confirm',
name: 'confirm',
message: `Do you want to install ${name} package?`,
initial: true
})
if (!confirm) {
return false
}
logger.info(`Installing ${name}...`)
try {
await addDependency(name, {
cwd: rootDir,
dev: true
})
logger.success(`Installed ${name}`)
return true
} catch (err) {
logger.error(err)
return false
}
}

View File

@ -1,7 +1,7 @@
import { existsSync, promises as fsp } from 'node:fs'
import { resolve, join, relative } from 'pathe'
import { createNitro, createDevServer, build, prepare, copyPublicAssets, writeTypes, scanHandlers, prerender } from 'nitropack'
import type { NitroConfig, Nitro } from 'nitropack'
import { join, relative, resolve } from 'pathe'
import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack'
import type { Nitro, NitroConfig } from 'nitropack'
import { logger, resolvePath } from '@nuxt/kit'
import escapeRE from 'escape-string-regexp'
import { defu } from 'defu'
@ -37,6 +37,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
dev: nuxt.options.dev,
buildDir: nuxt.options.buildDir,
imports: {
autoImports: nuxt.options.imports.autoImport,
imports: [
{
as: '__buildAssetsURL',
@ -126,6 +127,18 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
'nuxt/dist',
'nuxt3/dist',
distDir
],
traceInclude: [
// force include files used in generated code from the runtime-compiler
...(nuxt.options.experimental.runtimeVueCompiler && !nuxt.options.experimental.externalVue)
? [
...nuxt.options.modulesDir.reduce<string[]>((targets, path) => {
const serverRendererPath = resolve(path, 'vue/server-renderer/index.js')
if (existsSync(serverRendererPath)) { targets.push(serverRendererPath) }
return targets
}, [])
]
: []
]
},
alias: {
@ -137,11 +150,15 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
vue: await resolvePath(`vue/dist/vue.cjs${nuxt.options.dev ? '' : '.prod'}.js`)
},
// Vue 3 mocks
...nuxt.options.experimental.runtimeVueCompiler || nuxt.options.experimental.externalVue
? {}
: {
'estree-walker': 'unenv/runtime/mock/proxy',
'@babel/parser': 'unenv/runtime/mock/proxy',
'@vue/compiler-core': 'unenv/runtime/mock/proxy',
'@vue/compiler-dom': 'unenv/runtime/mock/proxy',
'@vue/compiler-ssr': 'unenv/runtime/mock/proxy',
'@vue/compiler-ssr': 'unenv/runtime/mock/proxy'
},
'@vue/devtools-api': 'vue-devtools-stub',
// Paths
@ -156,6 +173,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
'process.env.NUXT_NO_SCRIPTS': !!nuxt.options.experimental.noScripts && !nuxt.options.dev,
'process.env.NUXT_INLINE_STYLES': !!nuxt.options.experimental.inlineSSRStyles,
'process.env.NUXT_PAYLOAD_EXTRACTION': !!nuxt.options.experimental.payloadExtraction,
'process.env.NUXT_JSON_PAYLOADS': !!nuxt.options.experimental.renderJsonPayloads,
'process.env.NUXT_COMPONENT_ISLANDS': !!nuxt.options.experimental.componentIslands,
'process.dev': nuxt.options.dev,
__VUE_PROD_DEVTOOLS__: false
@ -166,6 +184,9 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
}
})
// Resolve user-provided paths
nitroConfig.srcDir = resolve(nuxt.options.rootDir, nuxt.options.srcDir, nitroConfig.srcDir!)
// Add head chunk for SPA renders
const head = createHeadCore()
head.push(nuxt.options.app.head)
@ -231,6 +252,37 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
nuxt.callHook('prerender:routes', { routes })
})
// Enable runtime compiler client side
if (nuxt.options.experimental.runtimeVueCompiler) {
nuxt.hook('vite:extendConfig', (config, { isClient }) => {
if (isClient) {
if (Array.isArray(config.resolve!.alias)) {
config.resolve!.alias.push({
find: 'vue',
replacement: 'vue/dist/vue.esm-bundler'
})
} else {
config.resolve!.alias = {
...config.resolve!.alias,
vue: 'vue/dist/vue.esm-bundler'
}
}
}
})
nuxt.hook('webpack:config', (configuration) => {
const clientConfig = configuration.find(config => config.name === 'client')
if (!clientConfig!.resolve) { clientConfig!.resolve!.alias = {} }
if (Array.isArray(clientConfig!.resolve!.alias)) {
clientConfig!.resolve!.alias.push({
name: 'vue',
alias: 'vue/dist/vue.esm-bundler'
})
} else {
clientConfig!.resolve!.alias!.vue = 'vue/dist/vue.esm-bundler'
}
})
}
// Setup handlers
const devMiddlewareHandler = dynamicEventHandler()
nitro.options.devHandlers.unshift({ handler: devMiddlewareHandler })

View File

@ -1,8 +1,8 @@
import { join, normalize, relative, resolve } from 'pathe'
import { createHooks, createDebugger } from 'hookable'
import { createDebugger, createHooks } from 'hookable'
import type { LoadNuxtOptions } from '@nuxt/kit'
import { resolvePath, resolveAlias, resolveFiles, loadNuxtConfig, nuxtCtx, installModule, addComponent, addVitePlugin, addWebpackPlugin, tryResolveModule, addPlugin } from '@nuxt/kit'
import type { Nuxt, NuxtOptions, NuxtHooks } from 'nuxt/schema'
import { addComponent, addPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolvePath, tryResolveModule } from '@nuxt/kit'
import type { Nuxt, NuxtHooks, NuxtOptions } from 'nuxt/schema'
import escapeRE from 'escape-string-regexp'
import fse from 'fs-extra'
@ -98,8 +98,12 @@ async function initNuxt (nuxt: Nuxt) {
nuxt.hook('modules:done', () => {
// Add unctx transform
addVitePlugin(UnctxTransformPlugin(nuxt).vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
addWebpackPlugin(UnctxTransformPlugin(nuxt).webpack({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
const options = {
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client,
transformerOptions: nuxt.options.optimization.asyncTransforms
}
addVitePlugin(UnctxTransformPlugin.vite(options))
addWebpackPlugin(UnctxTransformPlugin.webpack(options))
// Add composable tree-shaking optimisations
const serverTreeShakeOptions: TreeShakeComposablesPluginOptions = {
@ -281,6 +285,19 @@ async function initNuxt (nuxt: Nuxt) {
addPlugin(resolve(nuxt.options.appDir, 'plugins/restore-state.client'))
}
// Add experimental automatic view transition api support
if (nuxt.options.experimental.viewTransition) {
addPlugin(resolve(nuxt.options.appDir, 'plugins/view-transitions.client'))
}
// Add experimental support for custom types in JSON payload
if (nuxt.options.experimental.renderJsonPayloads) {
nuxt.hooks.hook('modules:done', () => {
addPlugin(resolve(nuxt.options.appDir, 'plugins/revive-payload.client'))
addPlugin(resolve(nuxt.options.appDir, 'plugins/revive-payload.server'))
})
}
// Track components used to render for webpack
if (nuxt.options.builder === '@nuxt/webpack-builder') {
addPlugin(resolve(nuxt.options.appDir, 'plugins/preload.server'))
@ -359,6 +376,15 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
options._modules.push('@nuxt/telemetry')
}
// Nuxt DevTools is currently opt-in
if (options.devtools === true || (options.devtools && options.devtools.enabled !== false)) {
if (await import('./features').then(r => r.ensurePackageInstalled(options.rootDir, '@nuxt/devtools', options.modulesDir))) {
options._modules.push('@nuxt/devtools')
} else {
logger.warn('Failed to install `@nuxt/devtools`, please install it manually, or disable `devtools` in `nuxt.config`')
}
}
const nuxt = createNuxt(options)
if (nuxt.options.debug) {

View File

@ -1,30 +1,42 @@
import { normalize } from 'pathe'
import { pathToFileURL } from 'node:url'
import { parseQuery, parseURL } from 'ufo'
import type { TransformerOptions } from 'unctx/transform'
import { createTransformer } from 'unctx/transform'
import { createUnplugin } from 'unplugin'
import type { Nuxt, NuxtApp } from 'nuxt/schema'
const TRANSFORM_MARKER = '/* _processed_nuxt_unctx_transform */\n'
export const UnctxTransformPlugin = (nuxt: Nuxt) => {
const transformer = createTransformer({
asyncFunctions: ['defineNuxtPlugin', 'defineNuxtRouteMiddleware']
})
interface UnctxTransformPluginOptions {
sourcemap?: boolean
transformerOptions: TransformerOptions
}
let app: NuxtApp | undefined
nuxt.hook('app:resolve', (_app) => { app = _app })
return createUnplugin((options: { sourcemap?: boolean } = {}) => ({
export const UnctxTransformPlugin = createUnplugin((options: UnctxTransformPluginOptions) => {
const transformer = createTransformer(options.transformerOptions)
return {
name: 'unctx:transform',
enforce: 'post',
transformInclude (id) {
if (id.includes('macro=true')) { return true }
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
const query = parseQuery(search)
id = normalize(id).replace(/\?.*$/, '')
return app?.plugins.some(i => i.src === id) || app?.middleware.some(m => m.path === id)
// Vue files
if (
pathname.endsWith('.vue') ||
'macro' in query ||
('vue' in query && (query.type === 'template' || query.type === 'script' || 'setup' in query))
) {
return true
}
// JavaScript files
if (pathname.match(/\.((c|m)?j|t)sx?$/g)) {
return true
}
},
transform (code, id) {
// TODO: needed for webpack - update transform in unctx/unplugin?
if (code.startsWith(TRANSFORM_MARKER)) { return }
if (code.startsWith(TRANSFORM_MARKER) || !transformer.shouldTransform(code)) { return }
const result = transformer.transform(code)
if (result) {
return {
@ -35,5 +47,5 @@ export const UnctxTransformPlugin = (nuxt: Nuxt) => {
}
}
}
}))
}
}
})

View File

@ -1,9 +1,9 @@
import { joinURL, withQuery } from 'ufo'
import type { NitroErrorHandler } from 'nitropack'
import type { H3Error } from 'h3'
import { setResponseHeader, getRequestHeaders, setResponseStatus } from 'h3'
import { getRequestHeaders, setResponseHeader, setResponseStatus } from 'h3'
import { useNitroApp, useRuntimeConfig } from '#internal/nitro'
import { normalizeError, isJsonRequest } from '#internal/nitro/utils'
import { isJsonRequest, normalizeError } from '#internal/nitro/utils'
export default <NitroErrorHandler> async function errorhandler (error: H3Error, event) {
// Parse and normalize error

View File

@ -2,14 +2,15 @@ import { createRenderer, renderResourceHeaders } from 'vue-bundle-renderer/runti
import type { RenderResponse } from 'nitropack'
import type { Manifest } from 'vite'
import type { H3Event } from 'h3'
import { appendHeader, getQuery, writeEarlyHints, readBody, createError } from 'h3'
import { appendHeader, createError, getQuery, readBody, writeEarlyHints } from 'h3'
import devalue from '@nuxt/devalue'
import { stringify, uneval } from 'devalue'
import destr from 'destr'
import { joinURL, withoutTrailingSlash } from 'ufo'
import { renderToString as _renderToString } from 'vue/server-renderer'
import { hash } from 'ohash'
import { useRuntimeConfig, defineRenderHandler, getRouteRules } from '#internal/nitro'
import { defineRenderHandler, getRouteRules, useRuntimeConfig } from '#internal/nitro'
import { useNitroApp } from '#internal/nitro/app'
// eslint-disable-next-line import/no-restricted-paths
@ -121,14 +122,15 @@ const getSPARenderer = lazyCachedFunction(async () => {
const renderToString = (ssrContext: NuxtSSRContext) => {
const config = useRuntimeConfig()
ssrContext!.payload = {
_errors: {},
serverRendered: false,
config: {
public: config.public,
app: config.app
},
data: {},
state: {}
}
ssrContext.config = {
public: config.public,
app: config.app
}
ssrContext!.renderMeta = ssrContext!.renderMeta ?? getStaticRenderedHead
return Promise.resolve(result)
}
@ -160,7 +162,7 @@ async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
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 = /\/_payload(\.[a-zA-Z0-9]+)?.js(\?.*)?$/
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}>$`)
const PRERENDER_NO_SSR_ROUTES = new Set(['/index.html', '/200.html', '/404.html'])
@ -219,12 +221,13 @@ export default defineRenderHandler(async (event) => {
error: !!ssrError,
nuxt: undefined!, /* NuxtApp */
payload: (ssrError ? { error: ssrError } : {}) as NuxtSSRContext['payload'],
_payloadReducers: {},
islandContext
}
// Whether we are prerendering route
const _PAYLOAD_EXTRACTION = process.env.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !ssrContext.noSSR
const payloadURL = _PAYLOAD_EXTRACTION ? joinURL(useRuntimeConfig().app.baseURL, url, '_payload.js') : undefined
const payloadURL = _PAYLOAD_EXTRACTION ? joinURL(useRuntimeConfig().app.baseURL, url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js') : undefined
if (process.env.prerender) {
ssrContext.payload.prerenderedAt = Date.now()
}
@ -260,7 +263,7 @@ export default defineRenderHandler(async (event) => {
if (_PAYLOAD_EXTRACTION) {
// Hint nitro to prerender payload for this route
appendHeader(event, 'x-nitro-prerender', joinURL(url, '_payload.js'))
appendHeader(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))
}
@ -273,14 +276,18 @@ export default defineRenderHandler(async (event) => {
? await renderInlineStyles(ssrContext.modules ?? ssrContext._registeredComponents ?? [])
: ''
const NO_SCRIPTS = process.env.NUXT_NO_SCRIPTS || routeOptions.experimentalNoScripts
// Create render context
const htmlContext: NuxtRenderHTMLContext = {
island: Boolean(islandContext),
htmlAttrs: normalizeChunks([renderedMeta.htmlAttrs]),
head: normalizeChunks([
renderedMeta.headTags,
_PAYLOAD_EXTRACTION ? `<link rel="modulepreload" href="${payloadURL}">` : null,
_rendered.renderResourceHints(),
process.env.NUXT_JSON_PAYLOADS
? _PAYLOAD_EXTRACTION ? `<link rel="modulepreload" href="${payloadURL}">` : null
: _PAYLOAD_EXTRACTION ? `<link rel="preload" as="fetch" crossorigin="anonymous" href="${payloadURL}">` : null,
NO_SCRIPTS ? null : _rendered.renderResourceHints(),
_rendered.renderStyles(),
inlinedStyles,
ssrContext.styles
@ -292,13 +299,17 @@ export default defineRenderHandler(async (event) => {
]),
body: [_rendered.html],
bodyAppend: normalizeChunks([
process.env.NUXT_NO_SCRIPTS
NO_SCRIPTS
? undefined
: (_PAYLOAD_EXTRACTION
? `<script type="module">import p from "${payloadURL}";window.__NUXT__={...p,...(${devalue(splitPayload(ssrContext).initial)})}</script>`
: `<script>window.__NUXT__=${devalue(ssrContext.payload)}</script>`
? process.env.NUXT_JSON_PAYLOADS
? renderPayloadJsonScript({ id: '__NUXT_DATA__', ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL })
: renderPayloadScript({ ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL })
: process.env.NUXT_JSON_PAYLOADS
? renderPayloadJsonScript({ id: '__NUXT_DATA__', ssrContext, data: ssrContext.payload })
: renderPayloadScript({ ssrContext, data: ssrContext.payload })
),
_rendered.renderScripts(),
routeOptions.experimentalNoScripts ? undefined : _rendered.renderScripts(),
// Note: bodyScripts may contain tags other than <script>
renderedMeta.bodyScripts
])
@ -420,16 +431,39 @@ async function renderInlineStyles (usedModules: Set<string> | string[]) {
function renderPayloadResponse (ssrContext: NuxtSSRContext) {
return <RenderResponse> {
body: `export default ${devalue(splitPayload(ssrContext).payload)}`,
body: process.env.NUXT_JSON_PAYLOADS
? stringify(splitPayload(ssrContext).payload, ssrContext._payloadReducers)
: `export default ${devalue(splitPayload(ssrContext).payload)}`,
statusCode: ssrContext.event.node.res.statusCode,
statusMessage: ssrContext.event.node.res.statusMessage,
headers: {
'content-type': 'text/javascript;charset=UTF-8',
'content-type': process.env.NUXT_JSON_PAYLOADS ? 'application/json;charset=utf-8' : 'text/javascript;charset=utf-8',
'x-powered-by': 'Nuxt'
}
}
}
function renderPayloadJsonScript (opts: { id: string, ssrContext: NuxtSSRContext, data?: any, src?: string }) {
const attrs = [
'type="application/json"',
`id="${opts.id}"`,
`data-ssr="${!(process.env.NUXT_NO_SSR || opts.ssrContext.noSSR)}"`,
opts.src ? `data-src="${opts.src}"` : ''
].filter(Boolean)
const contents = opts.data ? stringify(opts.data, opts.ssrContext._payloadReducers) : ''
return `<script ${attrs.join(' ')}>${contents}</script>` +
`<script>window.__NUXT__={};window.__NUXT__.config=${uneval(opts.ssrContext.config)}</script>`
}
function renderPayloadScript (opts: { ssrContext: NuxtSSRContext, data?: any, src?: string }) {
opts.data.config = opts.ssrContext.config
const _PAYLOAD_EXTRACTION = process.env.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !opts.ssrContext.noSSR
if (_PAYLOAD_EXTRACTION) {
return `<script type="module">import p from "${opts.src}";window.__NUXT__={...p,...(${devalue(opts.data)})}</script>`
}
return `<script>window.__NUXT__=${devalue(opts.data)}</script>`
}
function splitPayload (ssrContext: NuxtSSRContext) {
const { data, prerenderedAt, ...initial } = ssrContext.payload
return {

View File

@ -1,13 +1,13 @@
import { existsSync } from 'node:fs'
import { writeFile, mkdir } from 'node:fs/promises'
import { mkdir, writeFile } from 'node:fs/promises'
import { dirname, resolve } from 'pathe'
import chokidar from 'chokidar'
import { defu } from 'defu'
import { debounce } from 'perfect-debounce'
import { defineNuxtModule, createResolver } from '@nuxt/kit'
import { createResolver, defineNuxtModule } from '@nuxt/kit'
import {
resolveSchema as resolveUntypedSchema,
generateTypes
generateTypes,
resolveSchema as resolveUntypedSchema
} from 'untyped'
import type { Schema, SchemaDefinition } from 'untyped'
// @ts-ignore

View File

@ -1,6 +1,6 @@
import { genArrayFromRaw, genDynamicImport, genExport, genImport, genObjectFromRawEntries, genString, genSafeVariableName } from 'knitwork'
import { genArrayFromRaw, genDynamicImport, genExport, genImport, genObjectFromRawEntries, genSafeVariableName, genString } from 'knitwork'
import { isAbsolute, join, relative, resolve } from 'pathe'
import { resolveSchema, generateTypes } from 'untyped'
import { generateTypes, resolveSchema } from 'untyped'
import escapeRE from 'escape-string-regexp'
import { hash } from 'ohash'
import { camelCase } from 'scule'
@ -40,6 +40,11 @@ export const errorComponentTemplate: NuxtTemplate<TemplateContext> = {
filename: 'error-component.mjs',
getContents: ctx => genExport(ctx.app.errorComponent!, ['default'])
}
// TODO: Use an alias
export const testComponentWrapperTemplate = {
filename: 'test-component-wrapper.mjs',
getContents: (ctx: TemplateContext) => genExport(resolve(ctx.nuxt.options.appDir, 'components/test-component-wrapper'), ['default'])
}
export const cssTemplate: NuxtTemplate<TemplateContext> = {
filename: 'css.mjs',
@ -219,10 +224,10 @@ type IsAny<T> = 0 extends 1 & T ? true : false
type MergedAppConfig<Resolved extends Record<string, any>, Custom extends Record<string, any>> = {
[K in keyof Resolved]: K extends keyof Custom
? Custom[K] extends Record<string, any>
? IsAny<Custom[K]> extends true
? Resolved[K]
: Resolved[K] extends Record<string, any>
: Custom[K] extends Record<string, any>
? Resolved[K] extends Record<string, any>
? MergedAppConfig<Resolved[K], Custom[K]>
: Exclude<Custom[K], undefined>
: Exclude<Custom[K], undefined>
@ -291,6 +296,7 @@ export const nuxtConfigTemplate = {
getContents: (ctx: TemplateContext) => {
return [
...Object.entries(ctx.nuxt.options.app).map(([k, v]) => `export const ${camelCase('app-' + k)} = ${JSON.stringify(v)}`),
`export const renderJsonPayloads = ${!!ctx.nuxt.options.experimental.renderJsonPayloads}`,
`export const devPagesDir = ${ctx.nuxt.options.dev ? JSON.stringify(ctx.nuxt.options.dir.pages) : 'null'}`
].join('\n\n')
}

View File

@ -14,14 +14,6 @@ export default defineNuxtModule({
// Transpile @unhead/vue
nuxt.options.build.transpile.push('@unhead/vue')
// TODO: remove alias in v3.4
nuxt.options.alias['#head'] = nuxt.options.alias['#app']
nuxt.hook('prepare:types', ({ tsConfig }) => {
tsConfig.compilerOptions = tsConfig.compilerOptions || {}
delete tsConfig.compilerOptions.paths['#head']
delete tsConfig.compilerOptions.paths['#head/*']
})
// Register components
const componentsPath = resolve(runtimeDir, 'components')
for (const componentName of components) {

View File

@ -4,7 +4,9 @@ import { defineNuxtPlugin } from '#app/nuxt'
// @ts-expect-error untyped
import { appHead } from '#build/nuxt.config.mjs'
export default defineNuxtPlugin((nuxtApp) => {
export default defineNuxtPlugin({
name: 'nuxt:head',
setup (nuxtApp) {
const createHead = process.server ? createServerHead : createClientHead
const head = createHead()
head.push(appHead)
@ -38,4 +40,5 @@ export default defineNuxtPlugin((nuxtApp) => {
}
}
}
}
})

View File

@ -2,7 +2,10 @@
import { polyfillAsVueUseHead } from '@unhead/vue/polyfill'
import { defineNuxtPlugin } from '#app/nuxt'
export default defineNuxtPlugin((nuxtApp) => {
export default defineNuxtPlugin({
name: 'nuxt:vueuse-head-polyfill',
setup (nuxtApp) {
// avoid breaking ecosystem dependencies using low-level @vueuse/head APIs
polyfillAsVueUseHead(nuxtApp.vueApp._context.provides.usehead)
}
})

View File

@ -1,8 +1,8 @@
import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, resolveAlias, useNuxt, updateTemplates } from '@nuxt/kit'
import { isAbsolute, join, relative, resolve, normalize } from 'pathe'
import { addTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, resolveAlias, updateTemplates, useNuxt } from '@nuxt/kit'
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
import type { Import, Unimport } from 'unimport'
import { createUnimport, scanDirExports } from 'unimport'
import type { ImportsOptions, ImportPresetWithDeprecation } from 'nuxt/schema'
import type { ImportPresetWithDeprecation, ImportsOptions } from 'nuxt/schema'
import { TransformPlugin } from './transform'
import { defaultPresets } from './presets'

View File

@ -23,6 +23,7 @@ const appPreset = defineUnimportPreset({
'defineNuxtComponent',
'useNuxtApp',
'defineNuxtPlugin',
'definePayloadPlugin',
'reloadNuxtApp',
'useRuntimeConfig',
'useState',
@ -55,7 +56,9 @@ const appPreset = defineUnimportPreset({
'prefetchComponents',
'loadPayload',
'preloadPayload',
'isPrerendered'
'isPrerendered',
'definePayloadReducer',
'definePayloadReviver'
]
})

View File

@ -1,13 +1,13 @@
import { existsSync, readdirSync } from 'node:fs'
import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath, addComponent, updateTemplates } from '@nuxt/kit'
import { addComponent, addPlugin, addTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, updateTemplates } from '@nuxt/kit'
import { join, relative, resolve } from 'pathe'
import { genString, genImport, genObjectFromRawEntries } from 'knitwork'
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
import escapeRE from 'escape-string-regexp'
import { joinURL } from 'ufo'
import type { NuxtApp, NuxtPage } from 'nuxt/schema'
import { distDir } from '../dirs'
import { resolvePagesRoutes, normalizeRoutes } from './utils'
import { normalizeRoutes, resolvePagesRoutes } from './utils'
import type { PageMetaPluginOptions } from './page-meta'
import { PageMetaPlugin } from './page-meta'
@ -156,8 +156,10 @@ export default defineNuxtModule({
layer => resolve(layer.config.srcDir, layer.config.dir?.pages || 'pages')
)
}
nuxt.hook('modules:done', () => {
addVitePlugin(PageMetaPlugin.vite(pageMetaOptions))
addWebpackPlugin(PageMetaPlugin.webpack(pageMetaOptions))
})
// Add prefetching support for middleware & layouts
addPlugin(resolve(runtimeDir, 'plugins/prefetch.client'))

View File

@ -2,8 +2,8 @@ import { pathToFileURL } from 'node:url'
import { createUnplugin } from 'unplugin'
import { parseQuery, parseURL } from 'ufo'
import type { StaticImport } from 'mlly'
import { findStaticImports, findExports, parseStaticImport } from 'mlly'
import type { CallExpression, Identifier, Expression } from 'estree'
import { findExports, findStaticImports, parseStaticImport } from 'mlly'
import type { CallExpression, Expression, Identifier } from 'estree'
import type { Node } from 'estree-walker'
import { walk } from 'estree-walker'
import MagicString from 'magic-string'

View File

@ -1,15 +1,15 @@
import { computed, defineComponent, h, provide, reactive, onMounted, nextTick, Suspense, Transition } from 'vue'
import type { VNode, KeepAliveProps, TransitionProps } from 'vue'
import { Suspense, Transition, computed, defineComponent, h, nextTick, onMounted, provide, reactive } from 'vue'
import type { KeepAliveProps, TransitionProps, VNode } from 'vue'
import { RouterView } from 'vue-router'
import { defu } from 'defu'
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteLocation } from 'vue-router'
import type { RouteLocation, RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router'
import type { RouterViewSlotProps } from './utils'
import { generateRouteKey, wrapInKeepAlive } from './utils'
import { useNuxtApp } from '#app/nuxt'
import { _wrapIf } from '#app/components/utils'
// @ts-ignore
import { appPageTransition as defaultPageTransition, appKeepalive as defaultKeepaliveConfig } from '#build/nuxt.config.mjs'
import { appKeepalive as defaultKeepaliveConfig, appPageTransition as defaultPageTransition } from '#build/nuxt.config.mjs'
export default defineComponent({
name: 'NuxtPage',

View File

@ -1,13 +1,14 @@
import { hasProtocol } from 'ufo'
import { defineNuxtPlugin, useNuxtApp } from '#app/nuxt'
import { defineNuxtPlugin } from '#app/nuxt'
import { useRouter } from '#app/composables/router'
// @ts-ignore
import layouts from '#build/layouts'
// @ts-ignore
import { namedMiddleware } from '#build/middleware'
export default defineNuxtPlugin(() => {
const nuxtApp = useNuxtApp()
export default defineNuxtPlugin({
name: 'nuxt:prefetch',
setup (nuxtApp) {
const router = useRouter()
// Force layout prefetch on route changes
@ -38,4 +39,5 @@ export default defineNuxtPlugin(() => {
layouts[layout]()
}
})
}
})

View File

@ -2,17 +2,17 @@ import { computed, isReadonly, reactive, shallowRef } from 'vue'
import type { Ref } from 'vue'
import type { RouteLocation, Router } from 'vue-router'
import {
createRouter,
createWebHistory,
createMemoryHistory,
createWebHashHistory
createRouter,
createWebHashHistory,
createWebHistory
} from 'vue-router'
import { createError } from 'h3'
import { withoutBase, isEqual } from 'ufo'
import { isEqual, withoutBase } from 'ufo'
import type { PageMeta, RouteMiddleware, Plugin } from '../../../app/index'
import type { PageMeta, Plugin, RouteMiddleware } from '../../../app/index'
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig } from '#app/nuxt'
import { showError, clearError, useError } from '#app/composables/error'
import { clearError, showError, useError } from '#app/composables/error'
import { useRequestEvent } from '#app/composables/ssr'
import { useState } from '#app/composables/state'
import { navigateTo } from '#app/composables/router'
@ -45,7 +45,10 @@ function createCurrentLocation (
return path + search + hash
}
export default defineNuxtPlugin(async (nuxtApp) => {
export default defineNuxtPlugin({
name: 'nuxt:router',
enforce: 'pre',
async setup (nuxtApp) {
let routerBase = useRuntimeConfig().app.baseURL
if (routerOptions.hashMode && !routerBase.includes('#')) {
// allow the user to provide a `#` in the middle: `/base/#/app`
@ -198,4 +201,5 @@ export default defineNuxtPlugin(async (nuxtApp) => {
})
return { provide: { router } }
}
}) as Plugin<{ router: Router }>

View File

@ -1,4 +1,4 @@
import type { RouterScrollBehavior, RouteLocationNormalized } from 'vue-router'
import type { RouteLocationNormalized, RouterScrollBehavior } from 'vue-router'
import { nextTick } from 'vue'
import type { RouterConfig } from 'nuxt/schema'
import { useNuxtApp } from '#app/nuxt'

View File

@ -1,5 +1,5 @@
import { KeepAlive, h } from 'vue'
import type { RouterView, RouteLocationMatched, RouteLocationNormalizedLoaded } from 'vue-router'
import type { RouteLocationMatched, RouteLocationNormalizedLoaded, RouterView } from 'vue-router'
type InstanceOf<T> = T extends new (...args: any[]) => infer R ? R : never
type RouterViewSlot = Exclude<InstanceOf<typeof RouterView>['$slots']['default'], undefined>

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