Merge remote-tracking branch 'origin/main' into feat/import-nuxt-package

This commit is contained in:
Daniel Roe 2023-05-03 22:26:44 +01:00
commit e240d7a9dd
66 changed files with 612 additions and 478 deletions

View File

@ -5,6 +5,9 @@ on:
paths:
- "docs/**"
- ".github/workflows/docs.yml"
push:
branches:
- "renovate/**"
permissions:
contents: read

View File

@ -20,7 +20,7 @@ jobs:
run: git fetch --depth=1 origin "+refs/tags/*:refs/tags/*"
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
registry-url: 'https://registry.npmjs.org'
- name: install
run: yarn --check-files --frozen-lockfile --non-interactive

View File

@ -84,6 +84,13 @@ Whether to launch a server to respond to requests in the test suite.
* Type: `boolean`
* Default: `true`
#### `port`
If provided, set the launched test server port to the value.
* Type: `number | undefined`
* Default: `undefined`
#### `build`
Whether to run a separate build step.

View File

@ -131,5 +131,5 @@ Name | Config File | How To
| [ESLint](https://eslint.org) | `.eslintrc.js` | [More Info](https://eslint.org/docs/latest/user-guide/configuring/configuration-files)
| [Prettier](https://prettier.io) | `.prettierrc.json` | [More Info](https://prettier.io/docs/en/configuration.html)
| [Stylelint](https://stylelint.io) | `.stylelintrc.json` | [More Info](https://stylelint.io/user-guide/configure)
| [TailwindCSS](https://tailwindcss.com) | `tailwind.config.js` | [More Info](https://tailwindcss.nuxt.dev/tailwind/config/)
| [TailwindCSS](https://tailwindcss.com) | `tailwind.config.js` | [More Info](https://tailwindcss.nuxtjs.org/tailwind/config/)
| [Vitest](https://vitest.dev) | `vitest.config.ts` | [More Info](https://vitest.dev/config/)

View File

@ -314,13 +314,13 @@ 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'
import { appendResponseHeader, 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(',')
for (const cookie of cookies) {
appendHeader(event, 'set-cookie', cookie)
appendResponseHeader(event, 'set-cookie', cookie)
}
return res._data
}

View File

@ -67,6 +67,10 @@ When you are ready to remove the error page, you can call the `clearError` helpe
Make sure to check before using anything dependent on Nuxt plugins, such as `$route` or `useRouter`, as if a plugin threw an error, then it won't be re-run until you clear the error.
::
::alert{type="warning"}
If you are running on Node 16 and you set any cookies when rendering your error page, they will [overwrite cookies previously set](https://github.com/nuxt/nuxt/pull/20585). We recommend using a newer version of Node as Node 16 will reach end-of-life in September 2023.
::
### Example
```vue [error.vue]

View File

@ -114,7 +114,7 @@ const { data, pending, error, refresh } = await useFetch('/api/auth/login', {
},
onResponse({ request, response, options }) {
// Process the response data
return response._data
localStorage.setItem('token', response._data.token)
},
onResponseError({ request, response, options }) {
// Handle the response errors

View File

@ -15,12 +15,12 @@ export function useCustomFetch<T> (url: string, options: UseFetchOptions<T> = {}
? { Authorization: `Bearer ${userAuth.value}` }
: {},
onResponse (__ctx) {
// return new myBusinessResponse(response._data)
onResponse (_ctx) {
// _ctx.response._data = new myBusinessResponse(_ctx.response._data)
},
onResponseError (__ctx) {
// return new myBusinessError(error)
onResponseError (_ctx) {
// throw new myBusinessError()
}
}

View File

@ -36,7 +36,7 @@
"nuxt": "workspace:*",
"nuxt3": "workspace:nuxt@*",
"unbuild": "^1.2.1",
"vite": "^4.3.3",
"vite": "^4.3.4",
"vue": "3.2.47",
"magic-string": "^0.30.0"
},
@ -46,7 +46,7 @@
"@nuxt/webpack-builder": "workspace:*",
"@nuxtjs/eslint-config-typescript": "^12.0.0",
"@types/crawler": "^1.2.2",
"@types/node": "^18.16.2",
"@types/node": "^18.16.3",
"@types/semver": "^7.3.13",
"case-police": "^0.5.14",
"changelogen": "^0.5.3",
@ -73,13 +73,13 @@
"typescript": "^5.0.4",
"ufo": "^1.1.1",
"unbuild": "^1.2.1",
"vite": "^4.3.3",
"vite": "^4.3.4",
"vitest": "^0.30.1",
"vue": "3.2.47",
"vue-eslint-parser": "^9.1.1",
"vue-tsc": "^1.6.1"
"vue-eslint-parser": "^9.2.0",
"vue-tsc": "^1.6.3"
},
"packageManager": "pnpm@8.3.1",
"packageManager": "pnpm@8.4.0",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}

View File

@ -32,7 +32,7 @@
"lodash.template": "^4.5.0",
"mlly": "^1.2.0",
"pathe": "^1.1.0",
"pkg-types": "^1.0.2",
"pkg-types": "^1.0.3",
"scule": "^1.0.0",
"semver": "^7.5.0",
"unctx": "^2.3.0",
@ -45,7 +45,7 @@
"@types/semver": "^7.3.13",
"nitropack": "^2.3.3",
"unbuild": "latest",
"vite": "^4.3.3",
"vite": "^4.3.4",
"vitest": "^0.30.1",
"webpack": "^5.81.0"
},

View File

@ -106,9 +106,11 @@ export function extendViteConfig (
/**
* Append webpack plugin to the config.
*/
export function addWebpackPlugin (plugin: WebpackPluginInstance | WebpackPluginInstance[], options?: ExtendWebpackConfigOptions) {
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
export function addWebpackPlugin (pluginOrGetter: WebpackPluginInstance | WebpackPluginInstance[] | (() => WebpackPluginInstance | WebpackPluginInstance[]), options?: ExtendWebpackConfigOptions) {
extendWebpackConfig((config) => {
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter
config.plugins = config.plugins || []
if (Array.isArray(plugin)) {
config.plugins[method](...plugin)
@ -121,9 +123,11 @@ export function addWebpackPlugin (plugin: WebpackPluginInstance | WebpackPluginI
/**
* Append Vite plugin to the config.
*/
export function addVitePlugin (plugin: VitePlugin | VitePlugin[], options?: ExtendViteConfigOptions) {
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
export function addVitePlugin (pluginOrGetter: VitePlugin | VitePlugin[] | (() => VitePlugin | VitePlugin[]), options?: ExtendViteConfigOptions) {
extendViteConfig((config) => {
const method: 'push' | 'unshift' = options?.prepend ? 'unshift' : 'push'
const plugin = typeof pluginOrGetter === 'function' ? pluginOrGetter() : pluginOrGetter
config.plugins = config.plugins || []
if (Array.isArray(plugin)) {
config.plugins[method](...plugin)

View File

@ -44,7 +44,7 @@
"ohash": "^1.1.2",
"pathe": "^1.1.0",
"perfect-debounce": "^0.1.3",
"pkg-types": "^1.0.2",
"pkg-types": "^1.0.3",
"scule": "^1.0.0",
"semver": "^7.5.0",
"ufo": "^1.1.1",

View File

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

View File

@ -2,7 +2,7 @@ import { relative, resolve } from 'pathe'
import { consola } from 'consola'
import { writeTypes } from '../utils/prepare'
import { loadKit } from '../utils/kit'
import { clearDir } from '../utils/fs'
import { clearBuildDir } from '../utils/fs'
import { overrideEnv } from '../utils/env'
import { showVersions } from '../utils/banner'
import { defineNuxtCommand } from './index'
@ -36,7 +36,7 @@ export default defineNuxtCommand({
// Use ? for backward compatibility for Nuxt <= RC.10
const nitro = useNitro?.()
await clearDir(nuxt.options.buildDir)
await clearBuildDir(nuxt.options.buildDir)
await writeTypes(nuxt)

View File

@ -12,7 +12,8 @@ import { writeTypes } from '../utils/prepare'
import { loadKit } from '../utils/kit'
import { importModule } from '../utils/esm'
import { overrideEnv } from '../utils/env'
import { cleanupNuxtDirs, loadNuxtManifest, writeNuxtManifest } from '../utils/nuxt'
import { loadNuxtManifest, writeNuxtManifest } from '../utils/nuxt'
import { clearBuildDir } from '../utils/fs'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
@ -110,7 +111,7 @@ export default defineNuxtCommand({
const previousManifest = await loadNuxtManifest(currentNuxt.options.buildDir)
const newManifest = await writeNuxtManifest(currentNuxt)
if (previousManifest && newManifest && previousManifest._hash !== newManifest._hash) {
await cleanupNuxtDirs(currentNuxt.options.rootDir)
await clearBuildDir(currentNuxt.options.buildDir)
}
}

View File

@ -1,6 +1,6 @@
import { relative, resolve } from 'pathe'
import { consola } from 'consola'
import { clearDir } from '../utils/fs'
import { clearBuildDir } from '../utils/fs'
import { loadKit } from '../utils/kit'
import { writeTypes } from '../utils/prepare'
import { defineNuxtCommand } from './index'
@ -23,7 +23,7 @@ export default defineNuxtCommand({
logLevel: args['log-level']
}
})
await clearDir(nuxt.options.buildDir)
await clearBuildDir(nuxt.options.buildDir)
await buildNuxt(nuxt)
await writeTypes(nuxt)

View File

@ -1,5 +1,5 @@
import { promises as fsp } from 'node:fs'
import { dirname } from 'pathe'
import { existsSync, promises as fsp } from 'node:fs'
import { dirname, join } from 'pathe'
import { consola } from 'consola'
// Check if a file exists
@ -12,11 +12,24 @@ export async function exists (path: string) {
}
}
export async function clearDir (path: string) {
await fsp.rm(path, { recursive: true, force: true })
export async function clearDir (path: string, exclude?: string[]) {
if (!exclude) {
await fsp.rm(path, { recursive: true, force: true })
} else if (existsSync(path)) {
const files = await fsp.readdir(path)
await Promise.all(files.map(async (name) => {
if (!exclude.includes(name)) {
await fsp.rm(join(path, name), { recursive: true, force: true })
}
}))
}
await fsp.mkdir(path, { recursive: true })
}
export function clearBuildDir (path: string) {
return clearDir(path, ['cache', 'analyze'])
}
export async function rmRecursive (paths: string[]) {
await Promise.all(paths.filter(p => typeof p === 'string').map(async (path) => {
consola.debug('Removing recursive path', path)

View File

@ -1,4 +1,5 @@
import type { NuxtConfig } from 'nuxt/schema'
import type { DefineConfig, InputConfig, UserInputConfig, ConfigLayerMeta } from 'c12'
export { NuxtConfig } from 'nuxt/schema'
export declare function defineNuxtConfig(config: NuxtConfig): NuxtConfig
export declare const defineNuxtConfig: DefineConfig<NuxtConfig, ConfigLayerMeta>

View File

@ -52,7 +52,7 @@
"prepack": "unbuild"
},
"dependencies": {
"@nuxt/devalue": "^2.0.0",
"@nuxt/devalue": "^2.0.2",
"@nuxt/kit": "workspace:../kit",
"@nuxt/schema": "workspace:../schema",
"@nuxt/telemetry": "^2.2.0",
@ -61,6 +61,7 @@
"@unhead/ssr": "^1.1.26",
"@unhead/vue": "^1.1.26",
"@vue/shared": "^3.2.47",
"c12": "^1.4.1",
"chokidar": "^3.5.3",
"cookie-es": "^0.5.0",
"defu": "^6.1.2",
@ -107,7 +108,7 @@
"@vitejs/plugin-vue": "^4.2.1",
"acorn": "^8.8.2",
"unbuild": "latest",
"vite": "^4.3.3",
"vite": "^4.3.4",
"vitest": "^0.30.1"
},
"peerDependencies": {

View File

@ -2,7 +2,7 @@ import type { RendererNode } from 'vue'
import { computed, createStaticVNode, defineComponent, getCurrentInstance, h, ref, watch } from 'vue'
import { debounce } from 'perfect-debounce'
import { hash } from 'ohash'
import { appendHeader } from 'h3'
import { appendResponseHeader } from 'h3'
import { useHead } from '@unhead/vue'
// eslint-disable-next-line import/no-restricted-paths
@ -42,7 +42,7 @@ export default defineComponent({
const url = `/__nuxt_island/${props.name}:${hashId.value}`
if (process.server && process.env.prerender) {
// Hint to Nitro to prerender the island component
appendHeader(event, 'x-nitro-prerender', url)
appendResponseHeader(event, 'x-nitro-prerender', url)
}
// TODO: Validate response
return $fetch<NuxtIslandResponse>(url, {

View File

@ -212,7 +212,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
onNuxtReady(() => {
idleId = requestIdleCallback(() => {
if (el?.value?.tagName) {
unobserve = observer!.observe(el.value, async () => {
unobserve = observer!.observe(el.value as Element, async () => {
unobserve?.()
unobserve = null

View File

@ -9,7 +9,7 @@
<script setup>
import { defineAsyncComponent, onErrorCaptured, onServerPrefetch, provide } from 'vue'
import { callWithNuxt, useNuxtApp } from '#app/nuxt'
import { useNuxtApp } from '#app/nuxt'
import { isNuxtError, showError, useError } from '#app/composables/error'
import { useRoute } from '#app/composables/router'
import AppComponent from '#build/app-component.mjs'
@ -40,7 +40,7 @@ const error = useError()
onErrorCaptured((err, target, info) => {
nuxtApp.hooks.callHook('vue:error', err, target, info).catch(hookError => console.error('[nuxt] Error in `vue:error` hook', hookError))
if (process.server || (isNuxtError(err) && (err.fatal || err.unhandled))) {
const p = callWithNuxt(nuxtApp, showError, [err])
const p = nuxtApp.runWithContext(() => showError(err))
onServerPrefetch(() => p)
return false // suppress error from breaking render
}

View File

@ -1,22 +1,16 @@
import { defineComponent, h } from 'vue'
import { h } from 'vue'
import type { Component } from 'vue'
// eslint-disable-next-line
import { isString, isPromise, isArray } from '@vue/shared'
const Fragment = defineComponent({
name: 'FragmentWrapper',
setup (_props, { slots }) {
return () => slots.default?.()
}
})
/**
* Internal utility
*
* @private
*/
export const _wrapIf = (component: Component, props: any, slots: any) => {
return { default: () => props ? h(component, props === true ? {} : props, slots) : h(Fragment, {}, slots) }
props = props === true ? {} : props
return { default: () => props ? h(component, props, slots) : slots.default?.() }
}
// eslint-disable-next-line no-use-before-define

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 { callWithNuxt, useNuxtApp } from '../nuxt'
import { useNuxtApp } from '../nuxt'
import { useAsyncData } from './asyncData'
import { useRoute } from './router'
import { createError } from './error'
@ -10,12 +10,12 @@ import { createError } from './error'
export const NuxtComponentIndicator = '__nuxt_component'
async function runLegacyAsyncData (res: Record<string, any> | Promise<Record<string, any>>, fn: (nuxtApp: NuxtApp) => Promise<Record<string, any>>) {
const nuxt = useNuxtApp()
const nuxtApp = useNuxtApp()
const route = useRoute()
const vm = getCurrentInstance()!
const { fetchKey } = vm.proxy!.$options
const key = typeof fetchKey === 'function' ? fetchKey(() => '') : fetchKey || route.fullPath
const { data, error } = await useAsyncData(`options:asyncdata:${key}`, () => callWithNuxt(nuxt, fn, [nuxt]))
const { data, error } = await useAsyncData(`options:asyncdata:${key}`, () => nuxtApp.runWithContext(() => fn(nuxtApp)))
if (error.value) {
throw createError(error.value)
}
@ -43,7 +43,7 @@ export const defineNuxtComponent: typeof defineComponent =
...options,
setup (props, ctx) {
const nuxtApp = useNuxtApp()
const res = setup ? Promise.resolve(callWithNuxt(nuxtApp, setup, [props, ctx])).then(r => r || {}) : {}
const res = setup ? Promise.resolve(nuxtApp.runWithContext(() => setup(props, ctx))).then(r => r || {}) : {}
const promises: Promise<any>[] = []
if (options.asyncData) {

View File

@ -2,7 +2,7 @@ import type { Ref } from 'vue'
import { ref, watch } from 'vue'
import type { CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
import { parse, serialize } from 'cookie-es'
import { appendHeader } from 'h3'
import { appendResponseHeader } from 'h3'
import type { H3Event } from 'h3'
import destr from 'destr'
import { isEqual } from 'ohash'
@ -48,11 +48,10 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
}
}
const unhook = nuxtApp.hooks.hookOnce('app:rendered', writeFinalCookieValue)
const writeAndUnhook = () => {
nuxtApp.hooks.hookOnce('app:error', () => {
unhook() // don't write cookie subsequently when app:rendered is called
return writeFinalCookieValue()
}
nuxtApp.hooks.hookOnce('app:error', writeAndUnhook)
})
}
return cookie as CookieRef<T>
@ -82,6 +81,6 @@ function writeClientCookie (name: string, value: any, opts: CookieSerializeOptio
function writeServerCookie (event: H3Event, name: string, value: any, opts: CookieSerializeOptions = {}) {
if (event) {
// TODO: Try to smart join with existing Set-Cookie headers
appendHeader(event, 'Set-Cookie', serializeCookie(name, value, opts))
appendResponseHeader(event, 'Set-Cookie', serializeCookie(name, value, opts))
}
}

View File

@ -13,8 +13,10 @@ export const showError = (_err: string | Error | Partial<NuxtError>) => {
try {
const nuxtApp = useNuxtApp()
nuxtApp.callHook('app:error', err)
const error = useError()
if (process.client) {
nuxtApp.hooks.callHook('app:error', err)
}
error.value = error.value || err
} catch {
throw err

View File

@ -76,6 +76,8 @@ interface _NuxtApp {
hook: _NuxtApp['hooks']['hook']
callHook: _NuxtApp['hooks']['callHook']
runWithContext: <T extends () => any>(fn: T) => ReturnType<T> | Promise<Awaited<ReturnType<T>>>
[key: string]: unknown
/** @internal */
@ -193,6 +195,7 @@ export function createNuxtApp (options: CreateOptions) {
static: {
data: {}
},
runWithContext: (fn: any) => callWithNuxt(nuxtApp, fn),
isHydrating: process.client,
deferHydration () {
if (!nuxtApp.isHydrating) { return () => {} }
@ -224,7 +227,7 @@ export function createNuxtApp (options: CreateOptions) {
if (process.server) {
async function contextCaller (hooks: HookCallback[], args: any[]) {
for (const hook of hooks) {
await nuxtAppCtx.callAsync(nuxtApp, () => hook(...args))
await nuxtApp.runWithContext(() => hook(...args))
}
}
// Patch callHook to preserve NuxtApp context on server
@ -288,7 +291,7 @@ export function createNuxtApp (options: CreateOptions) {
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin) {
if (typeof plugin !== 'function') { return }
const { provide } = await callWithNuxt(nuxtApp, plugin, [nuxtApp]) || {}
const { provide } = await nuxtApp.runWithContext(() => plugin(nuxtApp)) || {}
if (provide && typeof provide === 'object') {
for (const key in provide) {
nuxtApp.provide(key, provide[key])

View File

@ -1,7 +1,7 @@
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'
import { defineNuxtPlugin } from '#app/nuxt'
const revivers = {
NuxtError: (data: any) => createError(data),
@ -20,7 +20,7 @@ export default defineNuxtPlugin({
for (const reviver in revivers) {
definePayloadReviver(reviver, revivers[reviver as keyof typeof revivers])
}
Object.assign(nuxtApp.payload, await callWithNuxt(nuxtApp, getNuxtClientPayload, []))
Object.assign(nuxtApp.payload, await nuxtApp.runWithContext(getNuxtClientPayload))
// For backwards compatibility - TODO: remove later
window.__NUXT__ = nuxtApp.payload
}

View File

@ -1,7 +1,7 @@
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 { defineNuxtPlugin, useRuntimeConfig } from '../nuxt'
import { clearError, showError } from '../composables/error'
import { navigateTo } from '../composables/router'
import { useState } from '../composables/state'
@ -142,7 +142,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
window.history[replace ? 'replaceState' : 'pushState']({}, '', joinURL(baseURL, to.fullPath))
if (!nuxtApp.isHydrating) {
// Clear any existing errors
await callWithNuxt(nuxtApp, clearError)
await nuxtApp.runWithContext(clearError)
}
}
// Run afterEach hooks
@ -238,7 +238,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
const middlewareEntries = new Set<RouteGuard>([...globalMiddleware, ...nuxtApp._middleware.global])
for (const middleware of middlewareEntries) {
const result = await callWithNuxt(nuxtApp, middleware, [to, from])
const result = await nuxtApp.runWithContext(() => middleware(to, from))
if (process.server) {
if (result === false || result instanceof Error) {
const error = result || createError({
@ -246,7 +246,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
statusMessage: `Page Not Found: ${initialURL}`
})
delete nuxtApp._processingMiddleware
return callWithNuxt(nuxtApp, showError, [error])
return nuxtApp.runWithContext(() => showError(error))
}
}
if (result || result === false) { return result }
@ -257,7 +257,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
await router.replace(initialURL)
if (!isEqual(route.fullPath, initialURL)) {
await callWithNuxt(nuxtApp, navigateTo, [route.fullPath])
await nuxtApp.runWithContext(() => navigateTo(route.fullPath))
}
})

View File

@ -113,14 +113,14 @@ export default defineNuxtModule<ComponentsOptions>({
})
// components.d.ts
addTemplate({ ...componentsTypeTemplate, options: { getComponents } })
addTemplate({ ...componentsTypeTemplate })
// components.plugin.mjs
addPluginTemplate({ ...componentsPluginTemplate, options: { getComponents } } as any)
addPluginTemplate({ ...componentsPluginTemplate } as any)
// component-names.mjs
addTemplate({ ...componentNamesTemplate, options: { getComponents, mode: 'all' } })
addTemplate({ ...componentNamesTemplate, options: { mode: 'all' } })
// components.islands.mjs
if (nuxt.options.experimental.componentIslands) {
addTemplate({ ...componentsIslandsTemplate, filename: 'components.islands.mjs', options: { getComponents } })
addTemplate({ ...componentsIslandsTemplate, filename: 'components.islands.mjs' })
} else {
addTemplate({ filename: 'components.islands.mjs', getContents: () => 'export default {}' })
}
@ -158,7 +158,7 @@ export default defineNuxtModule<ComponentsOptions>({
})
// Scan components and add to plugin
nuxt.hook('app:templates', async () => {
nuxt.hook('app:templates', async (app) => {
const newComponents = await scanComponents(componentDirs, nuxt.options.srcDir!)
await nuxt.callHook('components:extend', newComponents)
// add server placeholder for .client components server side. issue: #7085
@ -173,6 +173,7 @@ export default defineNuxtModule<ComponentsOptions>({
}
}
context.components = newComponents
app.components = newComponents
})
nuxt.hook('prepare:types', ({ references, tsConfig }) => {

View File

@ -1,7 +1,7 @@
import { Fragment, computed, createStaticVNode, createVNode, defineComponent, h, ref, watch } from 'vue'
import { debounce } from 'perfect-debounce'
import { hash } from 'ohash'
import { appendHeader } from 'h3'
import { appendResponseHeader } from 'h3'
import { useHead } from '@unhead/vue'
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
@ -51,7 +51,7 @@ const NuxtServerComponent = defineComponent({
const url = `/__nuxt_island/${props.name}:${hashId.value}`
if (process.server && process.env.prerender) {
// Hint to Nitro to prerender the island component
appendHeader(event, 'x-nitro-prerender', url)
appendResponseHeader(event, 'x-nitro-prerender', url)
}
// TODO: Validate response
return $fetch<NuxtIslandResponse>(url, {

View File

@ -1,8 +1,9 @@
import { isAbsolute, relative } from 'pathe'
import { genDynamicImport } from 'knitwork'
import type { Component, Nuxt, NuxtPluginTemplate, NuxtTemplate } from 'nuxt/schema'
import type { Component, Nuxt, NuxtApp, NuxtPluginTemplate, NuxtTemplate } from 'nuxt/schema'
export interface ComponentsTemplateContext {
app: NuxtApp
nuxt: Nuxt
options: {
getComponents: (mode?: 'client' | 'server' | 'all') => Component[]
@ -34,8 +35,8 @@ export default defineNuxtPlugin({
export const componentsPluginTemplate: NuxtPluginTemplate<ComponentsTemplateContext> = {
filename: 'components.plugin.mjs',
getContents ({ options }) {
const globalComponents = options.getComponents().filter(c => c.global)
getContents ({ app }) {
const globalComponents = app.components.filter(c => c.global)
if (!globalComponents.length) { return emptyComponentsPlugin }
return `import { defineNuxtPlugin } from '#app/nuxt'
@ -59,15 +60,15 @@ export default defineNuxtPlugin({
export const componentNamesTemplate: NuxtPluginTemplate<ComponentsTemplateContext> = {
filename: 'component-names.mjs',
getContents ({ options }) {
return `export const componentNames = ${JSON.stringify(options.getComponents().filter(c => !c.island).map(c => c.pascalName))}`
getContents ({ app }) {
return `export const componentNames = ${JSON.stringify(app.components.filter(c => !c.island).map(c => c.pascalName))}`
}
}
export const componentsIslandsTemplate: NuxtTemplate<ComponentsTemplateContext> = {
// components.islands.mjs'
getContents ({ options }) {
const components = options.getComponents()
getContents ({ app }) {
const components = app.components
const islands = components.filter(component =>
component.island ||
// .server components without a corresponding .client component will need to be rendered as an island
@ -85,9 +86,9 @@ export const componentsIslandsTemplate: NuxtTemplate<ComponentsTemplateContext>
export const componentsTypeTemplate: NuxtTemplate<ComponentsTemplateContext> = {
filename: 'components.d.ts',
getContents: ({ options, nuxt }) => {
getContents: ({ app, nuxt }) => {
const buildDir = nuxt.options.buildDir
const componentTypes = options.getComponents().filter(c => !c.island).map(c => [
const componentTypes = app.components.filter(c => !c.island).map(c => [
c.pascalName,
`typeof ${genDynamicImport(isAbsolute(c.filePath)
? relative(buildDir, c.filePath).replace(/(?<=\w)\.(?!vue)\w+$/g, '')

View File

@ -21,15 +21,19 @@ export function createTransformPlugin (nuxt: Nuxt, getComponents: getComponentsT
function getComponentsImports (): Import[] {
const components = getComponents(mode)
return components.flatMap((c): Import[] => {
const withMode = (mode: string | undefined) => mode
? `${c.filePath}${c.filePath.includes('?') ? '&' : '?'}nuxt_component=${mode}`
: c.filePath
return [
{
as: c.pascalName,
from: c.filePath + (c.mode === 'client' ? '?component=client' : ''),
from: withMode(c.mode === 'client' ? 'client' : undefined),
name: 'default'
},
{
as: 'Lazy' + c.pascalName,
from: c.filePath + '?component=' + [c.mode === 'client' ? 'client' : '', 'async'].filter(Boolean).join(','),
from: withMode([c.mode === 'client' ? 'client' : '', 'async'].filter(Boolean).join(',')),
name: 'default'
}
]
@ -43,10 +47,10 @@ export function createTransformPlugin (nuxt: Nuxt, getComponents: getComponentsT
},
async transform (code, id) {
// Virtual component wrapper
if (id.includes('?component')) {
if (id.match(/[?&]nuxt_component=/)) {
const { search } = parseURL(id)
const query = parseQuery(search)
const mode = query.component
const mode = query.nuxt_component
const bare = id.replace(/\?.*/, '')
if (mode === 'async') {
return [

View File

@ -12,6 +12,7 @@ export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp
dir: nuxt.options.srcDir,
extensions: nuxt.options.extensions,
plugins: [],
components: [],
templates: []
} as unknown as NuxtApp) as NuxtApp
}

View File

@ -65,7 +65,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
analyze: nuxt.options.build.analyze && {
template: 'treemap',
projectRoot: nuxt.options.rootDir,
filename: join(nuxt.options.rootDir, '.nuxt/stats', '{name}.html')
filename: join(nuxt.options.analyzeDir, '{name}.html')
},
scanDirs: nuxt.options._layers.map(layer => (layer.config.serverDir || layer.config.srcDir) && resolve(layer.cwd, layer.config.serverDir || resolve(layer.config.srcDir, 'server'))).filter(Boolean),
renderer: resolve(distDir, 'core/runtime/nitro/renderer'),
@ -130,7 +130,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
],
traceInclude: [
// force include files used in generated code from the runtime-compiler
...(nuxt.options.experimental.runtimeVueCompiler && !nuxt.options.experimental.externalVue)
...(nuxt.options.vue.runtimeCompiler && !nuxt.options.experimental.externalVue)
? [
...nuxt.options.modulesDir.reduce<string[]>((targets, path) => {
const serverRendererPath = resolve(path, 'vue/server-renderer/index.js')
@ -150,7 +150,7 @@ 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
...nuxt.options.vue.runtimeCompiler || nuxt.options.experimental.externalVue
? {}
: {
'estree-walker': 'unenv/runtime/mock/proxy',
@ -253,7 +253,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
})
// Enable runtime compiler client side
if (nuxt.options.experimental.runtimeVueCompiler) {
if (nuxt.options.vue.runtimeCompiler) {
nuxt.hook('vite:extendConfig', (config, { isClient }) => {
if (isClient) {
if (Array.isArray(config.resolve!.alias)) {

View File

@ -78,17 +78,17 @@ async function initNuxt (nuxt: Nuxt) {
exclude: [join(nuxt.options.rootDir, 'index.html')],
patterns: vueAppPatterns(nuxt)
}
addVitePlugin(ImportProtectionPlugin.vite(config))
addWebpackPlugin(ImportProtectionPlugin.webpack(config))
addVitePlugin(() => ImportProtectionPlugin.vite(config))
addWebpackPlugin(() => ImportProtectionPlugin.webpack(config))
if (nuxt.options.experimental.localLayerAliases) {
// Add layer aliasing support for ~, ~~, @ and @@ aliases
addVitePlugin(LayerAliasingPlugin.vite({
addVitePlugin(() => LayerAliasingPlugin.vite({
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client,
// skip top-level layer (user's project) as the aliases will already be correctly resolved
layers: nuxt.options._layers.slice(1)
}))
addWebpackPlugin(LayerAliasingPlugin.webpack({
addWebpackPlugin(() => LayerAliasingPlugin.webpack({
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client,
// skip top-level layer (user's project) as the aliases will already be correctly resolved
layers: nuxt.options._layers.slice(1),
@ -102,8 +102,8 @@ async function initNuxt (nuxt: Nuxt) {
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client,
transformerOptions: nuxt.options.optimization.asyncTransforms
}
addVitePlugin(UnctxTransformPlugin.vite(options))
addWebpackPlugin(UnctxTransformPlugin.webpack(options))
addVitePlugin(() => UnctxTransformPlugin.vite(options))
addWebpackPlugin(() => UnctxTransformPlugin.webpack(options))
// Add composable tree-shaking optimisations
const serverTreeShakeOptions: TreeShakeComposablesPluginOptions = {
@ -111,23 +111,23 @@ async function initNuxt (nuxt: Nuxt) {
composables: nuxt.options.optimization.treeShake.composables.server
}
if (Object.keys(serverTreeShakeOptions.composables).length) {
addVitePlugin(TreeShakeComposablesPlugin.vite(serverTreeShakeOptions), { client: false })
addWebpackPlugin(TreeShakeComposablesPlugin.webpack(serverTreeShakeOptions), { client: false })
addVitePlugin(() => TreeShakeComposablesPlugin.vite(serverTreeShakeOptions), { client: false })
addWebpackPlugin(() => TreeShakeComposablesPlugin.webpack(serverTreeShakeOptions), { client: false })
}
const clientTreeShakeOptions: TreeShakeComposablesPluginOptions = {
sourcemap: nuxt.options.sourcemap.client,
composables: nuxt.options.optimization.treeShake.composables.client
}
if (Object.keys(clientTreeShakeOptions.composables).length) {
addVitePlugin(TreeShakeComposablesPlugin.vite(clientTreeShakeOptions), { server: false })
addWebpackPlugin(TreeShakeComposablesPlugin.webpack(clientTreeShakeOptions), { server: false })
addVitePlugin(() => TreeShakeComposablesPlugin.vite(clientTreeShakeOptions), { server: false })
addWebpackPlugin(() => TreeShakeComposablesPlugin.webpack(clientTreeShakeOptions), { server: false })
}
})
if (!nuxt.options.dev) {
// DevOnly component tree-shaking - build time only
addVitePlugin(DevOnlyPlugin.vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
addWebpackPlugin(DevOnlyPlugin.webpack({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
addVitePlugin(() => DevOnlyPlugin.vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
addWebpackPlugin(() => DevOnlyPlugin.webpack({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
}
// TODO: [Experimental] Avoid emitting assets when flag is enabled
@ -358,6 +358,17 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
// Temporary until finding better placement for each
options.appDir = options.alias['#app'] = resolve(distDir, 'app')
options._majorVersion = 3
// 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`')
}
}
// Add core modules
options._modules.push(pagesModule, metaModule, componentsModule)
options._modules.push([importsModule, {
transform: {
@ -376,15 +387,6 @@ 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

@ -2,7 +2,7 @@ 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, createError, getQuery, readBody, writeEarlyHints } from 'h3'
import { appendResponseHeader, createError, getQuery, readBody, writeEarlyHints } from 'h3'
import devalue from '@nuxt/devalue'
import { stringify, uneval } from 'devalue'
import destr from 'destr'
@ -199,7 +199,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
let url = ssrError?.url as string || islandContext?.url || event.node.req.url!
// Whether we are rendering payload route
const isRenderingPayload = PAYLOAD_URL_RE.test(url)
const isRenderingPayload = PAYLOAD_URL_RE.test(url) && !islandContext
if (isRenderingPayload) {
url = url.substring(0, url.lastIndexOf('/')) || '/'
event.node.req.url = url
@ -229,7 +229,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
}
// Whether we are prerendering route
const _PAYLOAD_EXTRACTION = process.env.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !ssrContext.noSSR
const _PAYLOAD_EXTRACTION = process.env.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !ssrContext.noSSR && !islandContext
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()
@ -275,7 +275,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
if (_PAYLOAD_EXTRACTION) {
// Hint nitro to prerender payload for this route
appendHeader(event, 'x-nitro-prerender', joinURL(url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js'))
appendResponseHeader(event, 'x-nitro-prerender', joinURL(url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js'))
// Use same ssr context to generate payload for this route
PAYLOAD_CACHE!.set(withoutTrailingSlash(url), renderPayloadResponse(ssrContext))
}

View File

@ -81,8 +81,8 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
nuxt.options.alias['#imports'] = join(nuxt.options.buildDir, 'imports')
// Transform to inject imports in production mode
addVitePlugin(TransformPlugin.vite({ ctx, options, sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
addWebpackPlugin(TransformPlugin.webpack({ ctx, options, sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
addVitePlugin(() => TransformPlugin.vite({ ctx, options, sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
addWebpackPlugin(() => TransformPlugin.webpack({ ctx, options, sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
const priorities = nuxt.options._layers.map((layer, i) => [layer.config.srcDir, -i] as const).sort(([a], [b]) => b.length - a.length)

View File

@ -151,14 +151,11 @@ export default defineNuxtModule({
// Extract macros from pages
const pageMetaOptions: PageMetaPluginOptions = {
dev: nuxt.options.dev,
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client,
dirs: nuxt.options._layers.map(
layer => resolve(layer.config.srcDir, layer.config.dir?.pages || 'pages')
)
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client
}
nuxt.hook('modules:done', () => {
addVitePlugin(PageMetaPlugin.vite(pageMetaOptions))
addWebpackPlugin(PageMetaPlugin.webpack(pageMetaOptions))
addVitePlugin(() => PageMetaPlugin.vite(pageMetaOptions))
addWebpackPlugin(() => PageMetaPlugin.webpack(pageMetaOptions))
})
// Add prefetching support for middleware & layouts

View File

@ -10,7 +10,6 @@ import MagicString from 'magic-string'
import { isAbsolute, normalize } from 'pathe'
export interface PageMetaPluginOptions {
dirs: Array<string | RegExp>
dev?: boolean
sourcemap?: boolean
}
@ -42,11 +41,7 @@ export const PageMetaPlugin = createUnplugin((options: PageMetaPluginOptions) =>
const query = parseMacroQuery(id)
id = normalize(id)
const isPagesDir = options.dirs.some(dir => typeof dir === 'string' ? id.startsWith(dir) : dir.test(id))
if (!isPagesDir && !query.macro) { return false }
const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href))
return /\.(m?[jt]sx?|vue)/.test(pathname)
return !!query.macro
},
transform (code, id) {
const query = parseMacroQuery(id)
@ -66,26 +61,6 @@ export const PageMetaPlugin = createUnplugin((options: PageMetaPluginOptions) =>
const hasMacro = code.match(/\bdefinePageMeta\s*\(\s*/)
// Remove any references to the macro from our pages
if (!query.macro) {
if (hasMacro) {
walk(this.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest'
}) as Node, {
enter (_node) {
if (_node.type !== 'CallExpression' || (_node as CallExpression).callee.type !== 'Identifier') { return }
const node = _node as CallExpression & { start: number, end: number }
const name = 'name' in node.callee && node.callee.name
if (name === 'definePageMeta') {
s.overwrite(node.start, node.end, 'false && {}')
}
}
})
}
return result()
}
const imports = findStaticImports(code)
// [vite] Re-export any script imports

View File

@ -1,5 +1,7 @@
import type { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
import { getCurrentInstance } from 'vue'
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from 'vue-router'
import { useRoute } from 'vue-router'
import type { NuxtError } from '#app'
export interface PageMeta {
@ -51,6 +53,14 @@ const warnRuntimeUsage = (method: string) =>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const definePageMeta = (meta: PageMeta): void => {
if (process.dev) {
const component = getCurrentInstance()?.type
try {
const isRouteComponent = component && useRoute().matched.some(p => Object.values(p.components || {}).includes(component))
if (isRouteComponent) {
// don't warn if it's being used in a route component
return
}
} catch {}
warnRuntimeUsage('definePageMeta')
}
}

View File

@ -11,7 +11,7 @@ import { createError } from 'h3'
import { withoutBase } from 'ufo'
import type { PageMeta, Plugin, RouteMiddleware } from '../../../app/index'
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig } from '#app/nuxt'
import { defineNuxtPlugin, useRuntimeConfig } from '#app/nuxt'
import { clearError, showError, useError } from '#app/composables/error'
import { useState } from '#app/composables/state'
import { navigateTo } from '#app/composables/router'
@ -113,7 +113,7 @@ export default defineNuxtPlugin({
await router.isReady()
} catch (error: any) {
// We'll catch 404s here
await callWithNuxt(nuxtApp, showError, [error])
await nuxtApp.runWithContext(() => showError(error))
}
const initialLayout = useState('_layout')
@ -148,14 +148,14 @@ export default defineNuxtPlugin({
throw new Error(`Unknown route middleware: '${entry}'.`)
}
const result = await callWithNuxt(nuxtApp, middleware, [to, from])
const result = await nuxtApp.runWithContext(() => middleware(to, from))
if (process.server || (!nuxtApp.payload.serverRendered && nuxtApp.isHydrating)) {
if (result === false || result instanceof Error) {
const error = result || createError({
statusCode: 404,
statusMessage: `Page Not Found: ${initialURL}`
})
await callWithNuxt(nuxtApp, showError, [error])
await nuxtApp.runWithContext(() => showError(error))
return false
}
}
@ -170,19 +170,19 @@ export default defineNuxtPlugin({
if (process.client && !nuxtApp.isHydrating && error.value) {
// Clear any existing errors
await callWithNuxt(nuxtApp, clearError)
await nuxtApp.runWithContext(clearError)
}
if (process.server && failure?.type === 4 /* ErrorTypes.NAVIGATION_ABORTED */) {
return
}
if (to.matched.length === 0) {
await callWithNuxt(nuxtApp, showError, [createError({
await nuxtApp.runWithContext(() => showError(createError({
statusCode: 404,
fatal: false,
statusMessage: `Page not found: ${to.fullPath}`
})])
})))
} else if (process.server && to.redirectedFrom) {
await callWithNuxt(nuxtApp, navigateTo, [to.fullPath || '/'])
await nuxtApp.runWithContext(() => navigateTo(to.fullPath || '/'))
}
})
@ -195,7 +195,7 @@ export default defineNuxtPlugin({
})
} catch (error: any) {
// We'll catch middleware errors or deliberate exceptions here
await callWithNuxt(nuxtApp, showError, [error])
await nuxtApp.runWithContext(() => showError(error))
}
})

View File

@ -1,5 +1,5 @@
import { createError, showError } from '#app/composables/error'
import { callWithNuxt, useNuxtApp } from '#app/nuxt'
import { useNuxtApp } from '#app/nuxt'
import { defineNuxtRouteMiddleware, useRouter } from '#app/composables/router'
export default defineNuxtRouteMiddleware(async (to) => {
@ -24,7 +24,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
if (final === to) {
const unsub = router.afterEach(async () => {
unsub()
await callWithNuxt(nuxtApp, showError, [error])
await nuxtApp.runWithContext(() => showError(error))
// We pretend to have navigated to the invalid route so
// that the user can return to the previous page with
// the back button.

View File

@ -30,7 +30,7 @@
"nitropack": "^2.3.3",
"unbuild": "latest",
"unctx": "^2.3.0",
"vite": "~4.3.3",
"vite": "~4.3.4",
"vue": "3.2.47",
"vue-bundle-renderer": "^1.0.3",
"vue-router": "^4.1.6",
@ -40,7 +40,7 @@
"defu": "^6.1.2",
"hookable": "^5.5.3",
"pathe": "^1.1.0",
"pkg-types": "^1.0.2",
"pkg-types": "^1.0.3",
"postcss-import-resolver": "^2.0.0",
"std-env": "^3.3.2",
"ufo": "^1.1.1",

View File

@ -13,6 +13,13 @@ export default defineUntypedSchema({
* @type {typeof import('@vue/compiler-core').CompilerOptions}
*/
compilerOptions: {},
/**
* Include Vue compiler in runtime bundle.
*/
runtimeCompiler: {
$resolve: async (val, get) => val ?? await get('experimental.runtimeVueCompiler') ?? false,
},
},
/**

View File

@ -119,10 +119,11 @@ export default defineUntypedSchema({
return val ?? false
}
const rootDir = await get('rootDir')
const analyzeDir = await get('analyzeDir')
return {
template: 'treemap',
projectRoot: rootDir,
filename: join(rootDir, '.nuxt/stats', '{name}.html')
filename: join(analyzeDir, '{name}.html')
}
}
},
@ -170,7 +171,7 @@ export default defineUntypedSchema({
$resolve: async (val, get) => defu(val || {},
await get('dev') ? {} : {
vue: ['onBeforeMount', 'onMounted', 'onBeforeUpdate', 'onRenderTracked', 'onRenderTriggered', 'onActivated', 'onDeactivated', 'onBeforeUnmount'],
'#app': ['definePayloadReviver']
'#app': ['definePayloadReviver', 'definePageMeta']
}
)
},
@ -178,7 +179,7 @@ export default defineUntypedSchema({
$resolve: async (val, get) => defu(val || {},
await get('dev') ? {} : {
vue: ['onServerPrefetch', 'onRenderTracked', 'onRenderTriggered'],
'#app': ['definePayloadReducer']
'#app': ['definePayloadReducer', 'definePageMeta']
}
)
}

View File

@ -117,20 +117,20 @@ export default defineUntypedSchema({
},
/**
* Used to set the modules directories for path resolving (for example, webpack's
* `resolveLoading`, `nodeExternals` and `postcss`).
*
* The configuration path is relative to `options.rootDir` (default is current working directory).
*
* Setting this field may be necessary if your project is organized as a yarn workspace-styled mono-repository.
*
* @example
* ```js
* export default {
* modulesDir: ['../../node_modules']
* }
* ```
*/
* Used to set the modules directories for path resolving (for example, webpack's
* `resolveLoading`, `nodeExternals` and `postcss`).
*
* The configuration path is relative to `options.rootDir` (default is current working directory).
*
* Setting this field may be necessary if your project is organized as a yarn workspace-styled mono-repository.
*
* @example
* ```js
* export default {
* modulesDir: ['../../node_modules']
* }
* ```
*/
modulesDir: {
$default: ['node_modules'],
$resolve: async (val, get) => [
@ -139,6 +139,17 @@ export default defineUntypedSchema({
]
},
/**
* The directory where Nuxt will store the generated files when running `nuxt analyze`.
*
* If a relative path is specified, it will be relative to your `rootDir`.
*/
analyzeDir: {
$resolve: async (val, get) => val
? resolve(await get('rootDir'), val)
: resolve(await get('buildDir'), 'analyze')
},
/**
* Whether Nuxt is running in development mode.
*
@ -346,6 +357,7 @@ export default defineUntypedSchema({
'**/*.d.ts', // ignore type declarations
'.output',
'.git',
await get('analyzeDir'),
await get('ignorePrefix') && `**/${await get('ignorePrefix')}*.*`
].concat(val).filter(Boolean)
},

View File

@ -23,12 +23,6 @@ export default defineUntypedSchema({
*/
externalVue: true,
// TODO: move to `vue.runtimeCompiler` in v3.5
/**
* Include Vue compiler in runtime bundle.
*/
runtimeVueCompiler: false,
/**
* Tree shakes contents of client-only components from server bundle.
* @see https://github.com/nuxt/framework/pull/5750

View File

@ -22,10 +22,11 @@ export default defineUntypedSchema({
return val ?? false
}
const rootDir = await get('rootDir')
const analyzeDir = await get('analyzeDir')
return {
template: 'treemap',
projectRoot: rootDir,
filename: join(rootDir, '.nuxt/stats', '{name}.html')
filename: join(analyzeDir, '{name}.html')
}
}
},

View File

@ -47,6 +47,16 @@ export interface GenerateAppOptions {
filter?: (template: ResolvedNuxtTemplate<any>) => boolean
}
export interface NuxtAnalyzeMeta {
name: string
slug: string
startTime: number
endTime: number
analyzeDir: string
buildDir: string
outDir: string
}
/**
* The listeners to Nuxt build time events
*/
@ -131,6 +141,13 @@ export interface NuxtHooks {
*/
'build:manifest': (manifest: Manifest) => HookResult
/**
* Called when `nuxt analyze` is finished
* @param meta the analyze meta object, mutations will be saved to `meta.json`
* @returns Promise
*/
'build:analyze:done': (meta: NuxtAnalyzeMeta) => HookResult
/**
* Called before generating the app.
* @param options GenerateAppOptions object

View File

@ -1,6 +1,7 @@
import type { Hookable } from 'hookable'
import type { Ignore } from 'ignore'
import type { NuxtHooks, NuxtLayout, NuxtMiddleware } from './hooks'
import type { Component } from './components'
import type { NuxtOptions } from './config'
export interface Nuxt {
@ -58,6 +59,7 @@ export interface NuxtApp {
dir: string
extensions: string[]
plugins: NuxtPlugin[]
components: Component[]
layouts: Record<string, NuxtLayout>
middleware: NuxtMiddleware[]
templates: NuxtTemplate[]

View File

@ -13,7 +13,7 @@ const kit: typeof _kit = _kit.default || _kit
export async function startServer () {
const ctx = useTestContext()
await stopServer()
const port = await getRandomPort()
const port = ctx.options.port || await getRandomPort()
ctx.url = 'http://127.0.0.1:' + port
if (ctx.options.dev) {
const nuxiCLI = await kit.resolvePath('nuxi/cli')

View File

@ -23,6 +23,7 @@ export interface TestOptions {
launch?: LaunchOptions
}
server: boolean
port?: number
}
export interface TestContext {

View File

@ -30,7 +30,7 @@
"@vitejs/plugin-vue-jsx": "^3.0.1",
"autoprefixer": "^10.4.14",
"clear": "^0.1.0",
"cssnano": "^6.0.0",
"cssnano": "^6.0.1",
"defu": "^6.1.2",
"esbuild": "^0.17.18",
"escape-string-regexp": "^5.0.0",
@ -45,7 +45,7 @@
"ohash": "^1.1.2",
"pathe": "^1.1.0",
"perfect-debounce": "^0.1.3",
"pkg-types": "^1.0.2",
"pkg-types": "^1.0.3",
"postcss": "^8.4.23",
"postcss-import": "^15.1.0",
"postcss-url": "^10.1.3",
@ -54,9 +54,9 @@
"strip-literal": "^1.0.1",
"ufo": "^1.1.1",
"unplugin": "^1.3.1",
"vite": "~4.3.3",
"vite": "~4.3.4",
"vite-node": "^0.30.1",
"vite-plugin-checker": "^0.5.6",
"vite-plugin-checker": "^0.6.0",
"vue-bundle-renderer": "^1.0.3"
},
"peerDependencies": {

View File

@ -24,7 +24,7 @@
"autoprefixer": "^10.4.14",
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^5.0.0",
"cssnano": "^6.0.0",
"cssnano": "^6.0.1",
"esbuild-loader": "^3.0.1",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
@ -43,7 +43,7 @@
"pify": "^6.1.0",
"postcss": "^8.4.23",
"postcss-import": "^15.1.0",
"postcss-loader": "^7.2.4",
"postcss-loader": "^7.3.0",
"postcss-url": "^10.1.3",
"std-env": "^3.3.2",
"time-fix-plugin": "^2.0.7",

File diff suppressed because it is too large Load Diff

View File

@ -13,10 +13,10 @@
"main"
],
"ignoreDeps": [
"markdownlint-cli",
"nuxt",
"nuxt3",
"@nuxt/kit",
"parse5"
"@nuxt/kit"
]
},
{

View File

@ -113,9 +113,15 @@ describe('pages', () => {
expect(headers.get('location')).toEqual('/')
})
it('includes page metadata from pages added in pages:extend hook', async () => {
const res = await fetch('/page-extend')
expect(res.headers.get('x-extend')).toEqual('added in pages:extend')
})
it('validates routes', async () => {
const { status } = await fetch('/forbidden')
const { status, headers } = await fetch('/forbidden')
expect(status).toEqual(404)
expect(headers.get('Set-Cookie')).toBe('set-in-plugin=true; Path=/')
const page = await createPage('/navigate-to-forbidden')
await page.waitForLoadState('networkidle')
@ -135,8 +141,11 @@ describe('pages', () => {
expect(status).toEqual(500)
})
it('render 404', async () => {
const html = await $fetch('/not-found')
it('render catchall page', async () => {
const res = await fetch('/not-found')
expect(res.status).toEqual(200)
const html = await res.text()
// Snapshot
// expect(html).toMatchInlineSnapshot()
@ -578,7 +587,9 @@ describe('errors', () => {
it('should render a HTML error page', async () => {
const res = await fetch('/error')
expect(res.headers.get('Set-Cookie')).toBe('some-error=was%20set; Path=/')
expect(res.headers.get('Set-Cookie')).toBe('set-in-plugin=true; Path=/')
// TODO: enable when we update test to node v16
// expect(res.headers.get('Set-Cookie')).toBe('set-in-plugin=true; Path=/, some-error=was%20set; Path=/')
expect(await res.text()).toContain('This is a custom error')
})

View File

@ -34,7 +34,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e
it('default client bundle size', async () => {
stats.client = await analyzeSizes('**/*.js', publicDir)
expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"94.0k"')
expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"94.1k"')
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
[
"_nuxt/entry.js",
@ -45,7 +45,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e
it('default server bundle size', async () => {
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"66.6k"')
expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"66.7k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2654k"')

View File

@ -0,0 +1,19 @@
import { createResolver, defineNuxtModule, useNuxt } from 'nuxt/kit'
export default defineNuxtModule({
meta: {
name: 'page-extend'
},
setup () {
const nuxt = useNuxt()
const resolver = createResolver(import.meta.url)
nuxt.hook('pages:extend', (pages) => {
pages.push({
name: 'page-extend',
path: '/page-extend',
file: resolver.resolve('./runtime/page.vue')
})
})
}
})

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import { setResponseHeader } from 'h3'
definePageMeta({
value: 'added in pages:extend'
})
if (process.server) {
setResponseHeader(useRequestEvent(), 'x-extend', useRoute().meta.value as string)
}
</script>
<template>
<div>
added in pages:extend
</div>
</template>

View File

@ -106,8 +106,8 @@ export default defineNuxtConfig({
if (id === 'virtual.css') { return ':root { --virtual: red }' }
}
}))
addVitePlugin(plugin.vite())
addWebpackPlugin(plugin.webpack())
addVitePlugin(() => plugin.vite())
addWebpackPlugin(() => plugin.webpack())
},
function (_options, nuxt) {
const routesToDuplicate = ['/async-parent', '/fixed-keyed-child-parent', '/keyed-child-parent', '/with-layout', '/with-layout2']

3
test/fixtures/basic/plugins/cookie.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export default defineNuxtPlugin(() => {
useCookie('set-in-plugin').value = 'true'
})

View File

@ -96,7 +96,7 @@ describe('middleware', () => {
addRouteMiddleware('example', (to, from) => {
expectTypeOf(to).toEqualTypeOf<RouteLocationNormalizedLoaded>()
expectTypeOf(from).toEqualTypeOf<RouteLocationNormalizedLoaded>()
expectTypeOf(navigateTo).toEqualTypeOf <(to: RouteLocationRaw | null | undefined, options ?: NavigateToOptions) => RouteLocationRaw | void | false | Promise<void | NavigationFailure | false>>()
expectTypeOf(navigateTo).toEqualTypeOf<(to: RouteLocationRaw | null | undefined, options?: NavigateToOptions) => RouteLocationRaw | void | false | Promise<void | NavigationFailure | false>>()
navigateTo('/')
abortNavigation()
abortNavigation('error string')
@ -315,4 +315,8 @@ describe('composables inference', () => {
const bob = callWithNuxt({} as any, () => true)
expectTypeOf<typeof bob>().toEqualTypeOf<boolean | Promise<boolean>>()
})
it('runWithContext', () => {
const bob = useNuxtApp().runWithContext(() => true)
expectTypeOf<typeof bob>().toEqualTypeOf<boolean | Promise<boolean>>()
})
})

View File

@ -1,6 +1,11 @@
<script setup lang="ts">
import { componentNames } from '#components'
console.log(componentNames)
// @ts-expect-error this is not usable outside a pages directory
definePageMeta({
// this should be fully tree-shaken out
title: 'jet common fruit chose bright planning exercise herself position wealth stiff known prepare listen leader eleven found boat dollar eye come author won thought pony biggest feel organized die vast class ask cost ball wrong chicken origin model little properly dangerous dull corner jar mighty solution pilot city locate guide gradually affect curve about snake single silly against fireplace money another involved origin sport where thin stop question go stretch although arrow rush mixture fallen power pay fifteen layers play slightly heavy built needed sing sentence diagram quarter yesterday list faster been having construction curious shoe'
})
</script>
<template>

View File

@ -1,8 +1,10 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
experimental: {
runtimeVueCompiler: true,
externalVue: false
},
vue: {
runtimeCompiler: true
},
builder: process.env.TEST_BUILDER as 'webpack' | 'vite' ?? 'vite'
})