refactor(nuxt, kit): improve type strictness (#6685)

This commit is contained in:
Anthony Fu 2022-08-22 18:12:02 +08:00 committed by GitHub
parent cdc75373d4
commit e1e39b7e79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 139 additions and 98 deletions

View File

@ -11,7 +11,7 @@ export function isIgnored (pathname: string): boolean {
// Happens with CLI reloads // Happens with CLI reloads
if (!nuxt) { if (!nuxt) {
return null return false
} }
if (!nuxt._ignore) { if (!nuxt._ignore) {
@ -28,5 +28,5 @@ export function isIgnored (pathname: string): boolean {
if (relativePath.startsWith('..')) { if (relativePath.startsWith('..')) {
return false return false
} }
return relativePath && nuxt._ignore.ignores(relativePath) return !!(relativePath && nuxt._ignore.ignores(relativePath))
} }

View File

@ -85,10 +85,10 @@ export function requireModulePkg (id: string, opts: RequireModuleOptions = {}) {
/** Resolve the path of a module. */ /** Resolve the path of a module. */
export function resolveModule (id: string, opts: ResolveModuleOptions = {}) { export function resolveModule (id: string, opts: ResolveModuleOptions = {}) {
return normalize(_require.resolve(id, { return normalize(_require.resolve(id, {
paths: [].concat( paths: ([] as string[]).concat(
// @ts-ignore // @ts-ignore
global.__NUXT_PREPATHS__, global.__NUXT_PREPATHS__,
opts.paths, opts.paths || [],
process.cwd(), process.cwd(),
// @ts-ignore // @ts-ignore
global.__NUXT_PATHS__ global.__NUXT_PATHS__
@ -100,8 +100,8 @@ export function resolveModule (id: string, opts: ResolveModuleOptions = {}) {
export function tryResolveModule (path: string, opts: ResolveModuleOptions = {}): string | null { export function tryResolveModule (path: string, opts: ResolveModuleOptions = {}): string | null {
try { try {
return resolveModule(path, opts) return resolveModule(path, opts)
} catch (error) { } catch (error: any) {
if (error.code !== 'MODULE_NOT_FOUND') { if (error?.code !== 'MODULE_NOT_FOUND') {
throw error throw error
} }
} }

View File

@ -15,11 +15,11 @@ export function parallel<T, R> (
return Promise.all(tasks.map(fn)) return Promise.all(tasks.map(fn))
} }
export function chainFn (base, fn) { export function chainFn<Fn> (base: Fn, fn: Fn): Fn {
if (typeof fn !== 'function') { if (typeof fn !== 'function') {
return base return base
} }
return function (...args) { return function (this: any, ...args: any[]) {
if (typeof base !== 'function') { if (typeof base !== 'function') {
return fn.apply(this, args) return fn.apply(this, args)
} }
@ -38,5 +38,5 @@ export function chainFn (base, fn) {
return baseResult return baseResult
} }
return fnResult return fnResult
} } as unknown as Fn
} }

View File

@ -6,10 +6,10 @@ import { useNuxt } from './context'
import { logger } from './logger' import { logger } from './logger'
import { addTemplate } from './template' import { addTemplate } from './template'
export function addLayout (tmpl: NuxtTemplate, name?: string) { export function addLayout (this: any, template: NuxtTemplate, name?: string) {
const nuxt = useNuxt() const nuxt = useNuxt()
const { filename, src } = addTemplate(tmpl) const { filename, src } = addTemplate(template)
const layoutName = kebabCase(name || parse(tmpl.filename).name).replace(/["']/g, '') const layoutName = kebabCase(name || parse(filename).name).replace(/["']/g, '')
if (isNuxt2(nuxt)) { if (isNuxt2(nuxt)) {
// Nuxt 2 adds layouts in options // Nuxt 2 adds layouts in options

View File

@ -7,7 +7,7 @@ import { NuxtConfigSchema } from '@nuxt/schema'
export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {} export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> { export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
const { config: nuxtConfig, configFile, layers, cwd } = await loadConfig<NuxtConfig>({ const result = await loadConfig<NuxtConfig>({
name: 'nuxt', name: 'nuxt',
configFile: 'nuxt.config', configFile: 'nuxt.config',
rcFile: '.nuxtrc', rcFile: '.nuxtrc',
@ -15,6 +15,8 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
globalRc: true, globalRc: true,
...opts ...opts
}) })
const { configFile, layers = [], cwd } = result
const nuxtConfig = result.config!
// Fill config // Fill config
nuxtConfig.rootDir = nuxtConfig.rootDir || cwd nuxtConfig.rootDir = nuxtConfig.rootDir || cwd
@ -25,7 +27,7 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
for (const layer of layers) { for (const layer of layers) {
layer.config = layer.config || {} layer.config = layer.config || {}
layer.config.rootDir = layer.config.rootDir ?? layer.cwd layer.config.rootDir = layer.config.rootDir ?? layer.cwd
layer.config.srcDir = resolve(layer.config.rootDir, layer.config.srcDir) layer.config.srcDir = resolve(layer.config.rootDir!, layer.config.srcDir!)
} }
// Filter layers // Filter layers

View File

@ -29,7 +29,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
const nearestNuxtPkg = await Promise.all(['nuxt3', 'nuxt', 'nuxt-edge'] const nearestNuxtPkg = await Promise.all(['nuxt3', 'nuxt', 'nuxt-edge']
.map(pkg => resolvePackageJSON(pkg, { url: opts.cwd }).catch(() => null))) .map(pkg => resolvePackageJSON(pkg, { url: opts.cwd }).catch(() => null)))
.then(r => r.filter(Boolean).sort((a, b) => b.length - a.length)[0]) .then(r => (r.filter(Boolean) as string[]).sort((a, b) => b.length - a.length)[0])
if (!nearestNuxtPkg) { if (!nearestNuxtPkg) {
throw new Error(`Cannot find any nuxt version from ${opts.cwd}`) throw new Error(`Cannot find any nuxt version from ${opts.cwd}`)
} }

View File

@ -1,5 +1,5 @@
import { relative } from 'pathe' import { relative } from 'pathe'
import type { Nuxt, NuxtPluginTemplate, NuxtTemplate, ModuleContainer } from '@nuxt/schema' import type { Nuxt, ModuleContainer } from '@nuxt/schema'
import { chainFn } from '../internal/task' import { chainFn } from '../internal/task'
import { addTemplate } from '../template' import { addTemplate } from '../template'
import { addLayout } from '../layout' import { addLayout } from '../layout'
@ -12,11 +12,10 @@ import { installModule } from './install'
const MODULE_CONTAINER_KEY = '__module_container__' const MODULE_CONTAINER_KEY = '__module_container__'
export function useModuleContainer (nuxt: Nuxt = useNuxt()): ModuleContainer { export function useModuleContainer (nuxt: Nuxt = useNuxt()): ModuleContainer {
if (nuxt[MODULE_CONTAINER_KEY]) { // @ts-ignore
return nuxt[MODULE_CONTAINER_KEY] if (nuxt[MODULE_CONTAINER_KEY]) { return nuxt[MODULE_CONTAINER_KEY] }
}
async function requireModule (moduleOpts) { async function requireModule (moduleOpts: any) {
let src, inlineOptions let src, inlineOptions
if (typeof moduleOpts === 'string') { if (typeof moduleOpts === 'string') {
src = moduleOpts src = moduleOpts
@ -35,7 +34,7 @@ export function useModuleContainer (nuxt: Nuxt = useNuxt()): ModuleContainer {
await installModule(src, inlineOptions) await installModule(src, inlineOptions)
} }
nuxt[MODULE_CONTAINER_KEY] = <ModuleContainer>{ const container: ModuleContainer = {
nuxt, nuxt,
options: nuxt.options, options: nuxt.options,
@ -47,7 +46,7 @@ export function useModuleContainer (nuxt: Nuxt = useNuxt()): ModuleContainer {
addServerMiddleware, addServerMiddleware,
addTemplate (template: string | NuxtTemplate) { addTemplate (template) {
if (typeof template === 'string') { if (typeof template === 'string') {
template = { src: template } template = { src: template }
} }
@ -57,15 +56,15 @@ export function useModuleContainer (nuxt: Nuxt = useNuxt()): ModuleContainer {
return addTemplate(template) return addTemplate(template)
}, },
addPlugin (pluginTemplate: NuxtPluginTemplate): NuxtPluginTemplate { addPlugin (pluginTemplate) {
return addPluginTemplate(pluginTemplate) return addPluginTemplate(pluginTemplate)
}, },
addLayout (tmpl: NuxtTemplate, name?: string) { addLayout (tmpl, name) {
return addLayout(tmpl, name) return addLayout(tmpl, name)
}, },
addErrorLayout (dst: string) { addErrorLayout (dst) {
const relativeBuildDir = relative(nuxt.options.rootDir, nuxt.options.buildDir) const relativeBuildDir = relative(nuxt.options.rootDir, nuxt.options.buildDir)
nuxt.options.ErrorPage = `~/${relativeBuildDir}/${dst}` nuxt.options.ErrorPage = `~/${relativeBuildDir}/${dst}`
}, },
@ -93,5 +92,9 @@ export function useModuleContainer (nuxt: Nuxt = useNuxt()): ModuleContainer {
} }
} }
// @ts-ignore
nuxt[MODULE_CONTAINER_KEY] = container
// @ts-ignore
return nuxt[MODULE_CONTAINER_KEY] return nuxt[MODULE_CONTAINER_KEY]
} }

View File

@ -2,7 +2,7 @@ import { promises as fsp } from 'node:fs'
import defu from 'defu' import defu from 'defu'
import { applyDefaults } from 'untyped' import { applyDefaults } from 'untyped'
import { dirname } from 'pathe' import { dirname } from 'pathe'
import type { Nuxt, NuxtTemplate, NuxtModule, ModuleOptions, ModuleDefinition } from '@nuxt/schema' import type { Nuxt, NuxtModule, ModuleOptions, ModuleDefinition, NuxtOptions, ResolvedNuxtTemplate } from '@nuxt/schema'
import { logger } from '../logger' import { logger } from '../logger'
import { useNuxt, nuxtCtx, tryUseNuxt } from '../context' import { useNuxt, nuxtCtx, tryUseNuxt } from '../context'
import { isNuxt2, checkNuxtCompatibility } from '../compatibility' import { isNuxt2, checkNuxtCompatibility } from '../compatibility'
@ -31,9 +31,9 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema // Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
function getOptions (inlineOptions?: OptionsT, nuxt: Nuxt = useNuxt()) { function getOptions (inlineOptions?: OptionsT, nuxt: Nuxt = useNuxt()) {
const configKey = definition.meta.configKey || definition.meta.name const configKey = definition.meta!.configKey || definition.meta!.name!
const _defaults = definition.defaults instanceof Function ? definition.defaults(nuxt) : definition.defaults const _defaults = definition.defaults instanceof Function ? definition.defaults(nuxt) : definition.defaults
let _options = defu(inlineOptions, nuxt.options[configKey], _defaults) as OptionsT let _options = defu(inlineOptions, nuxt.options[configKey as keyof NuxtOptions], _defaults) as OptionsT
if (definition.schema) { if (definition.schema) {
_options = applyDefaults(definition.schema, _options) as OptionsT _options = applyDefaults(definition.schema, _options) as OptionsT
} }
@ -41,13 +41,13 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
} }
// Module format is always a simple function // Module format is always a simple function
async function normalizedModule (inlineOptions: OptionsT, nuxt: Nuxt) { async function normalizedModule (this: any, inlineOptions: OptionsT, nuxt: Nuxt) {
if (!nuxt) { if (!nuxt) {
nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */ nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */
} }
// Avoid duplicate installs // Avoid duplicate installs
const uniqueKey = definition.meta.name || definition.meta.configKey const uniqueKey = definition.meta!.name || definition.meta!.configKey
if (uniqueKey) { if (uniqueKey) {
nuxt.options._requiredModules = nuxt.options._requiredModules || {} nuxt.options._requiredModules = nuxt.options._requiredModules || {}
if (nuxt.options._requiredModules[uniqueKey]) { if (nuxt.options._requiredModules[uniqueKey]) {
@ -58,10 +58,10 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
} }
// Check compatibility constraints // Check compatibility constraints
if (definition.meta.compatibility) { if (definition.meta!.compatibility) {
const issues = await checkNuxtCompatibility(definition.meta.compatibility, nuxt) const issues = await checkNuxtCompatibility(definition.meta!.compatibility, nuxt)
if (issues.length) { if (issues.length) {
logger.warn(`Module \`${definition.meta.name}\` is disabled due to incompatibility issues:\n${issues.toString()}`) logger.warn(`Module \`${definition.meta!.name}\` is disabled due to incompatibility issues:\n${issues.toString()}`)
return return
} }
} }
@ -78,7 +78,7 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
} }
// Call setup // Call setup
await definition.setup?.call(null, _options, nuxt) await definition.setup?.call(null as any, _options, nuxt)
} }
// Define getters for options and meta // Define getters for options and meta
@ -92,11 +92,13 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
const NUXT2_SHIMS_KEY = '__nuxt2_shims_key__' const NUXT2_SHIMS_KEY = '__nuxt2_shims_key__'
function nuxt2Shims (nuxt: Nuxt) { function nuxt2Shims (nuxt: Nuxt) {
// Avoid duplicate install and only apply to Nuxt2 // Avoid duplicate install and only apply to Nuxt2
// @ts-ignore
if (!isNuxt2(nuxt) || nuxt[NUXT2_SHIMS_KEY]) { return } if (!isNuxt2(nuxt) || nuxt[NUXT2_SHIMS_KEY]) { return }
// @ts-ignore
nuxt[NUXT2_SHIMS_KEY] = true nuxt[NUXT2_SHIMS_KEY] = true
// Allow using nuxt.hooks // Allow using nuxt.hooks
// @ts-ignore Nuxt 2 extends hookable // @ts-expect-error Nuxt 2 extends hookable
nuxt.hooks = nuxt nuxt.hooks = nuxt
// Allow using useNuxt() // Allow using useNuxt()
@ -106,7 +108,7 @@ function nuxt2Shims (nuxt: Nuxt) {
} }
// Support virtual templates with getContents() by writing them to .nuxt directory // Support virtual templates with getContents() by writing them to .nuxt directory
let virtualTemplates: NuxtTemplate[] let virtualTemplates: ResolvedNuxtTemplate[]
nuxt.hook('builder:prepared', (_builder, buildOptions) => { nuxt.hook('builder:prepared', (_builder, buildOptions) => {
virtualTemplates = buildOptions.templates.filter(t => t.getContents) virtualTemplates = buildOptions.templates.filter(t => t.getContents)
for (const template of virtualTemplates) { for (const template of virtualTemplates) {

View File

@ -10,7 +10,12 @@ export async function installModule (moduleToInstall: string | NuxtModule, _inli
const { nuxtModule, inlineOptions } = await normalizeModule(moduleToInstall, _inlineOptions) const { nuxtModule, inlineOptions } = await normalizeModule(moduleToInstall, _inlineOptions)
// Call module // Call module
await nuxtModule.call(useModuleContainer(), inlineOptions, nuxt) await nuxtModule.call(
// Provide this context for backwards compatibility with Nuxt 2
useModuleContainer() as any,
inlineOptions,
nuxt
)
nuxt.options._installedModules = nuxt.options._installedModules || [] nuxt.options._installedModules = nuxt.options._installedModules || []
nuxt.options._installedModules.push({ nuxt.options._installedModules.push({

View File

@ -73,7 +73,7 @@ export function addPluginTemplate (plugin: NuxtPluginTemplate | string, opts: Ad
const normalizedPlugin: NuxtPlugin = typeof plugin === 'string' const normalizedPlugin: NuxtPlugin = typeof plugin === 'string'
? { src: plugin } ? { src: plugin }
// Update plugin src to template destination // Update plugin src to template destination
: { ...plugin, src: addTemplate(plugin).dst } : { ...plugin, src: addTemplate(plugin).dst! }
return addPlugin(normalizedPlugin, opts) return addPlugin(normalizedPlugin, opts)
} }

View File

@ -115,7 +115,7 @@ export function resolveAlias (path: string, alias?: Record<string, string>): str
} }
export interface Resolver { export interface Resolver {
resolve(...path): string resolve(...path: string[]): string
resolvePath(path: string, opts?: ResolvePathOptions): Promise<string> resolvePath(path: string, opts?: ResolvePathOptions): Promise<string>
} }

View File

@ -1,16 +1,16 @@
import { existsSync } from 'node:fs' import { existsSync } from 'node:fs'
import { basename, parse, resolve } from 'pathe' import { basename, parse, resolve } from 'pathe'
import hash from 'hash-sum' import hash from 'hash-sum'
import type { NuxtTemplate } from '@nuxt/schema' import type { NuxtTemplate, ResolvedNuxtTemplate } from '@nuxt/schema'
import { useNuxt } from './context' import { useNuxt } from './context'
/** /**
* Renders given template using lodash template during build into the project buildDir * Renders given template using lodash template during build into the project buildDir
*/ */
export function addTemplate (_template: NuxtTemplate | string) { export function addTemplate (_template: NuxtTemplate<any> | string) {
const nuxt = useNuxt() const nuxt = useNuxt()
// Noprmalize template // Normalize template
const template = normalizeTemplate(_template) const template = normalizeTemplate(_template)
// Remove any existing template with the same filename // Remove any existing template with the same filename
@ -26,7 +26,7 @@ export function addTemplate (_template: NuxtTemplate | string) {
/** /**
* Normalize a nuxt template object * Normalize a nuxt template object
*/ */
export function normalizeTemplate (template: NuxtTemplate | string): NuxtTemplate { export function normalizeTemplate (template: NuxtTemplate<any> | string): ResolvedNuxtTemplate<any> {
if (!template) { if (!template) {
throw new Error('Invalid template: ' + JSON.stringify(template)) throw new Error('Invalid template: ' + JSON.stringify(template))
} }
@ -69,5 +69,5 @@ export function normalizeTemplate (template: NuxtTemplate | string): NuxtTemplat
template.dst = resolve(nuxt.options.buildDir, template.filename) template.dst = resolve(nuxt.options.buildDir, template.filename)
} }
return template return template as ResolvedNuxtTemplate<any>
} }

View File

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

View File

@ -34,7 +34,7 @@ export const clearError = async (options: { redirect?: string } = {}) => {
error.value = null error.value = null
} }
export const isNuxtError = (err?: string | object): err is NuxtError => err && typeof err === 'object' && ('__nuxt_error' in err) export const isNuxtError = (err?: string | object): err is NuxtError => !!(err && typeof err === 'object' && ('__nuxt_error' in err))
export const createError = (err: string | Partial<NuxtError>): NuxtError => { export const createError = (err: string | Partial<NuxtError>): NuxtError => {
const _err: NuxtError = _createError(err) const _err: NuxtError = _createError(err)

View File

@ -80,7 +80,7 @@ interface _NuxtApp {
message: string message: string
description: string description: string
data?: any data?: any
} } | null
[key: string]: any [key: string]: any
} }

View File

@ -48,7 +48,9 @@ export const TransformPlugin = createUnplugin(({ ctx, options, sourcemap }: {ctx
if (s.hasChanged()) { if (s.hasChanged()) {
return { return {
code: s.toString(), code: s.toString(),
map: sourcemap && s.generateMap({ source: id, includeContent: true }) map: sourcemap
? s.generateMap({ source: id, includeContent: true })
: undefined
} }
} }
} }

View File

@ -94,7 +94,9 @@ export const loaderPlugin = createUnplugin((options: LoaderOptions) => {
if (s.hasChanged()) { if (s.hasChanged()) {
return { return {
code: s.toString(), code: s.toString(),
map: options.sourcemap && s.generateMap({ source: id, includeContent: true }) map: options.sourcemap
? s.generateMap({ source: id, includeContent: true })
: undefined
} }
} }
} }

View File

@ -9,7 +9,7 @@ import { TreeShakeTemplatePlugin } from './tree-shake'
const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string' const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string'
const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch (_e) { return false } } const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch (_e) { return false } }
function compareDirByPathLength ({ path: pathA }, { path: pathB }) { function compareDirByPathLength ({ path: pathA }: { path: string}, { path: pathB }: { path: string}) {
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
} }
@ -26,7 +26,7 @@ export default defineNuxtModule<ComponentsOptions>({
dirs: [] dirs: []
}, },
setup (componentOptions, nuxt) { setup (componentOptions, nuxt) {
let componentDirs = [] let componentDirs: ComponentsDir[] = []
const context = { const context = {
components: [] as Component[] components: [] as Component[]
} }
@ -37,7 +37,7 @@ export default defineNuxtModule<ComponentsOptions>({
: context.components : context.components
} }
const normalizeDirs = (dir: any, cwd: string) => { const normalizeDirs = (dir: any, cwd: string): ComponentsDir[] => {
if (Array.isArray(dir)) { if (Array.isArray(dir)) {
return dir.map(dir => normalizeDirs(dir, cwd)).flat().sort(compareDirByPathLength) return dir.map(dir => normalizeDirs(dir, cwd)).flat().sort(compareDirByPathLength)
} }
@ -48,14 +48,14 @@ export default defineNuxtModule<ComponentsOptions>({
] ]
} }
if (typeof dir === 'string') { if (typeof dir === 'string') {
return { return [
path: resolve(cwd, resolveAlias(dir)) { path: resolve(cwd, resolveAlias(dir)) }
} ]
} }
if (!dir) { if (!dir) {
return [] return []
} }
const dirs = (dir.dirs || [dir]).map(dir => typeof dir === 'string' ? { path: dir } : dir).filter(_dir => _dir.path) const dirs: ComponentsDir[] = (dir.dirs || [dir]).map((dir: any): ComponentsDir => typeof dir === 'string' ? { path: dir } : dir).filter((_dir: ComponentsDir) => _dir.path)
return dirs.map(_dir => ({ return dirs.map(_dir => ({
..._dir, ..._dir,
path: resolve(cwd, resolveAlias(_dir.path)) path: resolve(cwd, resolveAlias(_dir.path))
@ -113,7 +113,7 @@ export default defineNuxtModule<ComponentsOptions>({
// components.d.ts // components.d.ts
addTemplate({ ...componentsTypeTemplate, options: { getComponents } }) addTemplate({ ...componentsTypeTemplate, options: { getComponents } })
// components.plugin.mjs // components.plugin.mjs
addPluginTemplate({ ...componentsPluginTemplate, options: { getComponents } }) addPluginTemplate({ ...componentsPluginTemplate, options: { getComponents } } as any)
// components.server.mjs // components.server.mjs
addTemplate({ ...componentsTemplate, filename: 'components.server.mjs', options: { getComponents, mode: 'server' } }) addTemplate({ ...componentsTemplate, filename: 'components.server.mjs', options: { getComponents, mode: 'server' } })
// components.client.mjs // components.client.mjs
@ -121,12 +121,12 @@ export default defineNuxtModule<ComponentsOptions>({
nuxt.hook('vite:extendConfig', (config, { isClient }) => { nuxt.hook('vite:extendConfig', (config, { isClient }) => {
const mode = isClient ? 'client' : 'server' const mode = isClient ? 'client' : 'server'
config.resolve.alias['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`) ;(config.resolve!.alias as any)['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`)
}) })
nuxt.hook('webpack:config', (configs) => { nuxt.hook('webpack:config', (configs) => {
for (const config of configs) { for (const config of configs) {
const mode = config.name === 'server' ? 'server' : 'client' const mode = config.name === 'server' ? 'server' : 'client'
config.resolve.alias['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`) ;(config.resolve!.alias as any)['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`)
} }
}) })

View File

@ -58,12 +58,12 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
* *
* @example third-components/index.vue -> third-component * @example third-components/index.vue -> third-component
* if not take the filename * if not take the filename
* @example thid-components/Awesome.vue -> Awesome * @example third-components/Awesome.vue -> Awesome
*/ */
let fileName = basename(filePath, extname(filePath)) let fileName = basename(filePath, extname(filePath))
const global = /\.(global)$/.test(fileName) || dir.global const global = /\.(global)$/.test(fileName) || dir.global
const mode = fileName.match(/(?<=\.)(client|server)(\.global)?$/)?.[1] as 'client' | 'server' || 'all' const mode = (fileName.match(/(?<=\.)(client|server)(\.global)?$/)?.[1] || 'all') as 'client' | 'server' | 'all'
fileName = fileName.replace(/(\.(client|server))?(\.global)?$/, '') fileName = fileName.replace(/(\.(client|server))?(\.global)?$/, '')
if (fileName.toLowerCase() === 'index') { if (fileName.toLowerCase() === 'index') {

View File

@ -1,5 +1,5 @@
import { isAbsolute, relative } from 'pathe' import { isAbsolute, relative } from 'pathe'
import type { Component, Nuxt } from '@nuxt/schema' import type { Component, Nuxt, NuxtPluginTemplate, NuxtTemplate } from '@nuxt/schema'
import { genDynamicImport, genExport, genObjectFromRawEntries } from 'knitwork' import { genDynamicImport, genExport, genObjectFromRawEntries } from 'knitwork'
export interface ComponentsTemplateContext { export interface ComponentsTemplateContext {
@ -25,9 +25,9 @@ const createImportMagicComments = (options: ImportMagicCommentsOptions) => {
].filter(Boolean).join(', ') ].filter(Boolean).join(', ')
} }
export const componentsPluginTemplate = { export const componentsPluginTemplate: NuxtPluginTemplate<ComponentsTemplateContext> = {
filename: 'components.plugin.mjs', filename: 'components.plugin.mjs',
getContents ({ options }: ComponentsTemplateContext) { getContents ({ options }) {
const globalComponents = options.getComponents().filter(c => c.global === true) const globalComponents = options.getComponents().filter(c => c.global === true)
return `import { defineAsyncComponent } from 'vue' return `import { defineAsyncComponent } from 'vue'
@ -50,9 +50,9 @@ export default defineNuxtPlugin(nuxtApp => {
} }
} }
export const componentsTemplate = { export const componentsTemplate: NuxtTemplate<ComponentsTemplateContext> = {
// components.[server|client].mjs' // components.[server|client].mjs'
getContents ({ options }: ComponentsTemplateContext) { getContents ({ options }) {
return [ return [
'import { defineAsyncComponent } from \'vue\'', 'import { defineAsyncComponent } from \'vue\'',
...options.getComponents(options.mode).flatMap((c) => { ...options.getComponents(options.mode).flatMap((c) => {
@ -69,9 +69,9 @@ export const componentsTemplate = {
} }
} }
export const componentsTypeTemplate = { export const componentsTypeTemplate: NuxtTemplate<ComponentsTemplateContext> = {
filename: 'components.d.ts', filename: 'components.d.ts',
getContents: ({ options, nuxt }: ComponentsTemplateContext) => { getContents: ({ options, nuxt }) => {
const buildDir = nuxt.options.buildDir const buildDir = nuxt.options.buildDir
const componentTypes = options.getComponents().map(c => [ const componentTypes = options.getComponents().map(c => [
c.pascalName, c.pascalName,

View File

@ -31,7 +31,7 @@ export const TreeShakeTemplatePlugin = createUnplugin((options: TreeShakeTemplat
regexpMap.set(components, new RegExp(`(${clientOnlyComponents.join('|')})`, 'g')) regexpMap.set(components, new RegExp(`(${clientOnlyComponents.join('|')})`, 'g'))
} }
const COMPONENTS_RE = regexpMap.get(components) const COMPONENTS_RE = regexpMap.get(components)!
const s = new MagicString(code) const s = new MagicString(code)
// Do not render client-only slots on SSR, but preserve attributes // Do not render client-only slots on SSR, but preserve attributes
@ -40,7 +40,9 @@ export const TreeShakeTemplatePlugin = createUnplugin((options: TreeShakeTemplat
if (s.hasChanged()) { if (s.hasChanged()) {
return { return {
code: s.toString(), code: s.toString(),
map: options.sourcemap && s.generateMap({ source: id, includeContent: true }) map: options.sourcemap
? s.generateMap({ source: id, includeContent: true })
: undefined
} }
} }
} }

View File

@ -17,8 +17,8 @@ export const vueAppPatterns = (nuxt: Nuxt) => [
[/^(nuxt3|nuxt)$/, '`nuxt3`/`nuxt` cannot be imported directly. Instead, import runtime Nuxt composables from `#app` or `#imports`.'], [/^(nuxt3|nuxt)$/, '`nuxt3`/`nuxt` cannot be imported directly. Instead, import runtime Nuxt composables from `#app` or `#imports`.'],
[/^((|~|~~|@|@@)\/)?nuxt\.config(\.|$)/, 'Importing directly from a `nuxt.config` file is not allowed. Instead, use runtime config or a module.'], [/^((|~|~~|@|@@)\/)?nuxt\.config(\.|$)/, 'Importing directly from a `nuxt.config` file is not allowed. Instead, use runtime config or a module.'],
[/(^|node_modules\/)@vue\/composition-api/], [/(^|node_modules\/)@vue\/composition-api/],
...nuxt.options.modules.filter(m => typeof m === 'string').map((m: string) => ...nuxt.options.modules.filter(m => typeof m === 'string').map((m: any) =>
[new RegExp(`^${escapeRE(m)}$`), 'Importing directly from module entry points is not allowed.']), [new RegExp(`^${escapeRE(m as string)}$`), 'Importing directly from module entry points is not allowed.']),
...[/(^|node_modules\/)@nuxt\/kit/, /^nitropack/] ...[/(^|node_modules\/)@nuxt\/kit/, /^nitropack/]
.map(i => [i, 'This module cannot be imported in the Vue part of your app.']), .map(i => [i, 'This module cannot be imported in the Vue part of your app.']),
[new RegExp(escapeRE(resolve(nuxt.options.srcDir, (nuxt.options.dir as any).server || 'server')) + '\\/(api|routes|middleware|plugins)\\/'), 'Importing from server is not allowed in the Vue part of your app.'] [new RegExp(escapeRE(resolve(nuxt.options.srcDir, (nuxt.options.dir as any).server || 'server')) + '\\/(api|routes|middleware|plugins)\\/'), 'Importing from server is not allowed in the Vue part of your app.']
@ -31,10 +31,11 @@ export const ImportProtectionPlugin = createUnplugin(function (options: ImportPr
name: 'nuxt:import-protection', name: 'nuxt:import-protection',
enforce: 'pre', enforce: 'pre',
resolveId (id, importer) { resolveId (id, importer) {
if (!importer) { return }
if (importersToExclude.some(p => typeof p === 'string' ? importer === p : p.test(importer))) { return } if (importersToExclude.some(p => typeof p === 'string' ? importer === p : p.test(importer))) { return }
const invalidImports = options.patterns.filter(([pattern]) => pattern instanceof RegExp ? pattern.test(id) : pattern === id) const invalidImports = options.patterns.filter(([pattern]) => pattern instanceof RegExp ? pattern.test(id) : pattern === id)
let matched: boolean let matched = false
for (const match of invalidImports) { for (const match of invalidImports) {
cache[id] = cache[id] || new Map() cache[id] = cache[id] || new Map()
const [pattern, warning] = match const [pattern, warning] = match

View File

@ -35,13 +35,15 @@ export const TreeShakePlugin = createUnplugin((options: TreeShakePluginOptions)
const s = new MagicString(code) const s = new MagicString(code)
const strippedCode = stripLiteral(code) const strippedCode = stripLiteral(code)
for (const match of strippedCode.matchAll(COMPOSABLE_RE) || []) { for (const match of strippedCode.matchAll(COMPOSABLE_RE) || []) {
s.overwrite(match.index, match.index + match[0].length, `${match[1]} /*#__PURE__*/ false && ${match[2]}`) s.overwrite(match.index!, match.index! + match[0].length, `${match[1]} /*#__PURE__*/ false && ${match[2]}`)
} }
if (s.hasChanged()) { if (s.hasChanged()) {
return { return {
code: s.toString(), code: s.toString(),
map: options.sourcemap && s.generateMap({ source: id, includeContent: true }) map: options.sourcemap
? s.generateMap({ source: id, includeContent: true })
: undefined
} }
} }
} }

View File

@ -39,7 +39,7 @@ export default defineNuxtPlugin((nuxtApp) => {
if (!vm) { return } if (!vm) { return }
onBeforeUnmount(() => { onBeforeUnmount(() => {
head.removeHeadObjs(headObj) head.removeHeadObjs(headObj as any)
head.updateDOM() head.updateDOM()
}) })
} }

View File

@ -135,7 +135,7 @@ export default defineNuxtModule({
// Check for router options // Check for router options
const routerOptionsFiles = (await Promise.all(nuxt.options._layers.map( const routerOptionsFiles = (await Promise.all(nuxt.options._layers.map(
async layer => await findPath(resolve(layer.config.srcDir, 'app/router.options')) async layer => await findPath(resolve(layer.config.srcDir, 'app/router.options'))
))).filter(Boolean) ))).filter(Boolean) as string[]
const configRouterOptions = genObjectFromRawEntries(Object.entries(nuxt.options.router.options) const configRouterOptions = genObjectFromRawEntries(Object.entries(nuxt.options.router.options)
.map(([key, value]) => [key, genString(value as string)])) .map(([key, value]) => [key, genString(value as string)]))

View File

@ -20,8 +20,8 @@ describe('auto-imports:transform', () => {
const transformPlugin = TransformPlugin.raw({ ctx, options: { transform: { exclude: [/node_modules/] } } }, { framework: 'rollup' }) const transformPlugin = TransformPlugin.raw({ ctx, options: { transform: { exclude: [/node_modules/] } } }, { framework: 'rollup' })
const transform = async (source: string) => { const transform = async (source: string) => {
const { code } = await transformPlugin.transform.call({ error: null, warn: null }, source, '') || { code: null } const result = await transformPlugin.transform!.call({ error: null, warn: null } as any, source, '')
return code return typeof result === 'string' ? result : result?.code
} }
it('should correct inject', async () => { it('should correct inject', async () => {
@ -29,13 +29,13 @@ describe('auto-imports:transform', () => {
}) })
it('should ignore existing imported', async () => { it('should ignore existing imported', async () => {
expect(await transform('import { ref } from "foo"; const a = ref(0)')).to.equal(null) expect(await transform('import { ref } from "foo"; const a = ref(0)')).to.equal(undefined)
expect(await transform('import { computed as ref } from "foo"; const a = ref(0)')).to.equal(null) expect(await transform('import { computed as ref } from "foo"; const a = ref(0)')).to.equal(undefined)
expect(await transform('import ref from "foo"; const a = ref(0)')).to.equal(null) expect(await transform('import ref from "foo"; const a = ref(0)')).to.equal(undefined)
expect(await transform('import { z as ref } from "foo"; const a = ref(0)')).to.equal(null) expect(await transform('import { z as ref } from "foo"; const a = ref(0)')).to.equal(undefined)
expect(await transform('let ref = () => {}; const a = ref(0)')).to.equal(null) expect(await transform('let ref = () => {}; const a = ref(0)')).to.equal(undefined)
expect(await transform('let { ref } = Vue; const a = ref(0)')).to.equal(null) expect(await transform('let { ref } = Vue; const a = ref(0)')).to.equal(undefined)
expect(await transform('let [\ncomputed,\nref\n] = Vue; const a = ref(0); const b = ref(0)')).to.equal(null) expect(await transform('let [\ncomputed,\nref\n] = Vue; const a = ref(0); const b = ref(0)')).to.equal(undefined)
}) })
it('should ignore comments', async () => { it('should ignore comments', async () => {
@ -48,7 +48,7 @@ describe('auto-imports:transform', () => {
}) })
it('should exclude files from transform', async () => { it('should exclude files from transform', async () => {
expect(await transform('excluded')).toEqual(null) expect(await transform('excluded')).toEqual(undefined)
}) })
}) })
@ -65,7 +65,7 @@ describe('auto-imports:nuxt', () => {
continue continue
} }
it(`should register ${name} globally`, () => { it(`should register ${name} globally`, () => {
expect(defaultPresets.find(a => a.from === '#app').imports).to.include(name) expect(defaultPresets.find(a => a.from === '#app')!.imports).to.include(name)
}) })
} }
} catch (e) { } catch (e) {
@ -176,7 +176,7 @@ describe('auto-imports:vue', () => {
continue continue
} }
it(`should register ${name} globally`, () => { it(`should register ${name} globally`, () => {
expect(defaultPresets.find(a => a.from === 'vue').imports).toContain(name) expect(defaultPresets.find(a => a.from === 'vue')!.imports).toContain(name)
}) })
} }
}) })

View File

@ -304,7 +304,7 @@ describe('pages:generateRouteKey', () => {
const tests = [ const tests = [
{ description: 'should handle overrides', override: 'key', route: getRouteProps(), output: 'key' }, { description: 'should handle overrides', override: 'key', route: getRouteProps(), output: 'key' },
{ description: 'should handle overrides', override: route => route.meta.key as string, route: getRouteProps(), output: 'route-meta-key' }, { description: 'should handle overrides', override: (route: any) => route.meta.key as string, route: getRouteProps(), output: 'route-meta-key' },
{ description: 'should handle overrides', override: false as any, route: getRouteProps(), output: false }, { description: 'should handle overrides', override: false as any, route: getRouteProps(), output: false },
{ {
description: 'should key dynamic routes without keys', description: 'should key dynamic routes without keys',

View File

@ -4,7 +4,7 @@ import { expect, it, vi } from 'vitest'
import { scanComponents } from '../src/components/scan' import { scanComponents } from '../src/components/scan'
const fixtureDir = resolve(__dirname, 'fixture') const fixtureDir = resolve(__dirname, 'fixture')
const rFixture = (...p) => resolve(fixtureDir, ...p) const rFixture = (...p: string[]) => resolve(fixtureDir, ...p)
vi.mock('@nuxt/kit', () => ({ vi.mock('@nuxt/kit', () => ({
isIgnored: () => false isIgnored: () => false
@ -108,6 +108,7 @@ const srcDir = rFixture('.')
it('components:scanComponents', async () => { it('components:scanComponents', async () => {
const scannedComponents = await scanComponents(dirs, srcDir) const scannedComponents = await scanComponents(dirs, srcDir)
for (const c of scannedComponents) { for (const c of scannedComponents) {
// @ts-ignore
delete c.filePath delete c.filePath
} }
expect(scannedComponents).deep.eq(expectedComponents) expect(scannedComponents).deep.eq(expectedComponents)

View File

@ -9,7 +9,7 @@ import type { NuxtTemplate, Nuxt, NuxtApp } from './nuxt'
import type { Preset as ImportPreset, Import } from 'unimport' import type { Preset as ImportPreset, Import } from 'unimport'
import type { NuxtConfig, NuxtOptions } from './config' import type { NuxtConfig, NuxtOptions } from './config'
import type { Nitro, NitroConfig } from 'nitropack' import type { Nitro, NitroConfig } from 'nitropack'
import type { Component, ComponentsDir, ScanDir, ComponentsOptions } from './components' import type { Component, ComponentsOptions } from './components'
import { NuxtCompatibility, NuxtCompatibilityIssues } from '..' import { NuxtCompatibility, NuxtCompatibilityIssues } from '..'
@ -82,7 +82,7 @@ export interface NuxtHooks {
// Components // Components
'components:dirs': (dirs: ComponentsOptions['dirs']) => HookResult 'components:dirs': (dirs: ComponentsOptions['dirs']) => HookResult
'components:extend': (components: (Component | ComponentsDir | ScanDir)[]) => HookResult 'components:extend': (components: Component[]) => HookResult
// @nuxt/builder // @nuxt/builder
'build:before': 'build:before':

View File

@ -1,5 +1,5 @@
import { NuxtHooks } from './hooks' import { NuxtHooks } from './hooks'
import type { Nuxt, NuxtTemplate } from "./nuxt" import type { Nuxt, NuxtPluginTemplate, NuxtTemplate } from "./nuxt"
import type { NuxtCompatibility } from './compatibility' import type { NuxtCompatibility } from './compatibility'
export interface ModuleMeta { export interface ModuleMeta {
@ -58,6 +58,9 @@ export interface ModuleContainer {
/** Renders given template using lodash template during build into the project buildDir (`.nuxt`).*/ /** Renders given template using lodash template during build into the project buildDir (`.nuxt`).*/
addTemplate(template: string | NuxtTemplate): NuxtTemplate addTemplate(template: string | NuxtTemplate): NuxtTemplate
/** Registers a custom plugin. */
addPlugin(template: NuxtPluginTemplate): NuxtPluginTemplate
/** Registers a custom layout. If its name is 'error' it will override the default error layout. */ /** Registers a custom layout. If its name is 'error' it will override the default error layout. */
addLayout(tmpl: NuxtTemplate, name: string): any addLayout(tmpl: NuxtTemplate, name: string): any

View File

@ -34,15 +34,20 @@ export interface NuxtTemplate<Options = Record<string, any>> {
/** The target filename once the template is copied into the Nuxt buildDir */ /** The target filename once the template is copied into the Nuxt buildDir */
filename?: string filename?: string
/** An options object that will be accessible within the template via `<% options %>` */ /** An options object that will be accessible within the template via `<% options %>` */
options?: Record<string, any> options?: Options
/** 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 */
getContents?: (data: Record<string, any>) => string | Promise<string> getContents?: (data: Options) => string | Promise<string>
/** Write to filesystem */ /** Write to filesystem */
write?: boolean write?: boolean
} }
export interface ResolvedNuxtTemplate<Options = Record<string, any>> extends NuxtTemplate<Options> {
filename: string
dst: string
}
export interface NuxtPlugin { export interface NuxtPlugin {
/** @deprecated use mode */ /** @deprecated use mode */
ssr?: boolean ssr?: boolean
@ -63,5 +68,5 @@ export interface NuxtApp {
configs: string[] configs: string[]
} }
type _TemplatePlugin = Omit<NuxtPlugin, 'src'> & NuxtTemplate type _TemplatePlugin<Options> = Omit<NuxtPlugin, 'src'> & NuxtTemplate<Options>
export interface NuxtPluginTemplate extends _TemplatePlugin { } export interface NuxtPluginTemplate<Options = Record<string, any>> extends _TemplatePlugin<Options> { }