import { templateUtils } from '@nuxt/kit'
import type { Nuxt, NuxtApp, NuxtTemplate } from '@nuxt/schema'
import { genArrayFromRaw, genDynamicImport, genExport, genImport, genObjectFromRawEntries, genString } 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 => templateUtils.importName(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 => templateUtils.importName(p.src))
])}`
].join('\n')
}
}
export const appViewTemplate = {
filename: 'views/app.template.html',
getContents () {
return `
{{ HEAD }}
{{ BODY_PREPEND }}
{{ 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 Nuxt3'
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(nuxt.options.publicRuntimeConfig),
{
interfaceName: 'PublicRuntimeConfig',
addExport: false,
addDefaults: false,
allowExtraKeys: false,
indentation: 2
}),
generateTypes(resolveSchema(nuxt.options.privateRuntimeConfig), {
interfaceName: 'PrivateRuntimeConfig',
addExport: false,
addDefaults: false,
indentation: 2,
allowExtraKeys: false,
defaultDescrption: 'This value is only accessible from server-side.'
}),
'}'
].join('\n')
}
}
// Add layouts template
export const layoutTemplate: NuxtTemplate = {
filename: 'layouts.mjs',
getContents ({ app }) {
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')
}
}
export const clientConfigTemplate: NuxtTemplate = {
filename: 'config.client.mjs',
getContents: () => 'export default window?.__NUXT__?.config || {}'
}
export const publicPathTemplate: NuxtTemplate = {
filename: 'paths.mjs',
getContents ({ nuxt }) {
return [
'import { joinURL } from \'ufo\'',
!nuxt.options.dev && 'import config from \'#_config\'',
nuxt.options.dev
? `const appConfig = ${JSON.stringify(nuxt.options.app)}`
: 'const appConfig = config.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')
}
}