import { templateUtils } from '@nuxt/kit'
import type { Nuxt, NuxtApp, NuxtTemplate } from '@nuxt/schema'
import { genArrayFromRaw, genDynamicImport, genExport, genImport, genObjectFromRawEntries, genString, genSafeVariableName } from 'knitwork'
import { isAbsolute, join, relative } from 'pathe'
import { resolveSchema, generateTypes } from 'untyped'
import escapeRE from 'escape-string-regexp'
export interface TemplateContext {
nuxt: Nuxt
app: NuxtApp
}
export const vueShim = {
filename: 'types/vue-shim.d.ts',
getContents: () =>
[
'declare module \'*.vue\' {',
' import { DefineComponent } from \'@vue/runtime-core\'',
' const component: DefineComponent<{}, {}, any>',
' export default component',
'}'
].join('\n')
}
// TODO: Use an alias
export const appComponentTemplate = {
filename: 'app-component.mjs',
getContents: (ctx: TemplateContext) => genExport(ctx.app.mainComponent, ['default'])
}
// TODO: Use an alias
export const rootComponentTemplate = {
filename: 'root-component.mjs',
getContents: (ctx: TemplateContext) => genExport(ctx.app.rootComponent, ['default'])
}
// TODO: Use an alias
export const errorComponentTemplate = {
filename: 'error-component.mjs',
getContents: (ctx: TemplateContext) => genExport(ctx.app.errorComponent, ['default'])
}
export const cssTemplate = {
filename: 'css.mjs',
getContents: (ctx: TemplateContext) => ctx.nuxt.options.css.map(i => genImport(i)).join('\n')
}
export const clientPluginTemplate = {
filename: 'plugins/client.mjs',
getContents (ctx: TemplateContext) {
const clientPlugins = ctx.app.plugins.filter(p => !p.mode || p.mode !== 'server')
return [
templateUtils.importSources(clientPlugins.map(p => p.src)),
`export default ${genArrayFromRaw(clientPlugins.map(p => genSafeVariableName(p.src)))}`
].join('\n')
}
}
export const serverPluginTemplate = {
filename: 'plugins/server.mjs',
getContents (ctx: TemplateContext) {
const serverPlugins = ctx.app.plugins.filter(p => !p.mode || p.mode !== 'client')
return [
"import preload from '#app/plugins/preload.server'",
templateUtils.importSources(serverPlugins.map(p => p.src)),
`export default ${genArrayFromRaw([
'preload',
...serverPlugins.map(p => genSafeVariableName(p.src))
])}`
].join('\n')
}
}
export const appViewTemplate = {
filename: 'views/document.template.mjs',
write: true,
getContents () {
return `export default (params) => \`
\${params.HEAD}
\${params.BODY_PREPEND}
\${params.APP}
\`
`
}
}
export const pluginsDeclaration = {
filename: 'types/plugins.d.ts',
getContents: (ctx: TemplateContext) => {
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, ''))
return `// Generated by Nuxt'
import type { Plugin } from '#app'
type Decorate> = { [K in keyof T as K extends string ? \`$\${K}\` : never]: T[K] }
type InjectionType = A extends Plugin ? Decorate : unknown
type NuxtAppInjections = \n ${tsImports.map(p => `InjectionType`).join(' &\n ')}
declare module '#app' {
interface NuxtApp extends NuxtAppInjections { }
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties extends NuxtAppInjections { }
}
export { }
`
}
}
const adHocModules = ['router', 'pages', 'auto-imports', 'meta', 'components']
export const schemaTemplate = {
filename: 'types/schema.d.ts',
getContents: ({ nuxt }: TemplateContext) => {
const moduleInfo = nuxt.options._installedModules.map(m => ({
...m.meta || {},
importName: m.entryPath || m.meta?.name
})).filter(m => m.configKey && m.name && !adHocModules.includes(m.name))
return [
"import { NuxtModule } from '@nuxt/schema'",
"declare module '@nuxt/schema' {",
' interface NuxtConfig {',
...moduleInfo.filter(Boolean).map(meta =>
` [${genString(meta.configKey)}]?: typeof ${genDynamicImport(meta.importName, { wrapper: false })}.default extends NuxtModule ? Partial : Record`
),
' }',
generateTypes(resolveSchema(Object.fromEntries(Object.entries(nuxt.options.runtimeConfig).filter(([key]) => key !== 'public'))),
{
interfaceName: 'RuntimeConfig',
addExport: false,
addDefaults: false,
allowExtraKeys: false,
indentation: 2
}),
generateTypes(resolveSchema(nuxt.options.runtimeConfig.public),
{
interfaceName: 'PublicRuntimeConfig',
addExport: false,
addDefaults: false,
allowExtraKeys: false,
indentation: 2
}),
'}'
].join('\n')
}
}
// Add layouts template
export const layoutTemplate: NuxtTemplate = {
filename: 'layouts.mjs',
getContents ({ app }: TemplateContext) {
const layoutsObject = genObjectFromRawEntries(Object.values(app.layouts).map(({ name, file }) => {
return [name, `defineAsyncComponent(${genDynamicImport(file)})`]
}))
return [
'import { defineAsyncComponent } from \'vue\'',
`export default ${layoutsObject}`
].join('\n')
}
}
// Add middleware template
export const middlewareTemplate: NuxtTemplate = {
filename: 'middleware.mjs',
getContents ({ app }: TemplateContext) {
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)]))
return [
...globalMiddleware.map(mw => genImport(mw.path, genSafeVariableName(mw.name))),
`export const globalMiddleware = ${genArrayFromRaw(globalMiddleware.map(mw => genSafeVariableName(mw.name)))}`,
`export const namedMiddleware = ${namedMiddlewareObject}`
].join('\n')
}
}
export const clientConfigTemplate: NuxtTemplate = {
filename: 'nitro.client.mjs',
getContents: () => `
export const useRuntimeConfig = () => window?.__NUXT__?.config || {}
`
}
export const publicPathTemplate: NuxtTemplate = {
filename: 'paths.mjs',
getContents ({ nuxt }) {
return [
'import { joinURL } from \'ufo\'',
!nuxt.options.dev && 'import { useRuntimeConfig } from \'#internal/nitro\'',
nuxt.options.dev
? `const appConfig = ${JSON.stringify(nuxt.options.app)}`
: 'const appConfig = useRuntimeConfig().app',
'export const baseURL = () => appConfig.baseURL',
'export const buildAssetsDir = () => appConfig.buildAssetsDir',
'export const buildAssetsURL = (...path) => joinURL(publicAssetsURL(), buildAssetsDir(), ...path)',
'export const publicAssetsURL = (...path) => {',
' const publicBase = appConfig.cdnURL || appConfig.baseURL',
' return path.length ? joinURL(publicBase, ...path) : publicBase',
'}'
].filter(Boolean).join('\n')
}
}