refactor(nuxt): enable strict type checking (#6368)

Co-authored-by: Pooya Parsa <pooya@pi0.io>
This commit is contained in:
Anthony Fu 2022-08-13 01:47:58 +08:00 committed by GitHub
parent cb98c8b921
commit f350a70775
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 235 additions and 201 deletions

View File

@ -7,7 +7,7 @@ import { NuxtConfigSchema } from '@nuxt/schema'
export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
const { config: nuxtConfig, configFile, layers, cwd } = await loadConfig({
const { config: nuxtConfig, configFile, layers, cwd } = await loadConfig<NuxtConfig>({
name: 'nuxt',
configFile: 'nuxt.config',
rcFile: '.nuxtrc',
@ -23,6 +23,7 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
// Resolve `rootDir` & `srcDir` of layers
for (const layer of layers) {
layer.config = layer.config || {}
layer.config.rootDir = layer.config.rootDir ?? layer.cwd
layer.config.srcDir = resolve(layer.config.rootDir, layer.config.srcDir)
}

View File

@ -2,19 +2,19 @@ export * from 'vue'
export const install = () => {}
export function set (target, key, val) {
export function set (target: any, key: string | number | symbol, val: any) {
if (Array.isArray(target)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
target.length = Math.max(target.length, key as number)
target.splice(key as number, 1, val)
return val
}
target[key] = val
return val
}
export function del (target, key) {
export function del (target: any, key: string | number | symbol) {
if (Array.isArray(target)) {
target.splice(key, 1)
target.splice(key as number, 1)
return
}
delete target[key]

View File

@ -8,7 +8,7 @@ export default defineComponent({
}
},
setup (_props, { slots, emit }) {
const error = ref(null)
const error = ref<Error | null>(null)
const nuxtApp = useNuxtApp()
onErrorCaptured((err) => {

View File

@ -1,10 +1,10 @@
import { defineComponent, h, resolveComponent, PropType, computed, DefineComponent } from 'vue'
import { RouteLocationRaw, Router } from 'vue-router'
import { defineComponent, h, resolveComponent, PropType, computed, DefineComponent, ComputedRef } from 'vue'
import { RouteLocationRaw } from 'vue-router'
import { hasProtocol } from 'ufo'
import { navigateTo, useRouter } from '#app'
const firstNonUndefined = <T>(...args: T[]): T => args.find(arg => arg !== undefined)
const firstNonUndefined = <T>(...args: (T | undefined)[]) => args.find(arg => arg !== undefined)
const DEFAULT_EXTERNAL_REL_ATTRIBUTE = 'noopener noreferrer'
@ -24,8 +24,8 @@ export type NuxtLinkProps = {
custom?: boolean
// Attributes
target?: string
rel?: string
target?: string | null
rel?: string | null
noRel?: boolean
// Styling
@ -39,7 +39,7 @@ export type NuxtLinkProps = {
export function defineNuxtLink (options: NuxtLinkOptions) {
const componentName = options.componentName || 'NuxtLink'
const checkPropConflicts = (props: NuxtLinkProps, main: string, sub: string): void => {
const checkPropConflicts = (props: NuxtLinkProps, main: keyof NuxtLinkProps, sub: keyof NuxtLinkProps): void => {
if (process.dev && props[main] !== undefined && props[sub] !== undefined) {
console.warn(`[${componentName}] \`${main}\` and \`${sub}\` cannot be used together. \`${sub}\` will be ignored.`)
}
@ -116,10 +116,10 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
}
},
setup (props, { slots }) {
const router = useRouter() as Router | undefined
const router = useRouter()
// Resolving `to` value from `to` and `href` props
const to = computed<string | RouteLocationRaw>(() => {
const to: ComputedRef<string | RouteLocationRaw> = computed(() => {
checkPropConflicts(props, 'to', 'href')
return props.to || props.href || '' // Defaults to empty string (won't render any `href` attribute)
@ -127,7 +127,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
// Resolving link type
const isExternal = computed<boolean>(() => {
// External prop is explictly set
// External prop is explicitly set
if (props.external) {
return true
}
@ -180,11 +180,13 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
// https://router.vuejs.org/api/#custom
if (props.custom) {
if (!slots.default) { return null }
if (!slots.default) {
return null
}
return slots.default({
href,
navigate,
route: router.resolve(href),
route: router.resolve(href!),
rel,
target,
isActive: false,

View File

@ -1,11 +1,11 @@
import { h } from 'vue'
import { defineComponent, h } from 'vue'
import type { Component } from 'vue'
const Fragment = {
const Fragment = defineComponent({
setup (_props, { slots }) {
return () => slots.default?.()
}
}
})
/**
* Internal utility

View File

@ -1,6 +1,5 @@
import { onBeforeMount, onServerPrefetch, onUnmounted, ref, getCurrentInstance, watch, unref } from 'vue'
import type { Ref, WatchSource } from 'vue'
import { wrapInRef } from './utils'
import { NuxtApp, useNuxtApp } from '#app'
export type _Transform<Input = any, Output = any> = (input: Input) => Output
@ -25,7 +24,7 @@ export interface AsyncDataOptions<
> {
server?: boolean
lazy?: boolean
default?: () => DataT | Ref<DataT>
default?: () => DataT | Ref<DataT> | null
transform?: Transform
pick?: PickKeys
watch?: MultiWatchSources
@ -37,10 +36,10 @@ export interface RefreshOptions {
}
export interface _AsyncData<DataT, ErrorT> {
data: Ref<DataT>
data: Ref<DataT | null>
pending: Ref<boolean>
refresh: (opts?: RefreshOptions) => Promise<void>
error: Ref<ErrorT>
error: Ref<ErrorT | null>
}
export type AsyncData<Data, Error> = _AsyncData<Data, Error> & Promise<_AsyncData<Data, Error>>
@ -70,7 +69,7 @@ export function useAsyncData<
DataE = Error,
Transform extends _Transform<DataT> = _Transform<DataT, DataT>,
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
> (...args): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true> {
> (...args: any[]): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
@ -102,7 +101,8 @@ export function useAsyncData<
// Setup hook callbacks once per instance
const instance = getCurrentInstance()
if (instance && !instance._nuxtOnBeforeMountCbs) {
const cbs = instance._nuxtOnBeforeMountCbs = []
instance._nuxtOnBeforeMountCbs = []
const cbs = instance._nuxtOnBeforeMountCbs
if (instance && process.client) {
onBeforeMount(() => {
cbs.forEach((cb) => { cb() })
@ -115,7 +115,7 @@ export function useAsyncData<
const useInitialCache = () => options.initialCache && nuxt.payload.data[key] !== undefined
const asyncData = {
data: wrapInRef(nuxt.payload.data[key] ?? options.default()),
data: ref(nuxt.payload.data[key] ?? options.default?.() ?? null),
pending: ref(!useInitialCache()),
error: ref(nuxt.payload._errors[key] ?? null)
} as AsyncData<DataT, DataE>
@ -151,7 +151,7 @@ export function useAsyncData<
})
.catch((error: any) => {
asyncData.error.value = error
asyncData.data.value = unref(options.default())
asyncData.data.value = unref(options.default?.() ?? null)
})
.finally(() => {
asyncData.pending.value = false
@ -230,7 +230,7 @@ export function useLazyAsyncData<
DataE = Error,
Transform extends _Transform<DataT> = _Transform<DataT, DataT>,
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
> (...args): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true> {
> (...args: any[]): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
const [key, handler, options] = args as [string, (ctx?: NuxtApp) => Promise<DataT>, AsyncDataOptions<DataT, Transform, PickKeys>]
@ -249,7 +249,7 @@ export function refreshNuxtData (keys?: string | string[]): Promise<void> {
function pick (obj: Record<string, any>, keys: string[]) {
const newObj = {}
for (const key of keys) {
newObj[key] = obj[key]
(newObj as any)[key] = obj[key]
}
return newObj
}

View File

@ -9,8 +9,8 @@ export const NuxtComponentIndicator = '__nuxt_component'
async function runLegacyAsyncData (res: Record<string, any> | Promise<Record<string, any>>, fn: (nuxtApp: NuxtApp) => Promise<Record<string, any>>) {
const nuxt = useNuxtApp()
const route = useRoute()
const vm = getCurrentInstance()
const { fetchKey } = vm.proxy.$options
const vm = getCurrentInstance()!
const { fetchKey } = vm.proxy!.$options
const key = typeof fetchKey === 'function' ? fetchKey(() => '') : fetchKey || route.fullPath
const { data } = await useAsyncData(`options:asyncdata:${key}`, () => fn(nuxt))
if (data.value && typeof data.value === 'object') {
@ -38,8 +38,7 @@ export const defineNuxtComponent: typeof defineComponent =
setup (props, ctx) {
const res = setup?.(props, ctx) || {}
let promises: unknown[] | undefined = []
promises = promises || []
const promises: Promise<any>[] = []
if (options.asyncData) {
promises.push(runLegacyAsyncData(res, options.asyncData))
}
@ -49,7 +48,6 @@ export const defineNuxtComponent: typeof defineComponent =
.then(() => res)
.finally(() => {
promises.length = 0
promises = null
})
}
} as DefineComponent

View File

@ -1,11 +1,10 @@
import { Ref, watch } from 'vue'
import { ref, Ref, watch } from 'vue'
import { parse, serialize, CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
import { appendHeader } from 'h3'
import type { CompatibilityEvent } from 'h3'
import destr from 'destr'
import { isEqual } from 'ohash'
import { useRequestEvent } from './ssr'
import { wrapInRef } from './utils'
import { useNuxtApp } from '#app'
type _CookieOptions = Omit<CookieSerializeOptions & CookieParseOptions, 'decode' | 'encode'>
@ -24,11 +23,11 @@ const CookieDefaults: CookieOptions<any> = {
encode: val => encodeURIComponent(typeof val === 'string' ? val : JSON.stringify(val))
}
export function useCookie <T=string> (name: string, _opts?: CookieOptions<T>): CookieRef<T> {
export function useCookie <T = string> (name: string, _opts?: CookieOptions<T>): CookieRef<T> {
const opts = { ...CookieDefaults, ..._opts }
const cookies = readRawCookies(opts)
const cookies = readRawCookies(opts) || {}
const cookie = wrapInRef<T>(cookies[name] ?? opts.default?.())
const cookie = ref<T | undefined>(cookies[name] as any ?? opts.default?.())
if (process.client) {
watch(cookie, () => { writeClientCookie(name, cookie.value, opts as CookieSerializeOptions) })
@ -46,7 +45,7 @@ export function useCookie <T=string> (name: string, _opts?: CookieOptions<T>): C
return cookie as CookieRef<T>
}
function readRawCookies (opts: CookieOptions = {}): Record<string, string> {
function readRawCookies (opts: CookieOptions = {}): Record<string, string> | undefined {
if (process.server) {
return parse(useRequestEvent()?.req.headers.cookie || '', opts)
} else if (process.client) {

View File

@ -37,7 +37,7 @@ export function useFetch<
arg1?: string | UseFetchOptions<_ResT, Transform, PickKeys>,
arg2?: string
) {
const [opts, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2]
const [opts = {}, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2]
const _key = opts.key || autoKey
if (!_key || typeof _key !== 'string') {
throw new TypeError('[nuxt] [useFetch] key must be a string: ' + _key)

View File

@ -61,7 +61,7 @@ export interface NavigateToOptions {
redirectCode?: number
}
export const navigateTo = (to: RouteLocationRaw, options: NavigateToOptions = {}): Promise<void | NavigationFailure> | RouteLocationRaw => {
export const navigateTo = (to: RouteLocationRaw | undefined | null, options: NavigateToOptions = {}): Promise<void | NavigationFailure> | RouteLocationRaw => {
if (!to) {
to = '/'
}
@ -74,7 +74,7 @@ export const navigateTo = (to: RouteLocationRaw, options: NavigateToOptions = {}
const nuxtApp = useNuxtApp()
if (nuxtApp.ssrContext && nuxtApp.ssrContext.event) {
const redirectLocation = joinURL(useRuntimeConfig().app.baseURL, router.resolve(to).fullPath || '/')
return nuxtApp.callHook('app:redirected').then(() => sendRedirect(nuxtApp.ssrContext.event, redirectLocation, options.redirectCode || 302))
return nuxtApp.callHook('app:redirected').then(() => sendRedirect(nuxtApp.ssrContext!.event, redirectLocation, options.redirectCode || 302))
}
}
// Client-side redirection using vue-router

View File

@ -5,9 +5,9 @@ import { NuxtApp } from '#app/nuxt'
export function useRequestHeaders<K extends string = string> (include: K[]): Record<K, string | undefined>
export function useRequestHeaders (): Readonly<Record<string, string | undefined>>
export function useRequestHeaders (include?) {
export function useRequestHeaders (include?: any[]) {
if (process.client) { return {} }
const headers: Record<string, string | string[]> = useNuxtApp().ssrContext?.event.req.headers ?? {}
const headers = useNuxtApp().ssrContext?.event.req.headers ?? {}
if (!include) { return headers }
return Object.fromEntries(include.filter(key => headers[key]).map(key => [key, headers[key]]))
}

View File

@ -10,7 +10,7 @@ import { useNuxtApp } from '#app'
*/
export function useState <T> (key?: string, init?: (() => T | Ref<T>)): Ref<T>
export function useState <T> (init?: (() => T | Ref<T>)): Ref<T>
export function useState <T> (...args): Ref<T> {
export function useState <T> (...args: any): Ref<T> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
const [_key, init] = args as [string, (() => T | Ref<T>)]

View File

@ -1,3 +0,0 @@
import { isRef, ref, Ref } from 'vue'
export const wrapInRef = <T> (value: T | Ref<T>) => isRef(value) ? value : ref(value)

View File

@ -35,7 +35,7 @@ if (process.server) {
await nuxt.hooks.callHook('app:created', vueApp)
} catch (err) {
await nuxt.callHook('app:error', err)
nuxt.payload.error = nuxt.payload.error || err
nuxt.payload.error = (nuxt.payload.error || err) as any
}
return vueApp
@ -66,7 +66,7 @@ if (process.client) {
await applyPlugins(nuxt, plugins)
} catch (err) {
await nuxt.callHook('app:error', err)
nuxt.payload.error = nuxt.payload.error || err
nuxt.payload.error = (nuxt.payload.error || err) as any
}
try {
@ -77,11 +77,11 @@ if (process.client) {
await nextTick()
} catch (err) {
await nuxt.callHook('app:error', err)
nuxt.payload.error = nuxt.payload.error || err
nuxt.payload.error = (nuxt.payload.error || err) as any
}
}
entry().catch((error) => {
entry().catch((error: unknown) => {
console.error('Error while mounting app:', error) // eslint-disable-line no-console
})
}

View File

@ -38,6 +38,23 @@ export interface RuntimeNuxtHooks {
'vue:error': (...args: Parameters<Parameters<typeof onErrorCaptured>[0]>) => HookResult
}
export interface NuxtSSRContext extends SSRContext {
url: string
event: CompatibilityEvent
/** @deprecated Use `event` instead. */
req?: CompatibilityEvent['req']
/** @deprecated Use `event` instead. */
res?: CompatibilityEvent['res']
runtimeConfig: RuntimeConfig
noSSR: boolean
/** whether we are rendering an SSR error */
error?: boolean
nuxt: _NuxtApp
payload: _NuxtApp['payload']
teleports?: Record<string, string>
renderMeta?: () => Promise<NuxtMeta> | NuxtMeta
}
interface _NuxtApp {
vueApp: App<Element>
globalName: string
@ -48,28 +65,13 @@ interface _NuxtApp {
[key: string]: any
_asyncDataPromises?: Record<string, Promise<any>>
_asyncDataPromises: Record<string, Promise<any> | undefined>
ssrContext?: SSRContext & {
url: string
event: CompatibilityEvent
/** @deprecated Use `event` instead. */
req?: CompatibilityEvent['req']
/** @deprecated Use `event` instead. */
res?: CompatibilityEvent['res']
runtimeConfig: RuntimeConfig
noSSR: boolean
/** whether we are rendering an SSR error */
error?: boolean
nuxt: _NuxtApp
payload: _NuxtApp['payload']
teleports?: Record<string, string>
renderMeta?: () => Promise<NuxtMeta> | NuxtMeta
}
ssrContext?: NuxtSSRContext
payload: {
serverRendered?: boolean
data?: Record<string, any>
state?: Record<string, any>
data: Record<string, any>
state: Record<string, any>
rendered?: Function
error?: Error | {
url: string
@ -135,24 +137,24 @@ export function createNuxtApp (options: CreateOptions) {
}
// Expose to server renderer to create window.__NUXT__
nuxtApp.ssrContext = nuxtApp.ssrContext || {} as any
if (nuxtApp.ssrContext.payload) {
Object.assign(nuxtApp.payload, nuxtApp.ssrContext.payload)
if (nuxtApp.ssrContext!.payload) {
Object.assign(nuxtApp.payload, nuxtApp.ssrContext!.payload)
}
nuxtApp.ssrContext.payload = nuxtApp.payload
nuxtApp.ssrContext!.payload = nuxtApp.payload
// Expose client runtime-config to the payload
nuxtApp.payload.config = {
public: options.ssrContext.runtimeConfig.public,
app: options.ssrContext.runtimeConfig.app
public: options.ssrContext!.runtimeConfig.public,
app: options.ssrContext!.runtimeConfig.app
}
}
// Expose runtime config
const runtimeConfig = process.server
? options.ssrContext.runtimeConfig
? options.ssrContext!.runtimeConfig
: reactive(nuxtApp.payload.config)
// Backward compatibilty following #4254
// Backward compatibility following #4254
const compatibilityConfig = new Proxy(runtimeConfig, {
get (target, prop) {
if (prop === 'public') {
@ -192,9 +194,9 @@ export async function applyPlugins (nuxtApp: NuxtApp, plugins: Plugin[]) {
}
export function normalizePlugins (_plugins: Plugin[]) {
const unwrappedPlugins = []
const legacyInjectPlugins = []
const invalidPlugins = []
const unwrappedPlugins: Plugin[] = []
const legacyInjectPlugins: Plugin[] = []
const invalidPlugins: Plugin[] = []
const plugins = _plugins.map((plugin) => {
if (typeof plugin !== 'function') {

View File

@ -6,7 +6,7 @@ export default defineNuxtPlugin((nuxtApp) => {
if (logs.length > 0) {
const ssrLogStyle = 'background: #003C3C;border-radius: 0.5em;color: white;font-weight: bold;padding: 2px 0.5em;'
console.groupCollapsed && console.groupCollapsed('%cNuxt Server Logs', ssrLogStyle)
logs.forEach(logObj => (console[logObj.type] || console.log)(...logObj.args))
logs.forEach((logObj:any) => (console[logObj.type as 'log'] || console.log)(...logObj.args))
delete nuxtApp.payload.logs
console.groupEnd && console.groupEnd()
}

View File

@ -13,8 +13,8 @@ export default defineNuxtPlugin((nuxtApp) => {
el.style.transition = 'width 0.1s, opacity 0.4s'
const duration = 3000
const progress = 10000 / Math.floor(duration)
let timeout
let interval
let timeout: ReturnType<typeof setTimeout> | undefined
let interval: ReturnType<typeof setInterval> | undefined
nuxtApp.hook('page:start', () => {
if (timeout) { return }
timeout = setTimeout(() => {
@ -30,9 +30,9 @@ export default defineNuxtPlugin((nuxtApp) => {
})
nuxtApp.hook('page:finish', () => {
timeout && clearTimeout(timeout)
timeout = null
timeout = undefined
interval && clearInterval(interval)
interval = null
interval = undefined
el.style.width = '100%'
el.style.opacity = '0%'
setTimeout(() => {

View File

@ -1,9 +1,8 @@
import { reactive, h } from 'vue'
import { parseURL, parseQuery, withoutBase, isEqual, joinURL } from 'ufo'
import { createError } from 'h3'
import { defineNuxtPlugin } from '..'
import { defineNuxtPlugin, clearError, navigateTo, showError, useRuntimeConfig } from '..'
import { callWithNuxt } from '../nuxt'
import { clearError, navigateTo, showError, useRuntimeConfig } from '#app'
// @ts-ignore
import { globalMiddleware } from '#build/middleware'
@ -90,8 +89,9 @@ interface Router {
export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
const initialURL = process.client
? withoutBase(window.location.pathname, useRuntimeConfig().app.baseURL) + window.location.search + window.location.hash
: nuxtApp.ssrContext.url
const routes = []
: nuxtApp.ssrContext!.url
const routes: Route[] = []
const hooks: { [key in keyof RouterHooks]: RouterHooks[key][] } = {
'navigate:before': [],
@ -194,7 +194,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
const route = router.resolve(props.to)
return props.custom
? slots.default?.({ href: props.to, navigate, route })
: h('a', { href: props.to, onClick: (e) => { e.preventDefault(); return navigate() } }, slots)
: h('a', { href: props.to, onClick: (e: MouseEvent) => { e.preventDefault(); return navigate() } }, slots)
}
}
})

View File

@ -24,7 +24,7 @@ export default defineNuxtModule<Partial<AutoImportsOptions>>({
// Allow modules extending sources
await nuxt.callHook('autoImports:sources', options.presets as ImportPresetWithDeprecation[])
options.presets.forEach((i: ImportPresetWithDeprecation) => {
options.presets?.forEach((i: ImportPresetWithDeprecation | string) => {
if (typeof i !== 'string' && i.names && !i.imports) {
i.imports = i.names
logger.warn('auto-imports: presets.names is deprecated, use presets.imports instead')
@ -45,10 +45,13 @@ export default defineNuxtModule<Partial<AutoImportsOptions>>({
})
// composables/ dirs from all layers
let composablesDirs = []
let composablesDirs: string[] = []
for (const layer of nuxt.options._layers) {
composablesDirs.push(resolve(layer.config.srcDir, 'composables'))
for (const dir of (layer.config.autoImports?.dirs ?? [])) {
if (!dir) {
continue
}
composablesDirs.push(resolve(layer.config.srcDir, dir))
}
}
@ -123,7 +126,7 @@ function addDeclarationTemplates (ctx: Unimport) {
// Remove file extension for benefit of TypeScript
const stripExtension = (path: string) => path.replace(/\.[a-z]+$/, '')
const resolved = {}
const resolved: Record<string, string> = {}
const r = ({ from }: Import) => {
if (resolved[from]) {
return resolved[from]

View File

@ -1,7 +1,7 @@
import { promises as fsp } from 'node:fs'
import { dirname, resolve } from 'pathe'
import defu from 'defu'
import type { Nuxt, NuxtApp, NuxtPlugin } from '@nuxt/schema'
import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate } from '@nuxt/schema'
import { findPath, resolveFiles, normalizePlugin, normalizeTemplate, compileTemplate, templateUtils, tryResolveModule, resolvePath, resolveAlias } from '@nuxt/kit'
import * as defaultTemplates from './templates'
@ -13,7 +13,7 @@ export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp
extensions: nuxt.options.extensions,
plugins: [],
templates: []
} as NuxtApp)
} as unknown as NuxtApp) as NuxtApp
}
export async function generateApp (nuxt: Nuxt, app: NuxtApp) {
@ -21,7 +21,7 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp) {
await resolveApp(nuxt, app)
// User templates from options.build.templates
app.templates = Object.values(defaultTemplates).concat(nuxt.options.build.templates)
app.templates = Object.values(defaultTemplates).concat(nuxt.options.build.templates) as NuxtTemplate[]
// Extend templates with hook
await nuxt.callHook('app:templates', app)
@ -34,10 +34,10 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp) {
await Promise.all(app.templates.map(async (template) => {
const contents = await compileTemplate(template, templateContext)
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename)
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!)
nuxt.vfs[fullPath] = contents
const aliasPath = '#build/' + template.filename.replace(/\.\w+$/, '')
const aliasPath = '#build/' + template.filename!.replace(/\.\w+$/, '')
nuxt.vfs[aliasPath] = contents
// In case a non-normalized absolute path is called for on Windows
@ -102,10 +102,12 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
for (const config of nuxt.options._layers.map(layer => layer.config)) {
app.plugins.push(...[
...(config.plugins || []),
...await resolveFiles(config.srcDir, [
'plugins/*.{ts,js,mjs,cjs,mts,cts}',
'plugins/*/index.*{ts,js,mjs,cjs,mts,cts}'
])
...config.srcDir
? await resolveFiles(config.srcDir, [
'plugins/*.{ts,js,mjs,cjs,mts,cts}',
'plugins/*/index.*{ts,js,mjs,cjs,mts,cts}'
])
: []
].map(plugin => normalizePlugin(plugin as NuxtPlugin)))
}

View File

@ -15,10 +15,10 @@ export async function build (nuxt: Nuxt) {
nuxt.hook('builder:watch', async (event, path) => {
if (event !== 'change' && /^(app\.|error\.|plugins\/|middleware\/|layouts\/)/i.test(path)) {
if (path.startsWith('app')) {
app.mainComponent = null
app.mainComponent = undefined
}
if (path.startsWith('error')) {
app.errorComponent = null
app.errorComponent = undefined
}
await generateApp()
}
@ -38,7 +38,7 @@ export async function build (nuxt: Nuxt) {
}
function watch (nuxt: Nuxt) {
const watcher = chokidar.watch(nuxt.options._layers.map(i => i.config.srcDir), {
const watcher = chokidar.watch(nuxt.options._layers.map(i => i.config.srcDir as string).filter(Boolean), {
...nuxt.options.watchers.chokidar,
cwd: nuxt.options.srcDir,
ignoreInitial: true,
@ -61,7 +61,7 @@ async function bundle (nuxt: Nuxt) {
: nuxt.options.builder
return bundle(nuxt)
} catch (error) {
} catch (error: any) {
await nuxt.callHook('build:error', error)
if (error.toString().includes('Cannot find module \'@nuxt/webpack-builder\'')) {

View File

@ -20,6 +20,7 @@ export const addModuleTranspiles = (opts: AddModuleTranspilesOptions = {}) => {
// Try to sanitize modules to better match imports
nuxt.options.build.transpile =
nuxt.options.build.transpile.map(m => typeof m === 'string' ? m.split('node_modules/').pop() : m)
.filter(<T>(x: T | undefined): x is T => !!x)
function isTranspilePresent (mod: string) {
return nuxt.options.build.transpile.some(t => !(t instanceof Function) && (t instanceof RegExp ? t.test(mod) : new RegExp(t).test(mod)))

View File

@ -22,7 +22,7 @@ export async function initNitro (nuxt: Nuxt) {
dev: nuxt.options.dev,
preset: nuxt.options.dev ? 'nitro-dev' : undefined,
buildDir: nuxt.options.buildDir,
scanDirs: nuxt.options._layers.map(layer => join(layer.config.srcDir, 'server')),
scanDirs: nuxt.options._layers.map(layer => layer.config.srcDir).filter(Boolean).map(dir => join(dir!, 'server')),
renderer: resolve(distDir, 'core/runtime/nitro/renderer'),
errorHandler: resolve(distDir, 'core/runtime/nitro/error'),
nodeModulesDirs: nuxt.options.modulesDir,
@ -49,7 +49,7 @@ export async function initNitro (nuxt: Nuxt) {
],
prerender: {
crawlLinks: nuxt.options._generate ? nuxt.options.generate.crawler : false,
routes: []
routes: ([] as string[])
.concat(nuxt.options._generate ? ['/', ...nuxt.options.generate.routes] : [])
.concat(nuxt.options.ssr === false ? ['/', '/200.html', '/404.html'] : [])
},
@ -102,11 +102,11 @@ export async function initNitro (nuxt: Nuxt) {
// Add fallback server for `ssr: false`
if (!nuxt.options.ssr) {
nitroConfig.virtual['#build/dist/server/server.mjs'] = 'export default () => {}'
nitroConfig.virtual!['#build/dist/server/server.mjs'] = 'export default () => {}'
}
// Register nuxt protection patterns
nitroConfig.rollupConfig.plugins.push(ImportProtectionPlugin.rollup({
nitroConfig.rollupConfig!.plugins!.push(ImportProtectionPlugin.rollup({
rootDir: nuxt.options.rootDir,
patterns: [
...['#app', /^#build(\/|$)/]

View File

@ -81,7 +81,7 @@ async function initNuxt (nuxt: Nuxt) {
// Transpile layers within node_modules
nuxt.options.build.transpile.push(
...nuxt.options._layers.filter(i => i.cwd && i.cwd.includes('node_modules')).map(i => i.cwd)
...nuxt.options._layers.filter(i => i.cwd.includes('node_modules')).map(i => i.cwd as string)
)
// Init user modules
@ -95,7 +95,7 @@ async function initNuxt (nuxt: Nuxt) {
// Add <NuxtWelcome>
addComponent({
name: 'NuxtWelcome',
filePath: tryResolveModule('@nuxt/ui-templates/templates/welcome.vue')
filePath: tryResolveModule('@nuxt/ui-templates/templates/welcome.vue')!
})
addComponent({
@ -165,7 +165,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
transform: {
include: options._layers
.filter(i => i.cwd && i.cwd.includes('node_modules'))
.map(i => new RegExp(`(^|\\/)${escapeRE(i.cwd.split('node_modules/').pop())}(\\/|$)(?!node_modules\\/)`))
.map(i => new RegExp(`(^|\\/)${escapeRE(i.cwd!.split('node_modules/').pop()!)}(\\/|$)(?!node_modules\\/)`))
}
}])
options.modulesDir.push(resolve(pkgDir, 'node_modules'))

View File

@ -1,16 +1,14 @@
import { withQuery } from 'ufo'
import type { NitroErrorHandler } from 'nitropack'
import type { H3Error } from 'h3'
// @ts-ignore TODO
import { normalizeError, isJsonRequest } from '#internal/nitro/utils'
import { NuxtApp } from '#app'
export default <NitroErrorHandler> async function errorhandler (error: H3Error, event) {
// Parse and normalize error
const { stack, statusCode, statusMessage, message } = normalizeError(error)
// Create an error object
const errorObject: Exclude<NuxtApp['payload']['error'], Error> = {
const errorObject = {
url: event.req.url,
statusCode,
statusMessage,

View File

@ -1,20 +1,15 @@
import { createRenderer } from 'vue-bundle-renderer/runtime'
import type { RenderHandler, RenderResponse } from 'nitropack'
import type { RenderResponse } from 'nitropack'
import type { Manifest } from 'vite'
import { CompatibilityEvent, getQuery } from 'h3'
import { getQuery } from 'h3'
import devalue from '@nuxt/devalue'
import { renderToString as _renderToString } from 'vue/server-renderer'
import type { NuxtApp } from '#app'
import type { NuxtApp, NuxtSSRContext } from '#app'
import { useRuntimeConfig, useNitroApp, defineRenderHandler } from '#internal/nitro'
// @ts-ignore
import { useRuntimeConfig, useNitroApp, defineRenderHandler as _defineRenderHandler } from '#internal/nitro'
// @ts-ignore
import { buildAssetsURL } from '#paths'
export type NuxtSSRContext = NuxtApp['ssrContext']
const defineRenderHandler = _defineRenderHandler as (h: RenderHandler) => CompatibilityEvent
export interface NuxtRenderHTMLContext {
htmlAttrs: string[]
head: string[]
@ -31,10 +26,12 @@ export interface NuxtRenderResponse {
headers: Record<string, string>
}
interface ClientManifest {}
// @ts-ignore
const getClientManifest: () => Promise<Manifest> = () => import('#build/dist/server/client.manifest.mjs')
.then(r => r.default || r)
.then(r => typeof r === 'function' ? r() : r)
.then(r => typeof r === 'function' ? r() : r) as Promise<ClientManifest>
// @ts-ignore
const getServerEntry = () => import('#build/dist/server/server.mjs').then(r => r.default || r)
@ -57,7 +54,8 @@ const getSSRRenderer = lazyCachedFunction(async () => {
// Create renderer
const renderer = createRenderer(createSSRApp, options)
async function renderToString (input, context) {
type RenderToStringParams = Parameters<typeof _renderToString>
async function renderToString (input: RenderToStringParams[0], context: RenderToStringParams[1]) {
const html = await _renderToString(input, context)
// In development with vite-node, the manifest is on-demand and will be available after rendering
if (process.dev && process.env.NUXT_VITE_NODE_OPTIONS) {
@ -84,14 +82,16 @@ const getSPARenderer = lazyCachedFunction(async () => {
const renderToString = (ssrContext: NuxtSSRContext) => {
const config = useRuntimeConfig()
ssrContext.payload = {
ssrContext!.payload = {
serverRendered: false,
config: {
public: config.public,
app: config.app
}
},
data: {},
state: {}
}
ssrContext.renderMeta = ssrContext.renderMeta ?? (() => ({}))
ssrContext!.renderMeta = ssrContext!.renderMeta ?? (() => ({}))
return Promise.resolve(result)
}
@ -109,23 +109,25 @@ export default defineRenderHandler(async (event) => {
event,
req: event.req,
res: event.res,
runtimeConfig: useRuntimeConfig(),
runtimeConfig: useRuntimeConfig() as NuxtSSRContext['runtimeConfig'],
noSSR: !!event.req.headers['x-nuxt-no-ssr'],
error: !!ssrError,
nuxt: undefined, /* NuxtApp */
payload: ssrError ? { error: ssrError } : undefined
nuxt: undefined!, /* NuxtApp */
payload: ssrError ? { error: ssrError } as NuxtSSRContext['payload'] : undefined!
}
// Render app
const renderer = (process.env.NUXT_NO_SSR || ssrContext.noSSR) ? await getSPARenderer() : await getSSRRenderer()
const _rendered = await renderer.renderToString(ssrContext).catch((err) => {
if (!ssrError) { throw err }
if (!ssrError) {
throw err
}
})
await ssrContext.nuxt?.hooks.callHook('app:rendered', { ssrContext })
// Handle errors
if (!_rendered) {
return
return undefined!
}
if (ssrContext.payload?.error && !ssrError) {
throw ssrContext.payload.error
@ -143,7 +145,7 @@ export default defineRenderHandler(async (event) => {
_rendered.renderStyles(),
ssrContext.styles
]),
bodyAttrs: normalizeChunks([renderedMeta.bodyAttrs]),
bodyAttrs: normalizeChunks([renderedMeta.bodyAttrs!]),
bodyPreprend: normalizeChunks([
renderedMeta.bodyScriptsPrepend,
ssrContext.teleports?.body
@ -188,8 +190,8 @@ function lazyCachedFunction <T> (fn: () => Promise<T>): () => Promise<T> {
}
}
function normalizeChunks (chunks: string[]) {
return chunks.filter(Boolean).map(i => i.trim())
function normalizeChunks (chunks: (string | undefined)[]) {
return chunks.filter(Boolean).map(i => i!.trim())
}
function joinTags (tags: string[]) {

View File

@ -11,7 +11,7 @@ export interface TemplateContext {
app: NuxtApp
}
export const vueShim = {
export const vueShim: NuxtTemplate = {
filename: 'types/vue-shim.d.ts',
getContents: () =>
[
@ -24,29 +24,29 @@ export const vueShim = {
}
// TODO: Use an alias
export const appComponentTemplate = {
export const appComponentTemplate: NuxtTemplate<TemplateContext> = {
filename: 'app-component.mjs',
getContents: (ctx: TemplateContext) => genExport(ctx.app.mainComponent, ['default'])
getContents: ctx => genExport(ctx.app.mainComponent!, ['default'])
}
// TODO: Use an alias
export const rootComponentTemplate = {
export const rootComponentTemplate: NuxtTemplate<TemplateContext> = {
filename: 'root-component.mjs',
getContents: (ctx: TemplateContext) => genExport(ctx.app.rootComponent, ['default'])
getContents: ctx => genExport(ctx.app.rootComponent!, ['default'])
}
// TODO: Use an alias
export const errorComponentTemplate = {
export const errorComponentTemplate: NuxtTemplate<TemplateContext> = {
filename: 'error-component.mjs',
getContents: (ctx: TemplateContext) => genExport(ctx.app.errorComponent, ['default'])
getContents: ctx => genExport(ctx.app.errorComponent!, ['default'])
}
export const cssTemplate = {
export const cssTemplate: NuxtTemplate<TemplateContext> = {
filename: 'css.mjs',
getContents: (ctx: TemplateContext) => ctx.nuxt.options.css.map(i => genImport(i)).join('\n')
getContents: ctx => ctx.nuxt.options.css.map(i => genImport(i)).join('\n')
}
export const clientPluginTemplate = {
export const clientPluginTemplate: NuxtTemplate<TemplateContext> = {
filename: 'plugins/client.mjs',
getContents (ctx: TemplateContext) {
getContents (ctx) {
const clientPlugins = ctx.app.plugins.filter(p => !p.mode || p.mode !== 'server')
const exports: string[] = []
const imports: string[] = []
@ -63,9 +63,9 @@ export const clientPluginTemplate = {
}
}
export const serverPluginTemplate = {
export const serverPluginTemplate: NuxtTemplate<TemplateContext> = {
filename: 'plugins/server.mjs',
getContents (ctx: TemplateContext) {
getContents (ctx) {
const serverPlugins = ctx.app.plugins.filter(p => !p.mode || p.mode !== 'client')
const exports: string[] = ['preload']
const imports: string[] = ["import preload from '#app/plugins/preload.server'"]
@ -82,9 +82,9 @@ export const serverPluginTemplate = {
}
}
export const pluginsDeclaration = {
export const pluginsDeclaration: NuxtTemplate<TemplateContext> = {
filename: 'types/plugins.d.ts',
getContents: (ctx: TemplateContext) => {
getContents: (ctx) => {
const EXTENSION_RE = new RegExp(`(?<=\\w)(${ctx.nuxt.options.extensions.map(e => escapeRE(e)).join('|')})$`, 'g')
const tsImports = ctx.app.plugins.map(p => (isAbsolute(p.src) ? relative(join(ctx.nuxt.options.buildDir, 'types'), p.src) : p.src).replace(EXTENSION_RE, ''))
@ -111,9 +111,9 @@ export { }
}
const adHocModules = ['router', 'pages', 'auto-imports', 'meta', 'components']
export const schemaTemplate = {
export const schemaTemplate: NuxtTemplate<TemplateContext> = {
filename: 'types/schema.d.ts',
getContents: ({ nuxt }: TemplateContext) => {
getContents: ({ nuxt }) => {
const moduleInfo = nuxt.options._installedModules.map(m => ({
...m.meta || {},
importName: m.entryPath || m.meta?.name
@ -149,9 +149,9 @@ export const schemaTemplate = {
}
// Add layouts template
export const layoutTemplate: NuxtTemplate = {
export const layoutTemplate: NuxtTemplate<TemplateContext> = {
filename: 'layouts.mjs',
getContents ({ app }: TemplateContext) {
getContents ({ app }) {
const layoutsObject = genObjectFromRawEntries(Object.values(app.layouts).map(({ name, file }) => {
return [name, `defineAsyncComponent(${genDynamicImport(file)})`]
}))
@ -163,9 +163,9 @@ export const layoutTemplate: NuxtTemplate = {
}
// Add middleware template
export const middlewareTemplate: NuxtTemplate = {
export const middlewareTemplate: NuxtTemplate<TemplateContext> = {
filename: 'middleware.mjs',
getContents ({ app }: TemplateContext) {
getContents ({ app }) {
const globalMiddleware = app.middleware.filter(mw => mw.global)
const namedMiddleware = app.middleware.filter(mw => !mw.global)
const namedMiddlewareObject = genObjectFromRawEntries(namedMiddleware.map(mw => [mw.name, genDynamicImport(mw.path)]))
@ -184,7 +184,7 @@ export const useRuntimeConfig = () => window?.__NUXT__?.config || {}
`
}
export const publicPathTemplate: NuxtTemplate = {
export const publicPathTemplate: NuxtTemplate<TemplateContext> = {
filename: 'paths.mjs',
getContents ({ nuxt }) {
return [

View File

@ -20,20 +20,20 @@ export default defineNuxtPlugin((nuxtApp) => {
}
if (process.server) {
nuxtApp.ssrContext.renderMeta = async () => {
nuxtApp.ssrContext!.renderMeta = async () => {
// @ts-ignore
const { renderMetaToString } = await import('vue-meta/ssr')
nuxtApp.ssrContext.teleports = nuxtApp.ssrContext.teleports || {}
nuxtApp.ssrContext!.teleports = nuxtApp.ssrContext!.teleports || {}
await renderMetaToString(nuxtApp.app, nuxtApp.ssrContext)
return {
htmlAttrs: nuxtApp.ssrContext.teleports.htmlAttrs || '',
headAttrs: nuxtApp.ssrContext.teleports.headAttrs || '',
bodyAttrs: nuxtApp.ssrContext.teleports.bodyAttrs || '',
headTags: nuxtApp.ssrContext.teleports.head || '',
bodyScriptsPrepend: nuxtApp.ssrContext.teleports['body-prepend'] || '',
bodyScripts: nuxtApp.ssrContext.teleports.body || ''
htmlAttrs: nuxtApp.ssrContext!.teleports.htmlAttrs || '',
headAttrs: nuxtApp.ssrContext!.teleports.headAttrs || '',
bodyAttrs: nuxtApp.ssrContext!.teleports.bodyAttrs || '',
headTags: nuxtApp.ssrContext!.teleports.head || '',
bodyScriptsPrepend: nuxtApp.ssrContext!.teleports['body-prepend'] || '',
bodyScripts: nuxtApp.ssrContext!.teleports.body || ''
}
}
}

View File

@ -45,7 +45,7 @@ export default defineNuxtPlugin((nuxtApp) => {
}
if (process.server) {
nuxtApp.ssrContext.renderMeta = () => {
nuxtApp.ssrContext!.renderMeta = () => {
const meta = renderHeadToString(head)
return {
...meta,

View File

@ -34,6 +34,6 @@ export default defineNuxtPlugin((nuxtApp) => {
for (const name in Components) {
// eslint-disable-next-line import/namespace
nuxtApp.vueApp.component(name, Components[name])
nuxtApp.vueApp.component(name, (Components as any)[name])
}
})

View File

@ -47,7 +47,7 @@ export default defineNuxtModule({
nuxt.hook('app:resolve', (app) => {
// Add default layout for pages
if (app.mainComponent.includes('@nuxt/ui-templates')) {
if (app.mainComponent!.includes('@nuxt/ui-templates')) {
app.mainComponent = resolve(runtimeDir, 'app.vue')
}
})

View File

@ -1,5 +1,5 @@
import { computed, DefineComponent, defineComponent, h, inject, provide, reactive, Suspense, Transition } from 'vue'
import { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouterView } from 'vue-router'
import { RouteLocation, RouteLocationNormalized, RouteLocationNormalizedLoaded, RouterView } from 'vue-router'
import { generateRouteKey, RouterViewSlotProps, wrapInKeepAlive } from './utils'
import { useNuxtApp } from '#app'
@ -58,6 +58,7 @@ export default defineComponent({
const defaultPageTransition = { name: 'page', mode: 'out-in' }
const Component = defineComponent({
// TODO: Type props
// eslint-disable-next-line vue/require-prop-types
props: ['routeProps', 'pageKey'],
setup (props) {
@ -66,9 +67,9 @@ const Component = defineComponent({
const previousRoute = props.routeProps.route
// Provide a reactive route within the page
const route = {}
const route = {} as RouteLocation
for (const key in props.routeProps.route) {
route[key] = computed(() => previousKey === props.pageKey ? props.routeProps.route[key] : previousRoute[key])
(route as any)[key] = computed(() => previousKey === props.pageKey ? props.routeProps.route[key] : previousRoute[key])
}
provide('_route', reactive(route))

View File

@ -59,7 +59,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
? createWebHistory(baseURL)
: createMemoryHistory(baseURL)
const initialURL = process.server ? nuxtApp.ssrContext.url : createCurrentLocation(baseURL, window.location)
const initialURL = process.server ? nuxtApp.ssrContext!.url : createCurrentLocation(baseURL, window.location)
const router = createRouter({
...routerOptions,
history: routerHistory,
@ -89,9 +89,9 @@ export default defineNuxtPlugin(async (nuxtApp) => {
})
// https://github.com/vuejs/router/blob/main/packages/router/src/router.ts#L1225-L1233
const route = {}
const route = {} as RouteLocation
for (const key in _route.value) {
route[key] = computed(() => _route.value[key])
(route as any)[key] = computed(() => _route.value[key as keyof RouteLocation])
}
nuxtApp._route = reactive(route)
@ -109,7 +109,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
}
await router.isReady()
} catch (error) {
} catch (error: any) {
// We'll catch 404s here
callWithNuxt(nuxtApp, showError, [error])
}
@ -133,7 +133,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
}
for (const entry of middlewareEntries) {
const middleware = typeof entry === 'string' ? nuxtApp._middleware.named[entry] || await namedMiddleware[entry]?.().then(r => r.default || r) : entry
const middleware = typeof entry === 'string' ? nuxtApp._middleware.named[entry] || await namedMiddleware[entry]?.().then((r: any) => r.default || r) : entry
if (!middleware) {
if (process.dev) {
@ -169,7 +169,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
statusMessage: `Page not found: ${to.fullPath}`
})])
} else if (process.server && to.matched[0].name === '404' && nuxtApp.ssrContext) {
nuxtApp.ssrContext.res.statusCode = 404
nuxtApp.ssrContext.event.res.statusCode = 404
} else if (process.server) {
const currentURL = to.fullPath || '/'
if (!isEqual(currentURL, initialURL)) {
@ -185,7 +185,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
name: undefined, // #4920, #$4982
force: true
})
} catch (error) {
} catch (error: any) {
// We'll catch middleware errors or deliberate exceptions here
callWithNuxt(nuxtApp, showError, [error])
}

View File

@ -12,8 +12,8 @@ const interpolatePath = (route: RouteLocationNormalizedLoaded, match: RouteLocat
}
export const generateRouteKey = (override: string | ((route: RouteLocationNormalizedLoaded) => string), routeProps: RouterViewSlotProps) => {
const matchedRoute = routeProps.route.matched.find(m => m.components.default === routeProps.Component.type)
const source = override ?? matchedRoute?.meta.key ?? interpolatePath(routeProps.route, matchedRoute)
const matchedRoute = routeProps.route.matched.find(m => m.components?.default === routeProps.Component.type)
const source = override ?? matchedRoute?.meta.key ?? (matchedRoute && interpolatePath(routeProps.route, matchedRoute))
return typeof source === 'function' ? source(routeProps.route) : source
}

View File

@ -76,7 +76,7 @@ export function generateRoutesFromFiles (files: string[], pagesDir: string): Nux
// ex: parent.vue + parent/child.vue
const child = parent.find(parentRoute => parentRoute.name === route.name && !parentRoute.path.endsWith('(.*)*'))
if (child) {
if (child && child.children) {
parent = child.children
route.path = ''
} else if (segmentName === '404' && isSingleSegment) {
@ -213,11 +213,11 @@ function prepareRoutes (routes: NuxtPage[], parent?: NuxtPage) {
route.path = route.path.slice(1)
}
if (route.children.length) {
if (route.children?.length) {
route.children = prepareRoutes(route.children, route)
}
if (route.children.find(childRoute => childRoute.path === '')) {
if (route.children?.find(childRoute => childRoute.path === '')) {
delete route.name
}
}

View File

@ -8,7 +8,7 @@ vi.mock('vue', async () => {
return {
...vue,
resolveComponent: (name: string) => name,
h: (...args) => args
h: (...args: any[]) => args
}
})
@ -17,7 +17,7 @@ vi.mock('#app', () => ({
useRouter: () => ({ resolve: ({ to }: { to: string }) => ({ href: to }) })
}))
// Helpers for test lisibility
// Helpers for test visibility
const EXTERNAL = 'a'
const INTERNAL = 'RouterLink'
@ -44,7 +44,7 @@ describe('nuxt-link:to', () => {
})
it('renders link with `to` prop and warns about `href` prop conflict', () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(vi.fn())
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(vi.fn() as any)
expect(nuxtLink({ to: '/to', href: '/href' }).props.to).toBe('/to')
// TODO: Uncomment when `dev` mode for tests is available
@ -136,7 +136,7 @@ describe('nuxt-link:propsOrAttributes', () => {
})
it('honors `noRel` prop and warns about `rel` prop conflict', () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(vi.fn())
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(vi.fn() as any)
expect(nuxtLink({ to: 'https://nuxtjs.org', noRel: true, rel: 'foo' }).props.rel).toBe(null)
// TODO: Uncomment when `dev` mode for tests is available

View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"strict": true,
"noImplicitAny": true
},
"include": [
"./src/**/*.ts",
"./test/**/*.ts"
]
}

View File

@ -1,5 +1,4 @@
import { ConfigSchema } from '../../schema/config'
import type { ResolvedConfig } from 'c12'
import type { UserConfig as ViteUserConfig } from 'vite'
import type { Options as VuePluginOptions } from '@vitejs/plugin-vue'
@ -12,9 +11,20 @@ export interface NuxtConfig extends DeepPartial<Omit<ConfigSchema, 'vite'>> {
[key: string]: any
}
// TODO: Expose ConfigLayer<T> from c12
interface ConfigLayer<T> {
config: T;
cwd: string;
configFile: string
}
export type NuxtConfigLayer = ConfigLayer<NuxtConfig & {
srcDir: ConfigSchema['srcDir'],
rootDir: ConfigSchema['rootDir']
}>
/** Normalized Nuxt options available as `nuxt.options.*` */
export interface NuxtOptions extends ConfigSchema {
_layers: ResolvedConfig<NuxtConfig>[]
_layers: NuxtConfigLayer[]
}
type RuntimeConfigNamespace = Record<string, any>

View File

@ -24,7 +24,7 @@ export interface Nuxt {
vfs: Record<string, string>
}
export interface NuxtTemplate {
export interface NuxtTemplate<Options = Record<string, any>> {
/** @deprecated filename */
fileName?: string
/** @deprecated whether template is custom or a nuxt core template */
@ -51,9 +51,9 @@ export interface NuxtPlugin {
}
export interface NuxtApp {
mainComponent?: string
rootComponent?: string
errorComponent?: string
mainComponent?: string | null
rootComponent?: string | null
errorComponent?: string | null
dir: string
extensions: string[]
plugins: NuxtPlugin[]

View File

@ -4,8 +4,8 @@ import type { Ref } from 'vue'
import { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, useRouter as vueUseRouter } from 'vue-router'
import { defineNuxtConfig } from '~~/../../../packages/nuxt/src'
import { useRouter } from '#imports'
import { isVue3 } from '#app'
import { useRouter } from '#imports'
interface TestResponse { message: string }

View File

@ -8,6 +8,7 @@
"moduleResolution": "Node",
"strict": false,
"allowJs": true,
"noEmit": true,
"noUnusedLocals": true,
"resolveJsonModule": true,
"types": [
@ -23,6 +24,12 @@
"#head": [
"./packages/nuxt/src/head/runtime/index"
],
"#internal/nitro": [
"./node_modules/nitropack/dist/runtime"
],
"#internal/nitro/utils": [
"./node_modules/nitropack/dist/runtime/utils"
]
}
},
"exclude": [