chore: add back stylistic rules and lint project

This commit is contained in:
Daniel Roe 2024-03-09 06:48:15 +00:00
parent 3782ac0a2c
commit 7252b56d52
78 changed files with 435 additions and 303 deletions

View File

@ -12,11 +12,67 @@
}, },
"plugins": ["jsdoc", "import", "unicorn", "no-only-tests"], "plugins": ["jsdoc", "import", "unicorn", "no-only-tests"],
"extends": [ "extends": [
"standard",
"plugin:jsdoc/recommended", "plugin:jsdoc/recommended",
"@nuxt/eslint-config", "@nuxt/eslint-config",
"plugin:import/typescript" "plugin:import/typescript"
], ],
"rules": { "rules": {
// Imports should come first
"import/first": "error",
// Other import rules
"import/no-mutable-exports": "error",
// Allow unresolved imports
"import/no-unresolved": "off",
// Allow paren-less arrow functions only when there's no braces
"arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }],
// Allow async-await
"generator-star-spacing": "off",
// Prefer const over let
"prefer-const": ["error", { "destructuring": "any", "ignoreReadBeforeAssign": false }],
// No single if in an "else" block
"no-lonely-if": "error",
// Force curly braces for control flow,
// including if blocks with a single statement
"curly": ["error", "all"
],
// No async function without await
"require-await": "error",
// Force dot notation when possible
"dot-notation": "error",
"no-var": "error",
// Force object shorthand where possible
"object-shorthand": "error",
// No useless destructuring/importing/exporting renames
"no-useless-rename": "error",
/**********************/
/* Unicorn Rules */
/**********************/
// Pass error message when throwing errors
"unicorn/error-message": "error",
// Uppercase regex escapes
"unicorn/escape-case": "error",
// Array.isArray instead of instanceof
"unicorn/no-array-instanceof": "error",
// Prevent deprecated `new Buffer()`
"unicorn/no-new-buffer": "error",
// Keep regex literals safe!
"unicorn/no-unsafe-regex": "off",
// Lowercase number formatting for octal, hex, binary (0x12 instead of 0X12)
"unicorn/number-literal-case": "error",
// ** instead of Math.pow()
"unicorn/prefer-exponentiation-operator": "error",
// includes over indexOf when checking for existence
"unicorn/prefer-includes": "error",
// String methods startsWith/endsWith instead of more complicated stuff
"unicorn/prefer-starts-ends-with": "error",
// textContent instead of innerText
"unicorn/prefer-text-content": "error",
// Enforce throwing type error when throwing error while checking typeof
"unicorn/prefer-type-error": "error",
// Use new when throwing error
"unicorn/throw-new-error": "error",
"sort-imports": [ "sort-imports": [
"error", "error",
{ {

View File

@ -9,8 +9,8 @@ export default defineNuxtConfig({
function () { function () {
addPluginTemplate({ addPluginTemplate({
filename: 'plugins/my-plugin.mjs', filename: 'plugins/my-plugin.mjs',
getContents: () => `export default defineNuxtPlugin({ name: 'my-plugin' })` getContents: () => 'export default defineNuxtPlugin({ name: \'my-plugin\' })'
}) })
} }
], ]
}) })

View File

@ -60,6 +60,7 @@
"consola": "3.2.3", "consola": "3.2.3",
"devalue": "4.3.2", "devalue": "4.3.2",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-jsdoc": "48.2.1", "eslint-plugin-jsdoc": "48.2.1",
"eslint-plugin-no-only-tests": "3.1.0", "eslint-plugin-no-only-tests": "3.1.0",

View File

@ -9,7 +9,7 @@ import { toArray } from '../utils'
/** @deprecated */ /** @deprecated */
// TODO: Remove support for compiling ejs templates in v4 // TODO: Remove support for compiling ejs templates in v4
export async function compileTemplate <T>(template: NuxtTemplate<T>, ctx: any) { export async function compileTemplate <T> (template: NuxtTemplate<T>, ctx: any) {
const data = { ...ctx, options: template.options } const data = { ...ctx, options: template.options }
if (template.src) { if (template.src) {
try { try {

View File

@ -3,13 +3,13 @@ import { describe, expect, it, vi } from 'vitest'
import { consola } from 'consola' import { consola } from 'consola'
import { logger, useLogger } from './logger' import { logger, useLogger } from './logger'
vi.mock("consola", () => { vi.mock('consola', () => {
const logger = {} as any; const logger = {} as any
logger.create = vi.fn(() => ({...logger})); logger.create = vi.fn(() => ({ ...logger }))
logger.withTag = vi.fn(() => ({...logger})); logger.withTag = vi.fn(() => ({ ...logger }))
return { consola: logger }; return { consola: logger }
}) })
describe('logger', () => { describe('logger', () => {
@ -20,29 +20,28 @@ describe('logger', () => {
describe('useLogger', () => { describe('useLogger', () => {
it('should expose consola when not passing a tag', () => { it('should expose consola when not passing a tag', () => {
expect(useLogger()).toBe(consola); expect(useLogger()).toBe(consola)
}); })
it('should create a new instance when passing a tag', () => { it('should create a new instance when passing a tag', () => {
const logger = vi.mocked(consola); const logger = vi.mocked(consola)
const instance = useLogger("tag"); const instance = useLogger('tag')
expect(instance).toEqual(logger); expect(instance).toEqual(logger)
expect(instance).not.toBe(logger); expect(instance).not.toBe(logger)
expect(logger.create).toBeCalledWith({}); expect(logger.create).toBeCalledWith({})
expect(logger.withTag).toBeCalledWith("tag"); expect(logger.withTag).toBeCalledWith('tag')
}); })
it('should create a new instance when passing a tag and options', () => { it('should create a new instance when passing a tag and options', () => {
const logger = vi.mocked(consola); const logger = vi.mocked(consola)
const instance = useLogger("tag", { level: 0 }); const instance = useLogger('tag', { level: 0 })
expect(instance).toEqual(logger);
expect(instance).not.toBe(logger);
expect(logger.create).toBeCalledWith({ level: 0 });
expect(logger.withTag).toBeCalledWith("tag");
});
expect(instance).toEqual(logger)
expect(instance).not.toBe(logger)
expect(logger.create).toBeCalledWith({ level: 0 })
expect(logger.withTag).toBeCalledWith('tag')
})
}) })

View File

@ -1,5 +1,5 @@
import { consola } from 'consola' import { consola } from 'consola'
import type { ConsolaOptions } from 'consola'; import type { ConsolaOptions } from 'consola'
export const logger = consola export const logger = consola

View File

@ -22,9 +22,9 @@ const mockNuxt = {
modules: [], modules: [],
_layers: [{ config: { srcDir: '/my-app' } }], _layers: [{ config: { srcDir: '/my-app' } }],
_installedModules: [], _installedModules: [],
_modules: [], _modules: []
}, },
callHook: () => {}, callHook: () => {}
} satisfies DeepPartial<Nuxt> as unknown as Nuxt } satisfies DeepPartial<Nuxt> as unknown as Nuxt
const mockNuxtWithOptions = (options: NuxtConfig) => defu({ options }, mockNuxt) as Nuxt const mockNuxtWithOptions = (options: NuxtConfig) => defu({ options }, mockNuxt) as Nuxt

View File

@ -9,7 +9,7 @@ const fixtures = {
'basic test fixture': 'test/fixtures/basic', 'basic test fixture': 'test/fixtures/basic',
'basic test fixture (types)': 'test/fixtures/basic-types', 'basic test fixture (types)': 'test/fixtures/basic-types',
'minimal test fixture': 'test/fixtures/minimal', 'minimal test fixture': 'test/fixtures/minimal',
'minimal test fixture (types)': 'test/fixtures/minimal-types', 'minimal test fixture (types)': 'test/fixtures/minimal-types'
} }
describe('loadNuxtConfig', () => { describe('loadNuxtConfig', () => {

View File

@ -2,7 +2,9 @@ import { createError } from '../composables/error'
const intervalError = '[nuxt] `setInterval` should not be used on the server. Consider wrapping it with an `onNuxtReady`, `onBeforeMount` or `onMounted` lifecycle hook, or ensure you only call it in the browser by checking `import.meta.client`.' const intervalError = '[nuxt] `setInterval` should not be used on the server. Consider wrapping it with an `onNuxtReady`, `onBeforeMount` or `onMounted` lifecycle hook, or ensure you only call it in the browser by checking `import.meta.client`.'
export const setInterval = import.meta.client ? window.setInterval : () => { export const setInterval = import.meta.client
? window.setInterval
: () => {
if (import.meta.dev) { if (import.meta.dev) {
throw createError({ throw createError({
statusCode: 500, statusCode: 500,
@ -11,4 +13,4 @@ export const setInterval = import.meta.client ? window.setInterval : () => {
} }
console.error(intervalError) console.error(intervalError)
} }

View File

@ -33,7 +33,7 @@ export default defineComponent({
const cache = new WeakMap() const cache = new WeakMap()
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
export function createClientOnly<T extends ComponentOptions> (component: T) { export function createClientOnly<T extends ComponentOptions> (component: T) {
if (cache.has(component)) { if (cache.has(component)) {
return cache.get(component) return cache.get(component)

View File

@ -213,7 +213,7 @@ export default defineComponent({
} }
expose({ expose({
refresh: () => fetchComponent(true), refresh: () => fetchComponent(true)
}) })
if (import.meta.hot) { if (import.meta.hot) {
@ -264,7 +264,7 @@ export default defineComponent({
const { html, slots } = info const { html, slots } = info
let replaced = html.replaceAll('data-island-uid', `data-island-uid="${uid.value}"`) let replaced = html.replaceAll('data-island-uid', `data-island-uid="${uid.value}"`)
for (const slot in slots) { for (const slot in slots) {
replaced = replaced.replaceAll(`data-island-slot="${slot}">`, (full) => full + slots[slot]) replaced = replaced.replaceAll(`data-island-slot="${slot}">`, full => full + slots[slot])
} }
teleports.push(createVNode(Teleport, { to: `uid=${uid.value};client=${id}` }, { teleports.push(createVNode(Teleport, { to: `uid=${uid.value};client=${id}` }, {
default: () => [createStaticVNode(replaced, 1)] default: () => [createStaticVNode(replaced, 1)]

View File

@ -22,29 +22,6 @@ const firstNonUndefined = <T> (...args: (T | undefined)[]) => args.find(arg => a
const NuxtLinkDevKeySymbol: InjectionKey<boolean> = Symbol('nuxt-link-dev-key') const NuxtLinkDevKeySymbol: InjectionKey<boolean> = Symbol('nuxt-link-dev-key')
/**
* Create a NuxtLink component with given options as defaults.
* @see https://nuxt.com/docs/api/components/nuxt-link
*/
export interface NuxtLinkOptions extends
Pick<RouterLinkProps, 'activeClass' | 'exactActiveClass'>,
Pick<NuxtLinkProps, 'prefetchedClass'> {
/**
* The name of the component.
* @default "NuxtLink"
*/
componentName?: string
/**
* A default `rel` attribute value applied on external links. Defaults to `"noopener noreferrer"`. Set it to `""` to disable.
*/
externalRelAttribute?: string | null
/**
* An option to either add or remove trailing slashes in the `href`.
* If unset or not matching the valid values `append` or `remove`, it will be ignored.
*/
trailingSlash?: 'append' | 'remove'
}
/** /**
* <NuxtLink> is a drop-in replacement for both Vue Router's <RouterLink> component and HTML's <a> tag. * <NuxtLink> is a drop-in replacement for both Vue Router's <RouterLink> component and HTML's <a> tag.
* @see https://nuxt.com/docs/api/components/nuxt-link * @see https://nuxt.com/docs/api/components/nuxt-link
@ -88,7 +65,30 @@ export interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
noPrefetch?: boolean noPrefetch?: boolean
} }
/*@__NO_SIDE_EFFECTS__*/ /**
* Create a NuxtLink component with given options as defaults.
* @see https://nuxt.com/docs/api/components/nuxt-link
*/
export interface NuxtLinkOptions extends
Pick<RouterLinkProps, 'activeClass' | 'exactActiveClass'>,
Pick<NuxtLinkProps, 'prefetchedClass'> {
/**
* The name of the component.
* @default "NuxtLink"
*/
componentName?: string
/**
* A default `rel` attribute value applied on external links. Defaults to `"noopener noreferrer"`. Set it to `""` to disable.
*/
externalRelAttribute?: string | null
/**
* An option to either add or remove trailing slashes in the `href`.
* If unset or not matching the valid values `append` or `remove`, it will be ignored.
*/
trailingSlash?: 'append' | 'remove'
}
/* @__NO_SIDE_EFFECTS__ */
export function defineNuxtLink (options: NuxtLinkOptions) { export function defineNuxtLink (options: NuxtLinkOptions) {
const componentName = options.componentName || 'NuxtLink' const componentName = options.componentName || 'NuxtLink'

View File

@ -23,13 +23,13 @@ export default defineComponent({
estimatedProgress: { estimatedProgress: {
type: Function as unknown as () => (duration: number, elapsed: number) => number, type: Function as unknown as () => (duration: number, elapsed: number) => number,
required: false required: false
}, }
}, },
setup (props, { slots, expose }) { setup (props, { slots, expose }) {
const { progress, isLoading, start, finish, clear } = useLoadingIndicator({ const { progress, isLoading, start, finish, clear } = useLoadingIndicator({
duration: props.duration, duration: props.duration,
throttle: props.throttle, throttle: props.throttle,
estimatedProgress: props.estimatedProgress, estimatedProgress: props.estimatedProgress
}) })
expose({ expose({

View File

@ -40,7 +40,7 @@ export default defineComponent({
vnodes.push(h('div', { vnodes.push(h('div', {
style: 'display: contents;', style: 'display: contents;',
'data-island-uid': '', 'data-island-uid': '',
'data-island-slot': props.name, 'data-island-slot': props.name
}, { }, {
// Teleport in slot to not be hydrated client-side with the staticVNode // Teleport in slot to not be hydrated client-side with the staticVNode
default: () => [createVNode(Teleport, { to: `island-slot=${componentName};${props.name}` }, slots.default?.())] default: () => [createVNode(Teleport, { to: `island-slot=${componentName};${props.name}` }, slots.default?.())]
@ -49,7 +49,7 @@ export default defineComponent({
vnodes.push(h('div', { vnodes.push(h('div', {
style: 'display: contents;', style: 'display: contents;',
'data-island-uid': '', 'data-island-uid': '',
'data-island-slot': props.name, 'data-island-slot': props.name
})) }))
} }

View File

@ -214,7 +214,9 @@ export function useAsyncData<
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
// When prerendering, share payload data automatically between requests // When prerendering, share payload data automatically between requests
const handler = import.meta.client || !import.meta.prerender || !nuxtApp.ssrContext?._sharedPrerenderCache ? _handler : () => { const handler = import.meta.client || !import.meta.prerender || !nuxtApp.ssrContext?._sharedPrerenderCache
? _handler
: () => {
const value = nuxtApp.ssrContext!._sharedPrerenderCache!.get(key) const value = nuxtApp.ssrContext!._sharedPrerenderCache!.get(key)
if (value) { return value as Promise<ResT> } if (value) { return value as Promise<ResT> }

View File

@ -28,7 +28,7 @@ async function runLegacyAsyncData (res: Record<string, any> | Promise<Record<str
} }
/** @since 3.0.0 */ /** @since 3.0.0 */
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
export const defineNuxtComponent: typeof defineComponent = export const defineNuxtComponent: typeof defineComponent =
function defineNuxtComponent (...args: any[]): any { function defineNuxtComponent (...args: any[]): any {
const [options, key] = args const [options, key] = args

View File

@ -92,7 +92,7 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
if (store) { if (store) {
store.onchange = (event) => { store.onchange = (event) => {
const cookie = event.changed.find((c: any) => c.name === name) const cookie = event.changed.find((c: any) => c.name === name)
if (cookie) handleChange({ value: cookie.value }) if (cookie) { handleChange({ value: cookie.value }) }
} }
} else if (channel) { } else if (channel) {
channel.onmessage = ({ data }) => handleChange(data) channel.onmessage = ({ data }) => handleChange(data)
@ -124,7 +124,7 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
} }
/** @since 3.10.0 */ /** @since 3.10.0 */
export function refreshCookie (name: string) { export function refreshCookie (name: string) {
if (store || typeof BroadcastChannel === 'undefined') return if (store || typeof BroadcastChannel === 'undefined') { return }
new BroadcastChannel(`nuxt:cookies:${name}`)?.postMessage({ refresh: true }) new BroadcastChannel(`nuxt:cookies:${name}`)?.postMessage({ refresh: true })
} }

View File

@ -53,9 +53,7 @@ export const clearError = async (options: { redirect?: string } = {}) => {
/** @since 3.0.0 */ /** @since 3.0.0 */
export const isNuxtError = <DataT = unknown>( export const isNuxtError = <DataT = unknown>(
error?: string | object error?: string | object
): error is NuxtError<DataT> => ( ): error is NuxtError<DataT> => !!error && typeof error === 'object' && NUXT_ERROR_SIGNATURE in error
!!error && typeof error === 'object' && NUXT_ERROR_SIGNATURE in error
)
/** @since 3.0.0 */ /** @since 3.0.0 */
export const createError = <DataT = unknown>( export const createError = <DataT = unknown>(

View File

@ -243,10 +243,10 @@ export function useLazyFetch<
autoKey) autoKey)
} }
function generateOptionSegments <_ResT, DataT, DefaultT>(opts: UseFetchOptions<_ResT, DataT, any, DefaultT, any, any>) { function generateOptionSegments <_ResT, DataT, DefaultT> (opts: UseFetchOptions<_ResT, DataT, any, DefaultT, any, any>) {
const segments: Array<string | undefined | Record<string, string>> = [ const segments: Array<string | undefined | Record<string, string>> = [
toValue(opts.method as MaybeRef<string | undefined> | undefined)?.toUpperCase() || 'GET', toValue(opts.method as MaybeRef<string | undefined> | undefined)?.toUpperCase() || 'GET',
toValue(opts.baseURL), toValue(opts.baseURL)
] ]
for (const _obj of [opts.params || opts.query]) { for (const _obj of [opts.params || opts.query]) {
const obj = toValue(_obj) const obj = toValue(_obj)

View File

@ -29,7 +29,7 @@ export function usePreviewMode<S extends EnteredState> (options: PreviewModeOpti
if (preview.value._initialized) { if (preview.value._initialized) {
return { return {
enabled: toRef(preview.value, 'enabled'), enabled: toRef(preview.value, 'enabled'),
state: preview.value.state as S extends void ? Preview['state'] : (NonNullable<S> & Preview['state']), state: preview.value.state as S extends void ? Preview['state'] : (NonNullable<S> & Preview['state'])
} }
} }
@ -56,7 +56,7 @@ export function usePreviewMode<S extends EnteredState> (options: PreviewModeOpti
if (import.meta.client && !unregisterRefreshHook) { if (import.meta.client && !unregisterRefreshHook) {
refreshNuxtData() refreshNuxtData()
unregisterRefreshHook = useRouter().afterEach((() => refreshNuxtData())) unregisterRefreshHook = useRouter().afterEach(() => refreshNuxtData())
} }
} else if (unregisterRefreshHook) { } else if (unregisterRefreshHook) {
unregisterRefreshHook() unregisterRefreshHook()
@ -67,7 +67,7 @@ export function usePreviewMode<S extends EnteredState> (options: PreviewModeOpti
return { return {
enabled: toRef(preview.value, 'enabled'), enabled: toRef(preview.value, 'enabled'),
state: preview.value.state as S extends void ? Preview['state'] : (NonNullable<S> & Preview['state']), state: preview.value.state as S extends void ? Preview['state'] : (NonNullable<S> & Preview['state'])
} }
} }

View File

@ -48,7 +48,7 @@ export interface RouteMiddleware {
} }
/** @since 3.0.0 */ /** @since 3.0.0 */
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
export function defineNuxtRouteMiddleware (middleware: RouteMiddleware) { export function defineNuxtRouteMiddleware (middleware: RouteMiddleware) {
return middleware return middleware
} }

View File

@ -61,7 +61,7 @@ if (import.meta.client) {
const nuxt = createNuxtApp({ vueApp }) const nuxt = createNuxtApp({ vueApp })
async function handleVueError(error: any) { async function handleVueError (error: any) {
await nuxt.callHook('app:error', error) await nuxt.callHook('app:error', error)
nuxt.payload.error = nuxt.payload.error || createError(error as any) nuxt.payload.error = nuxt.payload.error || createError(error as any)
} }
@ -85,8 +85,7 @@ if (import.meta.client) {
} }
// If the errorHandler is not overridden by the user, we unset it // If the errorHandler is not overridden by the user, we unset it
if (vueApp.config.errorHandler === handleVueError) if (vueApp.config.errorHandler === handleVueError) { vueApp.config.errorHandler = undefined }
vueApp.config.errorHandler = undefined
return vueApp return vueApp
} }

View File

@ -21,7 +21,7 @@ import type { ViewTransition } from './plugins/view-transitions.client'
import type { NuxtAppLiterals } from '#app' import type { NuxtAppLiterals } from '#app'
const nuxtAppCtx = /*@__PURE__*/ getContext<NuxtApp>('nuxt-app', { const nuxtAppCtx = /* @__PURE__ */ getContext<NuxtApp>('nuxt-app', {
asyncContext: !!__NUXT_ASYNC_CONTEXT__ && import.meta.server asyncContext: !!__NUXT_ASYNC_CONTEXT__ && import.meta.server
}) })
@ -406,7 +406,7 @@ export async function applyPlugins (nuxtApp: NuxtApp, plugins: Array<Plugin & Ob
if (errors.length) { throw errors[0] } if (errors.length) { throw errors[0] }
} }
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPlugin<T>): Plugin<T> & ObjectPlugin<T> { export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPlugin<T>): Plugin<T> & ObjectPlugin<T> {
if (typeof plugin === 'function') { return plugin } if (typeof plugin === 'function') { return plugin }
@ -415,7 +415,7 @@ export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plu
return Object.assign(plugin.setup || (() => {}), plugin, { [NuxtPluginIndicator]: true, _name } as const) return Object.assign(plugin.setup || (() => {}), plugin, { [NuxtPluginIndicator]: true, _name } as const)
} }
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
export const definePayloadPlugin = defineNuxtPlugin export const definePayloadPlugin = defineNuxtPlugin
export function isNuxtPlugin (plugin: unknown) { export function isNuxtPlugin (plugin: unknown) {
@ -438,7 +438,7 @@ export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp |
} }
} }
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
/** /**
* Returns the current Nuxt instance. * Returns the current Nuxt instance.
* *
@ -455,7 +455,7 @@ export function tryUseNuxtApp (): NuxtApp | null {
return nuxtAppInstance || null return nuxtAppInstance || null
} }
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
/** /**
* Returns the current Nuxt instance. * Returns the current Nuxt instance.
* *
@ -475,7 +475,7 @@ export function useNuxtApp (): NuxtApp {
return nuxtAppInstance return nuxtAppInstance
} }
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
export function useRuntimeConfig (_event?: H3Event<EventHandlerRequest>): RuntimeConfig { export function useRuntimeConfig (_event?: H3Event<EventHandlerRequest>): RuntimeConfig {
return useNuxtApp().$config return useNuxtApp().$config
} }

View File

@ -83,7 +83,7 @@ export const islandsTransform = createUnplugin((options: ServerOnlyComponentTran
if (children.length) { if (children.length) {
const attrString = Object.entries(attributes).map(([name, value]) => name ? `${name}="${value}" ` : value).join(' ') const attrString = Object.entries(attributes).map(([name, value]) => name ? `${name}="${value}" ` : value).join(' ')
const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(/:?key="[^"]"/g, '') const slice = code.slice(startingIndex + loc[0].end, startingIndex + loc[1].start).replaceAll(/:?key="[^"]"/g, '')
s.overwrite(startingIndex + loc[0].start, startingIndex + loc[1].end, `<slot ${attrString} /><template #fallback>${attributes["v-for"] ? wrapWithVForDiv(slice, attributes['v-for']) : slice}</template>`) s.overwrite(startingIndex + loc[0].start, startingIndex + loc[1].end, `<slot ${attrString} /><template #fallback>${attributes['v-for'] ? wrapWithVForDiv(slice, attributes['v-for']) : slice}</template>`)
} }
const slotName = attributes.name ?? 'default' const slotName = attributes.name ?? 'default'

View File

@ -231,17 +231,17 @@ export default defineNuxtModule<ComponentsOptions>({
if (isClient && selectiveClient) { if (isClient && selectiveClient) {
fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}') fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}')
if(!nuxt.options.dev) { if (!nuxt.options.dev) {
config.plugins.push(componentsChunkPlugin.vite({ config.plugins.push(componentsChunkPlugin.vite({
getComponents, getComponents,
buildDir: nuxt.options.buildDir buildDir: nuxt.options.buildDir
})) }))
} else { } else {
fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'),`export const paths = ${JSON.stringify( fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), `export const paths = ${JSON.stringify(
getComponents().filter(c => c.mode === 'client' || c.mode === 'all').reduce((acc, c) => { getComponents().filter(c => c.mode === 'client' || c.mode === 'all').reduce((acc, c) => {
if(c.filePath.endsWith('.vue') || c.filePath.endsWith('.js') || c.filePath.endsWith('.ts')) return Object.assign(acc, {[c.pascalName]: `/@fs/${c.filePath}`}) if (c.filePath.endsWith('.vue') || c.filePath.endsWith('.js') || c.filePath.endsWith('.ts')) { return Object.assign(acc, { [c.pascalName]: `/@fs/${c.filePath}` }) }
const filePath = fs.existsSync( `${c.filePath}.vue`) ? `${c.filePath}.vue` : fs.existsSync( `${c.filePath}.js`) ? `${c.filePath}.js` : `${c.filePath}.ts` const filePath = fs.existsSync(`${c.filePath}.vue`) ? `${c.filePath}.vue` : fs.existsSync(`${c.filePath}.js`) ? `${c.filePath}.js` : `${c.filePath}.ts`
return Object.assign(acc, {[c.pascalName]: `/@fs/${filePath}`}) return Object.assign(acc, { [c.pascalName]: `/@fs/${filePath}` })
}, {} as Record<string, string>) }, {} as Record<string, string>)
)}`) )}`)
} }

View File

@ -1,8 +1,8 @@
import { defineAsyncComponent, defineComponent, h } from 'vue' import { defineAsyncComponent, defineComponent, h } from 'vue'
import type { AsyncComponentLoader } from 'vue' import type { AsyncComponentLoader } from 'vue'
import { default as ClientOnly } from '#app/components/client-only' import ClientOnly from '#app/components/client-only'
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
export const createClientPage = (loader: AsyncComponentLoader) => { export const createClientPage = (loader: AsyncComponentLoader) => {
const page = defineAsyncComponent(loader) const page = defineAsyncComponent(loader)

View File

@ -3,7 +3,7 @@ import NuxtIsland from '#app/components/nuxt-island'
import { useRoute } from '#app/composables/router' import { useRoute } from '#app/composables/router'
import { isPrerendered } from '#app/composables/payload' import { isPrerendered } from '#app/composables/payload'
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
export const createServerComponent = (name: string) => { export const createServerComponent = (name: string) => {
return defineComponent({ return defineComponent({
name, name,
@ -32,7 +32,7 @@ export const createServerComponent = (name: string) => {
}) })
} }
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
export const createIslandPage = (name: string) => { export const createIslandPage = (name: string) => {
return defineComponent({ return defineComponent({
name, name,

View File

@ -227,7 +227,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
output: {}, output: {},
plugins: [] plugins: []
}, },
logLevel: logLevelMapReverse[nuxt.options.logLevel], logLevel: logLevelMapReverse[nuxt.options.logLevel]
} satisfies NitroConfig) } satisfies NitroConfig)
// Resolve user-provided paths // Resolve user-provided paths

View File

@ -64,11 +64,11 @@ async function initNuxt (nuxt: Nuxt) {
nuxt.hook('close', () => nuxtCtx.unset()) nuxt.hook('close', () => nuxtCtx.unset())
const coreTypePackages = ['nitropack', 'defu', 'h3', '@unhead/vue', 'vue', 'vue-router', '@nuxt/schema'] const coreTypePackages = ['nitropack', 'defu', 'h3', '@unhead/vue', 'vue', 'vue-router', '@nuxt/schema']
const paths = Object.fromEntries(await Promise.all(coreTypePackages.map(async pkg => { const paths = Object.fromEntries(await Promise.all(coreTypePackages.map(async (pkg) => {
const path = await _resolvePath(pkg, { url: nuxt.options.modulesDir }).then(r => resolvePackageJSON(r)).catch(() => null) const path = await _resolvePath(pkg, { url: nuxt.options.modulesDir }).then(r => resolvePackageJSON(r)).catch(() => null)
if (!path) { return } if (!path) { return }
return [pkg, [dirname(path)]] return [pkg, [dirname(path)]]
})).then((r) => r.filter(Boolean) as [string, [string]][])) })).then(r => r.filter(Boolean) as [string, [string]][]))
// Set nitro resolutions for types that might be obscured with shamefully-hoist=false // Set nitro resolutions for types that might be obscured with shamefully-hoist=false
nuxt.options.nitro.typescript = defu(nuxt.options.nitro.typescript, { nuxt.options.nitro.typescript = defu(nuxt.options.nitro.typescript, {

View File

@ -17,7 +17,7 @@ export const UnctxTransformPlugin = createUnplugin((options: UnctxTransformPlugi
name: 'unctx:transform', name: 'unctx:transform',
enforce: 'post', enforce: 'post',
transformInclude (id) { transformInclude (id) {
return isVue(id, { type: ['template', 'script']}) || isJS(id) return isVue(id, { type: ['template', 'script'] }) || isJS(id)
}, },
transform (code) { transform (code) {
// TODO: needed for webpack - update transform in unctx/unplugin? // TODO: needed for webpack - update transform in unctx/unplugin?

View File

@ -21,6 +21,6 @@ export default defineDriver((opts: { base: string }) => {
}, },
async getItem (key, opts) { async getItem (key, opts) {
return await lru.getItem(key, opts) || await fs.getItem(normalizeFsKey(key), opts) return await lru.getItem(key, opts) || await fs.getItem(normalizeFsKey(key), opts)
}, }
} }
}) })

View File

@ -54,7 +54,9 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
const isRenderingError = event.path.startsWith('/__nuxt_error') || !!reqHeaders['x-nuxt-error'] const isRenderingError = event.path.startsWith('/__nuxt_error') || !!reqHeaders['x-nuxt-error']
// HTML response (via SSR) // HTML response (via SSR)
const res = isRenderingError ? null : await useNitroApp().localFetch( const res = isRenderingError
? null
: await useNitroApp().localFetch(
withQuery(joinURL(useRuntimeConfig(event).app.baseURL, '/__nuxt_error'), errorObject), withQuery(joinURL(useRuntimeConfig(event).app.baseURL, '/__nuxt_error'), errorObject),
{ {
headers: { ...reqHeaders, 'x-nuxt-error': 'true' }, headers: { ...reqHeaders, 'x-nuxt-error': 'true' },

View File

@ -53,6 +53,18 @@ export interface NuxtRenderHTMLContext {
bodyAppend: string[] bodyAppend: string[]
} }
export interface NuxtIslandSlotResponse {
props: Array<unknown>
fallback?: string
}
export interface NuxtIslandClientResponse {
html: string
props: unknown
chunk: string
slots?: Record<string, string>
}
export interface NuxtIslandContext { export interface NuxtIslandContext {
id?: string id?: string
name: string name: string
@ -62,17 +74,6 @@ export interface NuxtIslandContext {
components: Record<string, Omit<NuxtIslandClientResponse, 'html'>> components: Record<string, Omit<NuxtIslandClientResponse, 'html'>>
} }
export interface NuxtIslandSlotResponse {
props: Array<unknown>
fallback?: string
}
export interface NuxtIslandClientResponse {
html: string
props: unknown
chunk: string
slots?: Record<string, string>
}
export interface NuxtIslandResponse { export interface NuxtIslandResponse {
id?: string id?: string
html: string html: string
@ -186,20 +187,22 @@ const islandCache = import.meta.prerender ? useStorage('internal:nuxt:prerender:
const islandPropCache = import.meta.prerender ? useStorage('internal:nuxt:prerender:island-props') : null const islandPropCache = import.meta.prerender ? useStorage('internal:nuxt:prerender:island-props') : null
const sharedPrerenderPromises = import.meta.prerender && process.env.NUXT_SHARED_DATA ? new Map<string, Promise<any>>() : null const sharedPrerenderPromises = import.meta.prerender && process.env.NUXT_SHARED_DATA ? new Map<string, Promise<any>>() : null
const sharedPrerenderKeys = new Set<string>() const sharedPrerenderKeys = new Set<string>()
const sharedPrerenderCache = import.meta.prerender && process.env.NUXT_SHARED_DATA ? { const sharedPrerenderCache = import.meta.prerender && process.env.NUXT_SHARED_DATA
get <T = unknown>(key: string): Promise<T> | undefined { ? {
get<T = unknown> (key: string): Promise<T> | undefined {
if (sharedPrerenderKeys.has(key)) { if (sharedPrerenderKeys.has(key)) {
return sharedPrerenderPromises!.get(key) ?? useStorage('internal:nuxt:prerender:shared').getItem(key) as Promise<T> return sharedPrerenderPromises!.get(key) ?? useStorage('internal:nuxt:prerender:shared').getItem(key) as Promise<T>
} }
}, },
async set <T>(key: string, value: Promise<T>): Promise<void> { async set<T> (key: string, value: Promise<T>): Promise<void> {
sharedPrerenderKeys.add(key) sharedPrerenderKeys.add(key)
sharedPrerenderPromises!.set(key, value) sharedPrerenderPromises!.set(key, value)
useStorage('internal:nuxt:prerender:shared').setItem(key, await value as any) useStorage('internal:nuxt:prerender:shared').setItem(key, await value as any)
// free up memory after the promise is resolved // free up memory after the promise is resolved
.finally(() => sharedPrerenderPromises!.delete(key)) .finally(() => sharedPrerenderPromises!.delete(key))
}, }
} : null }
: null
async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> { async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
// TODO: Strict validation for url // TODO: Strict validation for url
@ -221,7 +224,7 @@ async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
name: componentName, name: componentName,
props: destr(context.props) || {}, props: destr(context.props) || {},
slots: {}, slots: {},
components: {}, components: {}
} }
return ctx return ctx
@ -302,8 +305,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
payload: (ssrError ? { error: ssrError } : {}) as NuxtPayload, payload: (ssrError ? { error: ssrError } : {}) as NuxtPayload,
_payloadReducers: {}, _payloadReducers: {},
modules: new Set(), modules: new Set(),
set _registeredComponents(value) { this.modules = value }, set _registeredComponents (value) { this.modules = value },
get _registeredComponents() { return this.modules }, get _registeredComponents () { return this.modules },
islandContext islandContext
} }
@ -327,7 +330,6 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
writeEarlyHints(event, link) writeEarlyHints(event, link)
} }
if (process.env.NUXT_INLINE_STYLES && !isRenderingIsland) { if (process.env.NUXT_INLINE_STYLES && !isRenderingIsland) {
for (const id of await getEntryIds()) { for (const id of await getEntryIds()) {
ssrContext.modules!.add(id) ssrContext.modules!.add(id)
@ -537,16 +539,16 @@ function joinTags (tags: string[]) {
} }
function joinAttrs (chunks: string[]) { function joinAttrs (chunks: string[]) {
if (chunks.length === 0) return '' if (chunks.length === 0) { return '' }
return ' ' + chunks.join(' ') return ' ' + chunks.join(' ')
} }
function renderHTMLDocument (html: NuxtRenderHTMLContext) { function renderHTMLDocument (html: NuxtRenderHTMLContext) {
return '<!DOCTYPE html>' return '<!DOCTYPE html>' +
+ `<html${joinAttrs(html.htmlAttrs)}>` `<html${joinAttrs(html.htmlAttrs)}>` +
+ `<head>${joinTags(html.head)}</head>` `<head>${joinTags(html.head)}</head>` +
+ `<body${joinAttrs(html.bodyAttrs)}>${joinTags(html.bodyPrepend)}${joinTags(html.body)}${joinTags(html.bodyAppend)}</body>` `<body${joinAttrs(html.bodyAttrs)}>${joinTags(html.bodyPrepend)}${joinTags(html.body)}${joinTags(html.bodyAppend)}</body>` +
+ '</html>' '</html>'
} }
async function renderInlineStyles (usedModules: Set<string> | string[]): Promise<Style[]> { async function renderInlineStyles (usedModules: Set<string> | string[]): Promise<Style[]> {
@ -639,7 +641,7 @@ function getSlotIslandResponse (ssrContext: NuxtSSRContext): NuxtIslandResponse[
for (const slot in ssrContext.islandContext.slots) { for (const slot in ssrContext.islandContext.slots) {
response[slot] = { response[slot] = {
...ssrContext.islandContext.slots[slot], ...ssrContext.islandContext.slots[slot],
fallback: ssrContext.teleports?.[`island-fallback=${slot}`], fallback: ssrContext.teleports?.[`island-fallback=${slot}`]
} }
} }
return response return response

View File

@ -48,7 +48,7 @@ export const errorComponentTemplate: NuxtTemplate = {
// TODO: Use an alias // TODO: Use an alias
export const testComponentWrapperTemplate: NuxtTemplate = { export const testComponentWrapperTemplate: NuxtTemplate = {
filename: 'test-component-wrapper.mjs', filename: 'test-component-wrapper.mjs',
getContents: (ctx) => genExport(resolve(ctx.nuxt.options.appDir, 'components/test-component-wrapper'), ['default']) getContents: ctx => genExport(resolve(ctx.nuxt.options.appDir, 'components/test-component-wrapper'), ['default'])
} }
export const cssTemplate: NuxtTemplate = { export const cssTemplate: NuxtTemplate = {

View File

@ -17,7 +17,7 @@ const removeUndefinedProps = (props: Props) => {
for (const key in props) { for (const key in props) {
const value = props[key] const value = props[key]
if (value !== undefined) { if (value !== undefined) {
filteredProps[key] = value; filteredProps[key] = value
} }
} }
return filteredProps return filteredProps

View File

@ -354,14 +354,14 @@ export default defineNuxtModule({
if (nuxt.options.experimental.appManifest) { if (nuxt.options.experimental.appManifest) {
// Add all redirect paths as valid routes to router; we will handle these in a client-side middleware // Add all redirect paths as valid routes to router; we will handle these in a client-side middleware
// when the app manifest is enabled. // when the app manifest is enabled.
nuxt.hook('pages:extend', routes => { nuxt.hook('pages:extend', (routes) => {
const nitro = useNitro() const nitro = useNitro()
for (const path in nitro.options.routeRules) { for (const path in nitro.options.routeRules) {
const rule = nitro.options.routeRules[path] const rule = nitro.options.routeRules[path]
if (!rule.redirect) { continue } if (!rule.redirect) { continue }
routes.push({ routes.push({
path: path.replace(/\/[^/]*\*\*/, '/:pathMatch(.*)'), path: path.replace(/\/[^/]*\*\*/, '/:pathMatch(.*)'),
file: resolve(runtimeDir, 'component-stub'), file: resolve(runtimeDir, 'component-stub')
}) })
} }
}) })
@ -400,7 +400,7 @@ export default defineNuxtModule({
const sourceFiles = nuxt.apps.default?.pages?.length ? getSources(nuxt.apps.default.pages) : [] const sourceFiles = nuxt.apps.default?.pages?.length ? getSources(nuxt.apps.default.pages) : []
for (const key in manifest) { for (const key in manifest) {
if (manifest[key].src && Object.values(nuxt.apps).some(app => app.pages?.some(page => page.mode === 'server' && page.file === join(nuxt.options.srcDir, manifest[key].src!) ))) { if (manifest[key].src && Object.values(nuxt.apps).some(app => app.pages?.some(page => page.mode === 'server' && page.file === join(nuxt.options.srcDir, manifest[key].src!)))) {
delete manifest[key] delete manifest[key]
continue continue
} }
@ -415,7 +415,7 @@ export default defineNuxtModule({
addTemplate({ addTemplate({
filename: 'routes.mjs', filename: 'routes.mjs',
getContents ({ app }) { getContents ({ app }) {
if (!app.pages) return 'export default []' if (!app.pages) { return 'export default []' }
const { routes, imports } = normalizeRoutes(app.pages, new Set(), nuxt.options.experimental.scanPageMeta) const { routes, imports } = normalizeRoutes(app.pages, new Set(), nuxt.options.experimental.scanPageMeta)
return [...imports, `export default ${routes}`].join('\n') return [...imports, `export default ${routes}`].join('\n')
} }
@ -490,7 +490,6 @@ export default defineNuxtModule({
} }
}) })
// add page meta types if enabled // add page meta types if enabled
if (nuxt.options.experimental.viewTransition) { if (nuxt.options.experimental.viewTransition) {
addTypeTemplate({ addTypeTemplate({
@ -502,9 +501,9 @@ export default defineNuxtModule({
'import type { ComputedRef, MaybeRef } from \'vue\'', 'import type { ComputedRef, MaybeRef } from \'vue\'',
`declare module ${genString(composablesFile)} {`, `declare module ${genString(composablesFile)} {`,
' interface PageMeta {', ' interface PageMeta {',
` viewTransition?: boolean | 'always'`, ' viewTransition?: boolean | \'always\'',
' }', ' }',
'}', '}'
].join('\n') ].join('\n')
} }
}) })

View File

@ -78,6 +78,6 @@ export const definePageMeta = (meta: PageMeta): void => {
* For more control, such as if you are using a custom `path` or `alias` set in the page's `definePageMeta`, you * For more control, such as if you are using a custom `path` or `alias` set in the page's `definePageMeta`, you
* should set `routeRules` directly within your `nuxt.config`. * should set `routeRules` directly within your `nuxt.config`.
*/ */
/*@__NO_SIDE_EFFECTS__*/ /* @__NO_SIDE_EFFECTS__ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
export const defineRouteRules = (rules: NitroRouteConfig): void => {} export const defineRouteRules = (rules: NitroRouteConfig): void => {}

View File

@ -4,9 +4,8 @@ import { RouterView } from '#vue-router'
import { defu } from 'defu' import { defu } from 'defu'
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from '#vue-router' import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from '#vue-router'
import { toArray } from './utils' import { generateRouteKey, toArray, wrapInKeepAlive } from './utils'
import type { RouterViewSlotProps } from './utils' import type { RouterViewSlotProps } from './utils'
import { generateRouteKey, wrapInKeepAlive } from './utils'
import { RouteProvider } from '#app/components/route-provider' import { RouteProvider } from '#app/components/route-provider'
import { useNuxtApp } from '#app/nuxt' import { useNuxtApp } from '#app/nuxt'
import { useRouter } from '#app/composables/router' import { useRouter } from '#app/composables/router'

View File

@ -11,9 +11,9 @@ export default defineNuxtPlugin({
function checkIfPageUnused () { function checkIfPageUnused () {
if (!error.value && !nuxtApp._isNuxtPageUsed) { if (!error.value && !nuxtApp._isNuxtPageUsed) {
console.warn( console.warn(
'[nuxt] Your project has pages but the `<NuxtPage />` component has not been used.' '[nuxt] Your project has pages but the `<NuxtPage />` component has not been used.' +
+ ' You might be using the `<RouterView />` component instead, which will not work correctly in Nuxt.' ' You might be using the `<RouterView />` component instead, which will not work correctly in Nuxt.' +
+ ' You can set `pages: false` in `nuxt.config` if you do not wish to use the Nuxt `vue-router` integration.' ' You can set `pages: false` in `nuxt.config` if you do not wish to use the Nuxt `vue-router` integration.'
) )
} }
} }

View File

@ -84,7 +84,7 @@ export async function generateRoutesFromFiles (files: ScannedFile[], options: Ge
name: '', name: '',
path: '', path: '',
file: file.absolutePath, file: file.absolutePath,
children: [], children: []
} }
// Array where routes should be added, useful when adding child routes // Array where routes should be added, useful when adding child routes
@ -399,7 +399,7 @@ function prepareRoutes (routes: NuxtPage[], parent?: NuxtPage, names = new Set<s
} }
function serializeRouteValue (value: any, skipSerialisation = false) { function serializeRouteValue (value: any, skipSerialisation = false) {
if (skipSerialisation || value === undefined) return undefined if (skipSerialisation || value === undefined) { return undefined }
return JSON.stringify(value) return JSON.stringify(value)
} }
@ -425,7 +425,7 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
name: serializeRouteValue(page.name), name: serializeRouteValue(page.name),
meta: serializeRouteValue(metaFiltered, skipMeta), meta: serializeRouteValue(metaFiltered, skipMeta),
alias: serializeRouteValue(toArray(page.alias), skipAlias), alias: serializeRouteValue(toArray(page.alias), skipAlias),
redirect: serializeRouteValue(page.redirect), redirect: serializeRouteValue(page.redirect)
} }
for (const key of ['path', 'name', 'meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) { for (const key of ['path', 'name', 'meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) {
@ -487,13 +487,13 @@ async function createClientPage(loader) {
// skip and retain fallback if marked dynamic // skip and retain fallback if marked dynamic
// set to extracted value or fallback if none extracted // set to extracted value or fallback if none extracted
for (const key of ['name', 'path'] satisfies NormalizedRouteKeys) { for (const key of ['name', 'path'] satisfies NormalizedRouteKeys) {
if (markedDynamic.has(key)) continue if (markedDynamic.has(key)) { continue }
metaRoute[key] = route[key] ?? metaRoute[key] metaRoute[key] = route[key] ?? metaRoute[key]
} }
// set to extracted value or delete if none extracted // set to extracted value or delete if none extracted
for (const key of ['meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) { for (const key of ['meta', 'alias', 'redirect'] satisfies NormalizedRouteKeys) {
if (markedDynamic.has(key)) continue if (markedDynamic.has(key)) { continue }
if (route[key] == null) { if (route[key] == null) {
delete metaRoute[key] delete metaRoute[key]

View File

@ -87,7 +87,7 @@ describe('resolveApp', () => {
'middleware/other.ts', 'middleware/other.ts',
'layouts/index.vue', 'layouts/index.vue',
'layouts/default/index.vue', 'layouts/default/index.vue',
'layouts/other.vue', 'layouts/other.vue'
]) ])
// Middleware are not resolved in a nested manner // Middleware are not resolved in a nested manner
expect(app.middleware.filter(m => m.path.startsWith('<rootDir>'))).toMatchInlineSnapshot(` expect(app.middleware.filter(m => m.path.startsWith('<rootDir>'))).toMatchInlineSnapshot(`

View File

@ -351,7 +351,7 @@ describe('islandTransform - server and island components', () => {
" "
`) `)
expect(result).toContain(`import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'`) expect(result).toContain('import NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'')
}) })
}) })

View File

@ -14,7 +14,7 @@ describe('loadNuxt', () => {
ready: true, ready: true,
overrides: { overrides: {
hooks: { hooks: {
ready() { ready () {
hookRan = true hookRan = true
} }
} }

View File

@ -11,10 +11,10 @@ describe('pages:generateRoutesFromFiles', () => {
vi.mock('knitwork', async (original) => { vi.mock('knitwork', async (original) => {
return { return {
...(await original<typeof import('knitwork')>()), ...(await original<typeof import('knitwork')>()),
'genArrayFromRaw': (val: any) => val, genArrayFromRaw: (val: any) => val,
'genSafeVariableName': (..._args: string[]) => { genSafeVariableName: (..._args: string[]) => {
return 'mock' return 'mock'
}, }
} }
}) })
@ -471,7 +471,7 @@ describe('pages:generateRoutesFromFiles', () => {
name: 'index', name: 'index',
path: '/' path: '/'
} }
], ]
}, },
{ {
description: 'should use fallbacks when normalized with `overrideMeta: true`', description: 'should use fallbacks when normalized with `overrideMeta: true`',
@ -527,7 +527,7 @@ describe('pages:generateRoutesFromFiles', () => {
alias: ['sweet-home'], alias: ['sweet-home'],
redirect: '/', redirect: '/',
children: [], children: [],
meta: { [DYNAMIC_META_KEY]: new Set(['meta']) }, meta: { [DYNAMIC_META_KEY]: new Set(['meta']) }
} }
] ]
}, },
@ -538,7 +538,7 @@ describe('pages:generateRoutesFromFiles', () => {
name: 'home', name: 'home',
path: '/', path: '/',
alias: ['sweet-home'], alias: ['sweet-home'],
meta: { hello: 'world' }, meta: { hello: 'world' }
} }
] ]
}, },
@ -550,10 +550,10 @@ describe('pages:generateRoutesFromFiles', () => {
path: '/', path: '/',
alias: ['pushed-route-alias'], alias: ['pushed-route-alias'],
meta: { someMetaData: true }, meta: { someMetaData: true },
file: `${pagesDir}/route-file.vue`, file: `${pagesDir}/route-file.vue`
} }
] ]
}, }
] ]
const normalizedResults: Record<string, any> = {} const normalizedResults: Record<string, any> = {}
@ -561,7 +561,6 @@ describe('pages:generateRoutesFromFiles', () => {
for (const test of tests) { for (const test of tests) {
it(test.description, async () => { it(test.description, async () => {
let result let result
if (test.files) { if (test.files) {
const vfs = Object.fromEntries( const vfs = Object.fromEntries(

View File

@ -1,11 +1,11 @@
/// <reference types="nitropack" /> /// <reference types="nitropack" />
export * from './dist/index'
import type { DefineNuxtConfig } from 'nuxt/config' import type { DefineNuxtConfig } from 'nuxt/config'
import type { RuntimeConfig, SchemaDefinition } from 'nuxt/schema' import type { RuntimeConfig, SchemaDefinition } from 'nuxt/schema'
import type { H3Event } from 'h3' import type { H3Event } from 'h3'
import type { NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext } from './dist/app/types' import type { NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext } from './dist/app/types'
export * from './dist/index'
declare global { declare global {
const defineNuxtConfig: DefineNuxtConfig const defineNuxtConfig: DefineNuxtConfig
const defineNuxtSchema: (schema: SchemaDefinition) => SchemaDefinition const defineNuxtSchema: (schema: SchemaDefinition) => SchemaDefinition

View File

@ -27,7 +27,7 @@ export default defineUntypedSchema({
* @see [Vue RFC#502](https://github.com/vuejs/rfcs/discussions/502) * @see [Vue RFC#502](https://github.com/vuejs/rfcs/discussions/502)
* @type {boolean} * @type {boolean}
*/ */
propsDestructure: false, propsDestructure: false
}, },
/** /**
@ -157,7 +157,7 @@ export default defineUntypedSchema({
*/ */
viewTransition: { viewTransition: {
$resolve: async (val, get) => val ?? await (get('experimental') as Promise<Record<string, any>>).then( $resolve: async (val, get) => val ?? await (get('experimental') as Promise<Record<string, any>>).then(
(e) => e?.viewTransition e => e?.viewTransition
) ?? false ) ?? false
}, },

View File

@ -27,7 +27,7 @@ export default defineUntypedSchema({
} }
return true return true
} }
}, }
}, },
/** /**
* Some features of Nuxt are available on an opt-in basis, or can be disabled based on your needs. * Some features of Nuxt are available on an opt-in basis, or can be disabled based on your needs.
@ -61,7 +61,7 @@ export default defineUntypedSchema({
// TODO: remove in v3.10 // TODO: remove in v3.10
return val ?? await (get('experimental') as Promise<Record<string, any>>).then((e: Record<string, any>) => e?.noScripts) ?? false return val ?? await (get('experimental') as Promise<Record<string, any>>).then((e: Record<string, any>) => e?.noScripts) ?? false
} }
}, }
}, },
experimental: { experimental: {
/** /**
@ -335,6 +335,6 @@ export default defineUntypedSchema({
* ``` * ```
* @type {boolean} * @type {boolean}
*/ */
clientNodeCompat: false, clientNodeCompat: false
} }
}) })

View File

@ -70,7 +70,7 @@ export default defineUntypedSchema({
exclude: { exclude: {
$resolve: async (val: string[] | undefined, get) => [ $resolve: async (val: string[] | undefined, get) => [
...val || [], ...val || [],
...(await get('build.transpile') as Array<string | RegExp | ((ctx: { isClient?: boolean; isServer?: boolean; isDev: boolean }) => string | RegExp | false)>).filter((i) => typeof i === 'string'), ...(await get('build.transpile') as Array<string | RegExp | ((ctx: { isClient?: boolean; isServer?: boolean; isDev: boolean }) => string | RegExp | false)>).filter(i => typeof i === 'string'),
'vue-demi' 'vue-demi'
] ]
} }

View File

@ -206,7 +206,7 @@ export default defineUntypedSchema({
embed: 'src' embed: 'src'
}, },
compilerOptions: { $resolve: async (val, get) => val ?? (await get('vue.compilerOptions')) }, compilerOptions: { $resolve: async (val, get) => val ?? (await get('vue.compilerOptions')) },
propsDestructure: { $resolve: async (val, get) => val ?? Boolean(await get('vue.propsDestructure')) }, propsDestructure: { $resolve: async (val, get) => val ?? Boolean(await get('vue.propsDestructure')) }
}, },
css: { css: {

View File

@ -36,6 +36,7 @@ export interface NuxtTemplate<Options = TemplateDefaultOptions> {
/** The resolved path to the source file to be template */ /** The resolved path to the source file to be template */
src?: string src?: string
/** Provided compile option instead of src */ /** Provided compile option instead of src */
// eslint-disable-next-line no-use-before-define
getContents?: (data: { nuxt: Nuxt, app: NuxtApp, options: Options }) => string | Promise<string> getContents?: (data: { nuxt: Nuxt, app: NuxtApp, options: Options }) => string | Promise<string>
/** Write to filesystem */ /** Write to filesystem */
write?: boolean write?: boolean

View File

@ -20,12 +20,14 @@ import { viteNodePlugin } from './vite-node'
import { createViteLogger } from './utils/logger' import { createViteLogger } from './utils/logger'
export async function buildClient (ctx: ViteBuildContext) { export async function buildClient (ctx: ViteBuildContext) {
const nodeCompat = ctx.nuxt.options.experimental.clientNodeCompat ? { const nodeCompat = ctx.nuxt.options.experimental.clientNodeCompat
? {
alias: env(nodeless).alias, alias: env(nodeless).alias,
define: { define: {
global: 'globalThis', global: 'globalThis'
} }
} : { alias: {}, define: {} } }
: { alias: {}, define: {} }
const clientConfig: ViteConfig = vite.mergeConfig(ctx.config, vite.mergeConfig({ const clientConfig: ViteConfig = vite.mergeConfig(ctx.config, vite.mergeConfig({
configFile: false, configFile: false,
@ -67,7 +69,7 @@ export async function buildClient (ctx: ViteBuildContext) {
...nodeCompat.alias, ...nodeCompat.alias,
...ctx.config.resolve?.alias, ...ctx.config.resolve?.alias,
'#build/plugins': resolve(ctx.nuxt.options.buildDir, 'plugins/client'), '#build/plugins': resolve(ctx.nuxt.options.buildDir, 'plugins/client'),
'#internal/nitro': resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs'), '#internal/nitro': resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs')
}, },
dedupe: [ dedupe: [
'vue' 'vue'

View File

@ -44,11 +44,11 @@ export async function buildServer (ctx: ViteBuildContext) {
'import.meta.server': true, 'import.meta.server': true,
'import.meta.client': false, 'import.meta.client': false,
'import.meta.browser': false, 'import.meta.browser': false,
'window': 'undefined', window: 'undefined',
'document': 'undefined', document: 'undefined',
'navigator': 'undefined', navigator: 'undefined',
'location': 'undefined', location: 'undefined',
'XMLHttpRequest': 'undefined' XMLHttpRequest: 'undefined'
}, },
optimizeDeps: { optimizeDeps: {
entries: ctx.nuxt.options.ssr ? [ctx.entry] : [] entries: ctx.nuxt.options.ssr ? [ctx.entry] : []
@ -99,7 +99,7 @@ export async function buildServer (ctx: ViteBuildContext) {
// https://github.com/vitest-dev/vitest/issues/229#issuecomment-1002685027 // https://github.com/vitest-dev/vitest/issues/229#issuecomment-1002685027
preTransformRequests: false, preTransformRequests: false,
hmr: false hmr: false
}, }
} satisfies vite.InlineConfig, ctx.nuxt.options.vite.$server || {})) } satisfies vite.InlineConfig, ctx.nuxt.options.vite.$server || {}))
if (!ctx.nuxt.options.dev) { if (!ctx.nuxt.options.dev) {

View File

@ -158,7 +158,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
// Trigger vite to optimize dependencies imported within a layer, just as if they were imported in final project // Trigger vite to optimize dependencies imported within a layer, just as if they were imported in final project
await this.resolve(source, join(nuxt.options.srcDir, 'index.html'), { skipSelf: true }).catch(() => null) await this.resolve(source, join(nuxt.options.srcDir, 'index.html'), { skipSelf: true }).catch(() => null)
} }
}, }
}) })
} }
} }

View File

@ -22,7 +22,7 @@ export function client (ctx: WebpackConfigContext) {
clientDevtool, clientDevtool,
clientPerformance, clientPerformance,
clientHMR, clientHMR,
clientNodeCompat, clientNodeCompat
]) ])
} }
@ -50,21 +50,21 @@ function clientPerformance (ctx: WebpackConfigContext) {
} }
} }
function clientNodeCompat(ctx: WebpackConfigContext) { function clientNodeCompat (ctx: WebpackConfigContext) {
if (!ctx.nuxt.options.experimental.clientNodeCompat) { if (!ctx.nuxt.options.experimental.clientNodeCompat) {
return return
} }
ctx.config.plugins!.push(new webpack.DefinePlugin({ global: 'globalThis', })) ctx.config.plugins!.push(new webpack.DefinePlugin({ global: 'globalThis' }))
ctx.config.resolve = ctx.config.resolve || {} ctx.config.resolve = ctx.config.resolve || {}
ctx.config.resolve.fallback = { ctx.config.resolve.fallback = {
...env(nodeless).alias, ...env(nodeless).alias,
...ctx.config.resolve.fallback, ...ctx.config.resolve.fallback
} }
// https://github.com/webpack/webpack/issues/13290#issuecomment-1188760779 // https://github.com/webpack/webpack/issues/13290#issuecomment-1188760779
ctx.config.plugins!.unshift(new webpack.NormalModuleReplacementPlugin(/node:/, (resource) => { ctx.config.plugins!.unshift(new webpack.NormalModuleReplacementPlugin(/node:/, (resource) => {
resource.request = resource.request.replace(/^node:/, ''); resource.request = resource.request.replace(/^node:/, '')
})) }))
} }

View File

@ -67,6 +67,9 @@ importers:
eslint: eslint:
specifier: 8.57.0 specifier: 8.57.0
version: 8.57.0 version: 8.57.0
eslint-config-standard:
specifier: ^17.1.0
version: 17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@16.6.2)(eslint-plugin-promise@6.1.1)(eslint@8.57.0)
eslint-plugin-import: eslint-plugin-import:
specifier: 2.29.1 specifier: 2.29.1
version: 2.29.1(@typescript-eslint/parser@6.8.0)(eslint@8.57.0) version: 2.29.1(@typescript-eslint/parser@6.8.0)(eslint@8.57.0)
@ -4201,7 +4204,6 @@ packages:
resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==}
dependencies: dependencies:
semver: 7.6.0 semver: 7.6.0
dev: false
/bundle-name@3.0.0: /bundle-name@3.0.0:
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
@ -5427,6 +5429,30 @@ packages:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'} engines: {node: '>=12'}
/eslint-compat-utils@0.1.2(eslint@8.57.0):
resolution: {integrity: sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==}
engines: {node: '>=12'}
peerDependencies:
eslint: '>=6.0.0'
dependencies:
eslint: 8.57.0
dev: true
/eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@16.6.2)(eslint-plugin-promise@6.1.1)(eslint@8.57.0):
resolution: {integrity: sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==}
engines: {node: '>=12.0.0'}
peerDependencies:
eslint: ^8.0.1
eslint-plugin-import: ^2.25.2
eslint-plugin-n: '^15.0.0 || ^16.0.0 '
eslint-plugin-promise: ^6.0.0
dependencies:
eslint: 8.57.0
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.8.0)(eslint@8.57.0)
eslint-plugin-n: 16.6.2(eslint@8.57.0)
eslint-plugin-promise: 6.1.1(eslint@8.57.0)
dev: true
/eslint-import-resolver-node@0.3.9: /eslint-import-resolver-node@0.3.9:
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
dependencies: dependencies:
@ -5466,6 +5492,18 @@ packages:
- supports-color - supports-color
dev: true dev: true
/eslint-plugin-es-x@7.5.0(eslint@8.57.0):
resolution: {integrity: sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
eslint: '>=8'
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@eslint-community/regexpp': 4.9.1
eslint: 8.57.0
eslint-compat-utils: 0.1.2(eslint@8.57.0)
dev: true
/eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.8.0)(eslint@8.57.0): /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.8.0)(eslint@8.57.0):
resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -5521,11 +5559,40 @@ packages:
- supports-color - supports-color
dev: true dev: true
/eslint-plugin-n@16.6.2(eslint@8.57.0):
resolution: {integrity: sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==}
engines: {node: '>=16.0.0'}
peerDependencies:
eslint: '>=7.0.0'
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
builtins: 5.0.1
eslint: 8.57.0
eslint-plugin-es-x: 7.5.0(eslint@8.57.0)
get-tsconfig: 4.7.2
globals: 13.24.0
ignore: 5.3.1
is-builtin-module: 3.2.1
is-core-module: 2.13.1
minimatch: 3.1.2
resolve: 1.22.8
semver: 7.6.0
dev: true
/eslint-plugin-no-only-tests@3.1.0: /eslint-plugin-no-only-tests@3.1.0:
resolution: {integrity: sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==} resolution: {integrity: sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==}
engines: {node: '>=5.0.0'} engines: {node: '>=5.0.0'}
dev: true dev: true
/eslint-plugin-promise@6.1.1(eslint@8.57.0):
resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
dependencies:
eslint: 8.57.0
dev: true
/eslint-plugin-unicorn@51.0.1(eslint@8.57.0): /eslint-plugin-unicorn@51.0.1(eslint@8.57.0):
resolution: {integrity: sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==} resolution: {integrity: sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -6168,6 +6235,13 @@ packages:
dependencies: dependencies:
type-fest: 0.20.2 type-fest: 0.20.2
/globals@13.24.0:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
engines: {node: '>=8'}
dependencies:
type-fest: 0.20.2
dev: true
/globalthis@1.0.3: /globalthis@1.0.3:
resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}

View File

@ -45,7 +45,7 @@ async function main () {
.replace(/^## v.*?\n/, '') .replace(/^## v.*?\n/, '')
.replace(`...${releaseBranch}`, `...v${newVersion}`) .replace(`...${releaseBranch}`, `...v${newVersion}`)
.replace(/### ❤️ Contributors[\s\S]*$/, ''), .replace(/### ❤️ Contributors[\s\S]*$/, ''),
`### ❤️ Contributors`, '### ❤️ Contributors',
contributors.map(c => `- ${c.name} (@${c.username})`).join('\n') contributors.map(c => `- ${c.name} (@${c.username})`).join('\n')
].join('\n') ].join('\n')

View File

@ -363,7 +363,7 @@ describe('pages', () => {
// don't expect any errors or warning on client-side navigation // don't expect any errors or warning on client-side navigation
const { page: page2, consoleLogs: consoleLogs2 } = await renderPage('/') const { page: page2, consoleLogs: consoleLogs2 } = await renderPage('/')
await page2.locator('#to-client-only-components').click() await page2.locator('#to-client-only-components').click()
await page2.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, `/client-only-components`) await page2.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, '/client-only-components')
expect(consoleLogs2.some(log => log.type === 'error' || log.type === 'warning')).toBeFalsy() expect(consoleLogs2.some(log => log.type === 'error' || log.type === 'warning')).toBeFalsy()
await page2.close() await page2.close()
}) })
@ -485,14 +485,14 @@ describe('pages', () => {
}" }"
`) `)
expect(await clientInitialPage.locator('#server-rendered').textContent()).toMatchInlineSnapshot(`"false"`) expect(await clientInitialPage.locator('#server-rendered').textContent()).toMatchInlineSnapshot('"false"')
// Then go to non client only page // Then go to non client only page
await clientInitialPage.click('a') await clientInitialPage.click('a')
await new Promise((r) => setTimeout(r, 50)) // little delay to finish transition await new Promise(resolve => setTimeout(resolve, 50)) // little delay to finish transition
// that page should be client rendered // that page should be client rendered
expect(await clientInitialPage.locator('#server-rendered').textContent()).toMatchInlineSnapshot(`"false"`) expect(await clientInitialPage.locator('#server-rendered').textContent()).toMatchInlineSnapshot('"false"')
// and not contain any errors or warnings // and not contain any errors or warnings
expect(errors.length).toBe(0) expect(errors.length).toBe(0)
@ -509,7 +509,7 @@ describe('pages', () => {
}) })
// Now non client only page should be sever rendered // Now non client only page should be sever rendered
expect(await normalInitialPage.locator('#server-rendered').textContent()).toMatchInlineSnapshot(`"true"`) expect(await normalInitialPage.locator('#server-rendered').textContent()).toMatchInlineSnapshot('"true"')
// Go to client only page // Go to client only page
await normalInitialPage.click('a') await normalInitialPage.click('a')
@ -563,7 +563,7 @@ describe('nuxt composables', () => {
expect(text).toContain('baz') expect(text).toContain('baz')
await page.getByText('Change cookie').click() await page.getByText('Change cookie').click()
expect(await extractCookie()).toEqual({ foo: 'bar' }) expect(await extractCookie()).toEqual({ foo: 'bar' })
await page.evaluate(() => document.cookie = `browser-object-default=${encodeURIComponent('{"foo":"foobar"}')}`) await page.evaluate(() => { document.cookie = `browser-object-default=${encodeURIComponent('{"foo":"foobar"}')}` })
await page.getByText('Refresh cookie').click() await page.getByText('Refresh cookie').click()
text = await page.innerText('pre') text = await page.innerText('pre')
expect(text).toContain('foobar') expect(text).toContain('foobar')
@ -639,7 +639,7 @@ describe('rich payloads', () => {
'BigInt ref:', 'BigInt ref:',
'Reactive: true', 'Reactive: true',
'Ref: true', 'Ref: true',
'Recursive objects: true', 'Recursive objects: true'
]) { ]) {
expect(html).toContain(test) expect(html).toContain(test)
} }
@ -731,13 +731,13 @@ describe('nuxt links', () => {
await page.locator('#big-page-2').scrollIntoViewIfNeeded() await page.locator('#big-page-2').scrollIntoViewIfNeeded()
expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(0) expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(0)
await page.locator('#big-page-2').click() await page.locator('#big-page-2').click()
await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, `/big-page-2`) await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, '/big-page-2')
expect(await page.evaluate(() => window.scrollY)).toBe(0) expect(await page.evaluate(() => window.scrollY)).toBe(0)
await page.locator('#big-page-1').scrollIntoViewIfNeeded() await page.locator('#big-page-1').scrollIntoViewIfNeeded()
expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(0) expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(0)
await page.locator('#big-page-1').click() await page.locator('#big-page-1').click()
await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, `/big-page-1`) await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, '/big-page-1')
expect(await page.evaluate(() => window.scrollY)).toBe(0) expect(await page.evaluate(() => window.scrollY)).toBe(0)
await page.close() await page.close()
}, },
@ -754,18 +754,18 @@ describe('nuxt links', () => {
height: 1000 height: 1000
} }
}) })
await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, `/nested/foo/test`) await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, '/nested/foo/test')
await page.locator('#user-test').scrollIntoViewIfNeeded() await page.locator('#user-test').scrollIntoViewIfNeeded()
expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(0) expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(0)
await page.locator('#user-test').click() await page.locator('#user-test').click()
await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, `/nested/foo/user-test`) await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, '/nested/foo/user-test')
expect(await page.evaluate(() => window.scrollY)).toBe(0) expect(await page.evaluate(() => window.scrollY)).toBe(0)
await page.locator('#test').scrollIntoViewIfNeeded() await page.locator('#test').scrollIntoViewIfNeeded()
expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(0) expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(0)
await page.locator('#test').click() await page.locator('#test').click()
await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, `/nested/foo/test`) await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, '/nested/foo/test')
expect(await page.evaluate(() => window.scrollY)).toBe(0) expect(await page.evaluate(() => window.scrollY)).toBe(0)
await page.close() await page.close()
}, },
@ -879,7 +879,7 @@ describe('navigate', () => {
const res = await fetch('/navigate-some-path/', { redirect: 'manual', headers: { 'trailing-slash': 'true' } }) const res = await fetch('/navigate-some-path/', { redirect: 'manual', headers: { 'trailing-slash': 'true' } })
expect(res.headers.get('location')).toEqual('/navigate-some-path') expect(res.headers.get('location')).toEqual('/navigate-some-path')
expect(res.status).toEqual(307) expect(res.status).toEqual(307)
expect(await res.text()).toMatchInlineSnapshot(`"<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=/navigate-some-path"></head></html>"`) expect(await res.text()).toMatchInlineSnapshot('"<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=/navigate-some-path"></head></html>"')
}) })
it('should not overwrite headers', async () => { it('should not overwrite headers', async () => {
@ -1729,9 +1729,9 @@ describe('server components/islands', () => {
const text = (await page.innerText('pre')).replaceAll(/ data-island-uid="([^"]*)"/g, '').replace(/data-island-component="([^"]*)"/g, (_, content) => `data-island-component="${content.split('-')[0]}"`) const text = (await page.innerText('pre')).replaceAll(/ data-island-uid="([^"]*)"/g, '').replace(/data-island-component="([^"]*)"/g, (_, content) => `data-island-component="${content.split('-')[0]}"`)
if (isWebpack) { if (isWebpack) {
expect(text).toMatchInlineSnapshot(`" End page <pre></pre><section id="fallback"><div> This is a .server (20ms) async component that was very long ... <div id="async-server-component-count">42</div><div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><!--[--><div style="display: contents;" data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--></div></section><section id="no-fallback"><div> This is a .server (20ms) async component that was very long ... <div id="async-server-component-count">42</div><div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><!--[--><div style="display: contents;" data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--></div></section><div> ServerWithClient.server.vue : <p>count: 0</p> This component should not be preloaded <div><!--[--><div>a</div><div>b</div><div>c</div><!--]--></div> This is not interactive <div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><div class="interactive-component-wrapper" style="border:solid 1px red;"> The component bellow is not a slot but declared as interactive <div class="sugar-counter" nuxt-client=""> Sugar Counter 12 x 1 = 12 <button> Inc </button></div></div></div>"`) expect(text).toMatchInlineSnapshot('" End page <pre></pre><section id="fallback"><div> This is a .server (20ms) async component that was very long ... <div id="async-server-component-count">42</div><div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><!--[--><div style="display: contents;" data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--></div></section><section id="no-fallback"><div> This is a .server (20ms) async component that was very long ... <div id="async-server-component-count">42</div><div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><!--[--><div style="display: contents;" data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--></div></section><div> ServerWithClient.server.vue : <p>count: 0</p> This component should not be preloaded <div><!--[--><div>a</div><div>b</div><div>c</div><!--]--></div> This is not interactive <div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><div class="interactive-component-wrapper" style="border:solid 1px red;"> The component bellow is not a slot but declared as interactive <div class="sugar-counter" nuxt-client=""> Sugar Counter 12 x 1 = 12 <button> Inc </button></div></div></div>"')
} else { } else {
expect(text).toMatchInlineSnapshot(`" End page <pre></pre><section id="fallback"><div> This is a .server (20ms) async component that was very long ... <div id="async-server-component-count">42</div><div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><!--[--><div style="display: contents;" data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--></div></section><section id="no-fallback"><div> This is a .server (20ms) async component that was very long ... <div id="async-server-component-count">42</div><div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><!--[--><div style="display: contents;" data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--></div></section><div> ServerWithClient.server.vue : <p>count: 0</p> This component should not be preloaded <div><!--[--><div>a</div><div>b</div><div>c</div><!--]--></div> This is not interactive <div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><div class="interactive-component-wrapper" style="border:solid 1px red;"> The component bellow is not a slot but declared as interactive <!--[--><div style="display: contents;" data-island-component="Counter"></div><!--teleport start--><!--teleport end--><!--]--></div></div>"`) expect(text).toMatchInlineSnapshot('" End page <pre></pre><section id="fallback"><div> This is a .server (20ms) async component that was very long ... <div id="async-server-component-count">42</div><div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><!--[--><div style="display: contents;" data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--></div></section><section id="no-fallback"><div> This is a .server (20ms) async component that was very long ... <div id="async-server-component-count">42</div><div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><!--[--><div style="display: contents;" data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--></div></section><div> ServerWithClient.server.vue : <p>count: 0</p> This component should not be preloaded <div><!--[--><div>a</div><div>b</div><div>c</div><!--]--></div> This is not interactive <div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><div class="interactive-component-wrapper" style="border:solid 1px red;"> The component bellow is not a slot but declared as interactive <!--[--><div style="display: contents;" data-island-component="Counter"></div><!--teleport start--><!--teleport end--><!--]--></div></div>"')
} }
expect(text).toContain('async component that was very long') expect(text).toContain('async component that was very long')
@ -1847,7 +1847,7 @@ describe.skipIf(isDev())('dynamic paths', () => {
await startServer({ await startServer({
env: { env: {
NUXT_APP_BUILD_ASSETS_DIR: '/_other/', NUXT_APP_BUILD_ASSETS_DIR: '/_other/',
NUXT_APP_BASE_URL: '/foo/', NUXT_APP_BASE_URL: '/foo/'
} }
}) })
@ -1889,7 +1889,7 @@ describe.skipIf(isDev())('dynamic paths', () => {
await startServer({ await startServer({
env: { env: {
NUXT_APP_BUILD_ASSETS_DIR: '/_other/', NUXT_APP_BUILD_ASSETS_DIR: '/_other/',
NUXT_APP_BASE_URL: '/foo/', NUXT_APP_BASE_URL: '/foo/'
} }
}) })
const { headers } = await fetch('/foo/navigate-to/', { redirect: 'manual' }) const { headers } = await fetch('/foo/navigate-to/', { redirect: 'manual' })
@ -2444,7 +2444,7 @@ describe('keepalive', () => {
'keepalive-in-nuxtpage-2', 'keepalive-in-nuxtpage-2',
'keepalive-in-nuxtpage', 'keepalive-in-nuxtpage',
'not-keepalive', 'not-keepalive',
'keepalive-in-nuxtpage-2', 'keepalive-in-nuxtpage-2'
] ]
for (const slug of slugs) { for (const slug of slugs) {

View File

@ -19,7 +19,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
for (const outputDir of ['.output', '.output-inline']) { for (const outputDir of ['.output', '.output-inline']) {
it('default client bundle size', async () => { it('default client bundle size', async () => {
const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public')) const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public'))
expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot(`"105k"`) expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot('"105k"')
expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(` expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
[ [
"_nuxt/entry.js", "_nuxt/entry.js",
@ -32,10 +32,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output/server') const serverDir = join(rootDir, '.output/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"205k"`) expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"205k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir) const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1335k"`) expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"1335k"')
const packages = modules.files const packages = modules.files
.filter(m => m.endsWith('package.json')) .filter(m => m.endsWith('package.json'))
@ -72,10 +72,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output-inline/server') const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"524k"`) expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"524k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir) const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"78.0k"`) expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"78.0k"')
const packages = modules.files const packages = modules.files
.filter(m => m.endsWith('package.json')) .filter(m => m.endsWith('package.json'))

View File

@ -3,7 +3,7 @@ import { addTypeTemplate } from 'nuxt/kit'
export default defineNuxtConfig({ export default defineNuxtConfig({
experimental: { experimental: {
typedPages: true, typedPages: true,
appManifest: true, appManifest: true
}, },
future: { future: {
typescriptBundlerResolution: process.env.MODULE_RESOLUTION === 'bundler' typescriptBundlerResolution: process.env.MODULE_RESOLUTION === 'bundler'

View File

@ -331,7 +331,7 @@ describe('head', () => {
}) })
it('types head for defineNuxtComponent', () => { it('types head for defineNuxtComponent', () => {
defineNuxtComponent({ defineNuxtComponent({
head(nuxtApp) { head (nuxtApp) {
expectTypeOf(nuxtApp).not.toBeAny() expectTypeOf(nuxtApp).not.toBeAny()
return { return {
title: 'Site Title' title: 'Site Title'
@ -341,9 +341,9 @@ describe('head', () => {
defineNuxtComponent({ defineNuxtComponent({
// @ts-expect-error wrong return type for head function // @ts-expect-error wrong return type for head function
head() { head () {
return { return {
'test': true test: true
} }
} }
}) })

View File

@ -10,40 +10,40 @@ export default defineNuxtModule({
addComponent({ addComponent({
name: 'DCompClient', name: 'DCompClient',
filePath: resolve('./runtime/components'), filePath: resolve('./runtime/components'),
mode: 'client', mode: 'client'
}) })
addComponent({ addComponent({
name: 'DCompServer', name: 'DCompServer',
filePath: resolve('./runtime/components'), filePath: resolve('./runtime/components'),
mode: 'server', mode: 'server'
}) })
addComponent({ addComponent({
name: 'DCompAll', name: 'DCompAll',
filePath: resolve('./runtime/components'), filePath: resolve('./runtime/components'),
mode: 'all', mode: 'all'
}) })
addComponent({ addComponent({
name: 'NCompClient', name: 'NCompClient',
export: 'NComp', export: 'NComp',
filePath: resolve('./runtime/components'), filePath: resolve('./runtime/components'),
mode: 'client', mode: 'client'
}) })
addComponent({ addComponent({
name: 'NCompServer', name: 'NCompServer',
export: 'NComp', export: 'NComp',
filePath: resolve('./runtime/components'), filePath: resolve('./runtime/components'),
mode: 'server', mode: 'server'
}) })
addComponent({ addComponent({
name: 'NCompAll', name: 'NCompAll',
export: 'NComp', export: 'NComp',
filePath: resolve('./runtime/components'), filePath: resolve('./runtime/components'),
mode: 'all', mode: 'all'
}) })
}, }
}) })

View File

@ -3,11 +3,11 @@ import { defineComponent, h } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'DComp', name: 'DComp',
props: { message: String }, props: { message: String },
render: (props: any) => h('h1', props.message), render: (props: any) => h('h1', props.message)
}) })
export const NComp = defineComponent({ export const NComp = defineComponent({
name: 'NComp', name: 'NComp',
props: { message: String }, props: { message: String },
render: (props: any) => h('h1', props.message), render: (props: any) => h('h1', props.message)
}) })

View File

@ -11,21 +11,21 @@ export default defineNuxtModule({
name: 'NCompClient', name: 'NCompClient',
export: 'NComp', export: 'NComp',
filePath: resolve('./runtime/components'), filePath: resolve('./runtime/components'),
mode: 'client', mode: 'client'
}) })
addComponent({ addComponent({
name: 'NCompServer', name: 'NCompServer',
export: 'NComp', export: 'NComp',
filePath: resolve('./runtime/components'), filePath: resolve('./runtime/components'),
mode: 'server', mode: 'server'
}) })
addComponent({ addComponent({
name: 'NCompAll', name: 'NCompAll',
export: 'NComp', export: 'NComp',
filePath: resolve('./runtime/components'), filePath: resolve('./runtime/components'),
mode: 'all', mode: 'all'
}) })
}, }
}) })

View File

@ -3,5 +3,5 @@ import { defineComponent, h } from 'vue'
export const NComp = defineComponent({ export const NComp = defineComponent({
name: 'NComp', name: 'NComp',
props: { message: String }, props: { message: String },
render: (props: any) => h('h1', props.message), render: (props: any) => h('h1', props.message)
}) })

View File

@ -1,5 +1,5 @@
import { defineNuxtModule } from 'nuxt/kit' import { defineNuxtModule } from 'nuxt/kit'
export default defineNuxtModule({ export default defineNuxtModule({
meta: { name: 'subpath' }, meta: { name: 'subpath' }
}) })

View File

@ -101,10 +101,10 @@ export default defineNuxtConfig({
addBuildPlugin(plugin) addBuildPlugin(plugin)
}, },
function (_options, nuxt) { function (_options, nuxt) {
nuxt.hook('pages:extend', pages => { nuxt.hook('pages:extend', (pages) => {
pages.push({ pages.push({
path: '/manual-redirect', path: '/manual-redirect',
redirect: '/', redirect: '/'
}) })
}) })
}, },
@ -201,7 +201,7 @@ export default defineNuxtConfig({
} }
}, },
features: { features: {
inlineStyles: id => !!id && !id.includes('assets.vue'), inlineStyles: id => !!id && !id.includes('assets.vue')
}, },
experimental: { experimental: {
typedPages: true, typedPages: true,

View File

@ -3,7 +3,7 @@ const state = useState('test', () => {
let hasAccessToWindow = null as null | boolean let hasAccessToWindow = null as null | boolean
try { try {
hasAccessToWindow = Object.keys(window).at(0) ? true : false hasAccessToWindow = !!Object.keys(window).at(0)
} catch { } catch {
hasAccessToWindow = null hasAccessToWindow = null
} }

View File

@ -5,4 +5,3 @@ import css from '~/assets/inline-only.css?inline'
<template> <template>
<pre>{{ css }}</pre> <pre>{{ css }}</pre>
</template> </template>

View File

@ -3,7 +3,7 @@ import { Buffer } from 'node:buffer'
import process from 'node:process' import process from 'node:process'
const base64 = atob(Buffer.from('Nuxt is Awesome!', 'utf8').toString('base64')) const base64 = atob(Buffer.from('Nuxt is Awesome!', 'utf8').toString('base64'))
const cwd = typeof process.cwd == 'function' && "[available]" const cwd = typeof process.cwd === 'function' && '[available]'
</script> </script>
<template> <template>

View File

@ -12,4 +12,3 @@ const id = useId()
<ComponentWithIds /> <ComponentWithIds />
</form> </form>
</template> </template>

View File

@ -1,3 +1,3 @@
export default defineEventHandler(async () => { export default defineEventHandler(() => {
throw createError({ statusCode: 400 }) throw createError({ statusCode: 400 })
}) })

View File

@ -120,21 +120,21 @@ if (process.env.TEST_ENV !== 'built' && !isWindows) {
// initial state // initial state
await expectWithPolling( await expectWithPolling(
resolveHmrId, resolveHmrId,
0, 0
) )
// first edit // first edit
await triggerHmr() await triggerHmr()
await expectWithPolling( await expectWithPolling(
resolveHmrId, resolveHmrId,
1, 1
) )
// just in-case // just in-case
await triggerHmr() await triggerHmr()
await expectWithPolling( await expectWithPolling(
resolveHmrId, resolveHmrId,
2, 2
) )
// ensure no errors // ensure no errors

View File

@ -42,7 +42,7 @@ registerEndpoint('/_nuxt/builds/meta/override.json', defineEventHandler(() => ({
}, },
prerendered: ['/specific-prerendered'] prerendered: ['/specific-prerendered']
}))) })))
registerEndpoint('/api/test', defineEventHandler((event) => ({ registerEndpoint('/api/test', defineEventHandler(event => ({
method: event.method, method: event.method,
headers: Object.fromEntries(event.headers.entries()) headers: Object.fromEntries(event.headers.entries())
}))) })))
@ -106,7 +106,7 @@ describe('composables', () => {
'navigateTo', 'navigateTo',
'abortNavigation', 'abortNavigation',
'setPageLayout', 'setPageLayout',
'defineNuxtComponent', 'defineNuxtComponent'
] ]
const skippedComposables: string[] = [ const skippedComposables: string[] = [
'addRouteMiddleware', 'addRouteMiddleware',
@ -248,7 +248,7 @@ describe('useAsyncData', () => {
expect(data.value).toMatchInlineSnapshot('"default"') expect(data.value).toMatchInlineSnapshot('"default"')
}) })
it('should execute the promise function once when dedupe option is "defer" for multiple calls', async () => { it('should execute the promise function once when dedupe option is "defer" for multiple calls', () => {
const promiseFn = vi.fn(() => Promise.resolve('test')) const promiseFn = vi.fn(() => Promise.resolve('test'))
useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' }) useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' })
useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' }) useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' })
@ -257,7 +257,7 @@ describe('useAsyncData', () => {
expect(promiseFn).toHaveBeenCalledTimes(1) expect(promiseFn).toHaveBeenCalledTimes(1)
}) })
it('should execute the promise function multiple times when dedupe option is not specified for multiple calls', async () => { it('should execute the promise function multiple times when dedupe option is not specified for multiple calls', () => {
const promiseFn = vi.fn(() => Promise.resolve('test')) const promiseFn = vi.fn(() => Promise.resolve('test'))
useAsyncData('dedupedKey', promiseFn) useAsyncData('dedupedKey', promiseFn)
useAsyncData('dedupedKey', promiseFn) useAsyncData('dedupedKey', promiseFn)
@ -266,7 +266,7 @@ describe('useAsyncData', () => {
expect(promiseFn).toHaveBeenCalledTimes(3) expect(promiseFn).toHaveBeenCalledTimes(3)
}) })
it('should execute the promise function as per dedupe option when different dedupe options are used for multiple calls', async () => { it('should execute the promise function as per dedupe option when different dedupe options are used for multiple calls', () => {
const promiseFn = vi.fn(() => Promise.resolve('test')) const promiseFn = vi.fn(() => Promise.resolve('test'))
useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' }) useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' })
useAsyncData('dedupedKey', promiseFn) useAsyncData('dedupedKey', promiseFn)
@ -556,13 +556,13 @@ describe.skipIf(process.env.TEST_MANIFEST === 'manifest-off')('app manifests', (
describe('routing utilities: `navigateTo`', () => { describe('routing utilities: `navigateTo`', () => {
it('navigateTo should disallow navigation to external URLs by default', () => { it('navigateTo should disallow navigation to external URLs by default', () => {
expect(() => navigateTo('https://test.com')).toThrowErrorMatchingInlineSnapshot(`[Error: Navigating to an external URL is not allowed by default. Use \`navigateTo(url, { external: true })\`.]`) expect(() => navigateTo('https://test.com')).toThrowErrorMatchingInlineSnapshot('[Error: Navigating to an external URL is not allowed by default. Use `navigateTo(url, { external: true })`.]')
expect(() => navigateTo('https://test.com', { external: true })).not.toThrow() expect(() => navigateTo('https://test.com', { external: true })).not.toThrow()
}) })
it('navigateTo should disallow navigation to data/script URLs', () => { it('navigateTo should disallow navigation to data/script URLs', () => {
const urls = [ const urls = [
['data:alert("hi")', 'data'], ['data:alert("hi")', 'data'],
['\0data:alert("hi")', 'data'], ['\0data:alert("hi")', 'data']
] ]
for (const [url, protocol] of urls) { for (const [url, protocol] of urls) {
expect(() => navigateTo(url, { external: true })).toThrowError(`Cannot navigate to a URL with '${protocol}:' protocol.`) expect(() => navigateTo(url, { external: true })).toThrowError(`Cannot navigate to a URL with '${protocol}:' protocol.`)
@ -571,7 +571,7 @@ describe('routing utilities: `navigateTo`', () => {
}) })
describe('routing utilities: `useRoute`', () => { describe('routing utilities: `useRoute`', () => {
it('should show provide a mock route', async () => { it('should show provide a mock route', () => {
expect(useRoute()).toMatchObject({ expect(useRoute()).toMatchObject({
fullPath: '/', fullPath: '/',
hash: '', hash: '',
@ -582,7 +582,7 @@ describe('routing utilities: `useRoute`', () => {
params: {}, params: {},
path: '/', path: '/',
query: {}, query: {},
redirectedFrom: undefined, redirectedFrom: undefined
}) })
}) })
}) })
@ -590,7 +590,7 @@ describe('routing utilities: `useRoute`', () => {
describe('routing utilities: `abortNavigation`', () => { describe('routing utilities: `abortNavigation`', () => {
it('should throw an error if one is provided', () => { it('should throw an error if one is provided', () => {
const error = useError() const error = useError()
expect(() => abortNavigation({ message: 'Page not found' })).toThrowErrorMatchingInlineSnapshot(`[Error: Page not found]`) expect(() => abortNavigation({ message: 'Page not found' })).toThrowErrorMatchingInlineSnapshot('[Error: Page not found]')
expect(error.value).toBeFalsy() expect(error.value).toBeFalsy()
}) })
it('should block navigation if no error is provided', () => { it('should block navigation if no error is provided', () => {
@ -607,7 +607,7 @@ describe('routing utilities: `setPageLayout`', () => {
route.meta.layout = undefined route.meta.layout = undefined
}) })
it('should not set layout directly if run within middleware', async () => { it('should not set layout directly if run within middleware', () => {
const route = useRoute() const route = useRoute()
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
nuxtApp._processingMiddleware = true nuxtApp._processingMiddleware = true
@ -632,7 +632,7 @@ describe('useCookie', () => {
it('should watch custom cookie refs', () => { it('should watch custom cookie refs', () => {
const user = useCookie('userInfo', { const user = useCookie('userInfo', {
default: () => ({ score: -1 }), default: () => ({ score: -1 }),
maxAge: 60 * 60, maxAge: 60 * 60
}) })
const computedVal = computed(() => user.value.score) const computedVal = computed(() => user.value.score)
expect(computedVal.value).toBe(-1) expect(computedVal.value).toBe(-1)

View File

@ -135,7 +135,6 @@ describe('runtime server component', () => {
}) })
}) })
describe('client components', () => { describe('client components', () => {
it('expect swapping nuxt-client should not trigger errors #25289', async () => { it('expect swapping nuxt-client should not trigger errors #25289', async () => {
const mockPath = '/nuxt-client.js' const mockPath = '/nuxt-client.js'
@ -198,7 +197,7 @@ describe('client components', () => {
// @ts-expect-error mock // @ts-expect-error mock
vi.mocked(fetch).mockImplementation(() => ({ vi.mocked(fetch).mockImplementation(() => ({
id: '123', id: '123',
html: `<div data-island-uid>hello<div><div>fallback</div></div></div>`, html: '<div data-island-uid>hello<div><div>fallback</div></div></div>',
state: {}, state: {},
head: { head: {
link: [], link: [],
@ -267,7 +266,7 @@ describe('client components', () => {
default: { default: {
name: 'ClientWithSlot', name: 'ClientWithSlot',
setup (_, { slots }) { setup (_, { slots }) {
return () => h('div', { class: "client-component" }, slots.default()) return () => h('div', { class: 'client-component' }, slots.default())
} }
} }
})) }))
@ -300,7 +299,7 @@ describe('client components', () => {
vi.stubGlobal('fetch', stubFetch) vi.stubGlobal('fetch', stubFetch)
const wrapper = await mountSuspended(NuxtIsland, { const wrapper = await mountSuspended(NuxtIsland, {
props: { props: {
name: 'NuxtClientWithSlot', name: 'NuxtClientWithSlot'
}, },
attachTo: 'body' attachTo: 'body'
}) })

View File

@ -206,7 +206,7 @@ describe('plugin dependsOn', () => {
pluginFactory('A', ['B'], sequence), pluginFactory('A', ['B'], sequence),
pluginFactory('B', ['C'], sequence), pluginFactory('B', ['C'], sequence),
pluginFactory('C', ['D'], sequence), pluginFactory('C', ['D'], sequence),
pluginFactory('D', [], sequence), pluginFactory('D', [], sequence)
] ]
await applyPlugins(nuxtApp, plugins) await applyPlugins(nuxtApp, plugins)
@ -251,7 +251,7 @@ describe('plugin dependsOn', () => {
const plugins = [ const plugins = [
pluginFactory('A', undefined, sequence, false), pluginFactory('A', undefined, sequence, false),
pluginFactory('B', ['A', 'C'], sequence, false), pluginFactory('B', ['A', 'C'], sequence, false),
pluginFactory('C', undefined, sequence, false), pluginFactory('C', undefined, sequence, false)
] ]
await applyPlugins(nuxtApp, plugins) await applyPlugins(nuxtApp, plugins)
@ -261,7 +261,7 @@ describe('plugin dependsOn', () => {
'start C', 'start C',
'end C', 'end C',
'start B', 'start B',
'end B', 'end B'
]) ])
}) })
@ -269,8 +269,8 @@ describe('plugin dependsOn', () => {
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
const sequence: string[] = [] const sequence: string[] = []
const plugins = [ const plugins = [
pluginFactory('B', undefined, sequence,), pluginFactory('B', undefined, sequence),
pluginFactory('C', ['A', 'B'], sequence,), pluginFactory('C', ['A', 'B'], sequence)
] ]
await applyPlugins(nuxtApp, plugins) await applyPlugins(nuxtApp, plugins)
@ -278,7 +278,7 @@ describe('plugin dependsOn', () => {
'start B', 'start B',
'end B', 'end B',
'start C', 'start C',
'end C', 'end C'
]) ])
}) })
}) })