2023-07-30 16:14:42 +00:00
|
|
|
import { promises as fsp, mkdirSync, writeFileSync } from 'node:fs'
|
2023-04-07 16:02:47 +00:00
|
|
|
import { dirname, join, resolve } from 'pathe'
|
2023-01-30 12:09:48 +00:00
|
|
|
import { defu } from 'defu'
|
2023-04-07 16:02:47 +00:00
|
|
|
import { compileTemplate, findPath, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath, templateUtils, tryResolveModule } from '@nuxt/kit'
|
2023-03-11 21:16:01 +00:00
|
|
|
import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from 'nuxt/schema'
|
2021-09-21 14:55:07 +00:00
|
|
|
|
2021-10-13 20:41:21 +00:00
|
|
|
import * as defaultTemplates from './templates'
|
2022-06-27 12:10:29 +00:00
|
|
|
import { getNameFromPath, hasSuffix, uniqueBy } from './utils'
|
2023-06-19 23:00:03 +00:00
|
|
|
import { extractMetadata, orderMap } from './plugins/plugin-metadata'
|
2021-04-02 11:47:01 +00:00
|
|
|
|
2021-05-20 11:42:41 +00:00
|
|
|
export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp {
|
|
|
|
return defu(options, {
|
2020-08-19 13:06:27 +00:00
|
|
|
dir: nuxt.options.srcDir,
|
|
|
|
extensions: nuxt.options.extensions,
|
2021-02-19 01:08:45 +00:00
|
|
|
plugins: [],
|
2023-05-01 16:35:00 +00:00
|
|
|
components: [],
|
2021-07-28 11:35:24 +00:00
|
|
|
templates: []
|
2022-08-12 17:47:58 +00:00
|
|
|
} as unknown as NuxtApp) as NuxtApp
|
2021-05-20 11:42:41 +00:00
|
|
|
}
|
|
|
|
|
2022-10-24 08:53:02 +00:00
|
|
|
export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?: (template: ResolvedNuxtTemplate<any>) => boolean } = {}) {
|
2021-05-20 11:42:41 +00:00
|
|
|
// Resolve app
|
|
|
|
await resolveApp(nuxt, app)
|
|
|
|
|
2021-07-28 11:35:24 +00:00
|
|
|
// User templates from options.build.templates
|
2022-08-12 17:47:58 +00:00
|
|
|
app.templates = Object.values(defaultTemplates).concat(nuxt.options.build.templates) as NuxtTemplate[]
|
2021-06-16 11:22:01 +00:00
|
|
|
|
2021-07-28 11:35:24 +00:00
|
|
|
// Extend templates with hook
|
2021-05-20 11:42:41 +00:00
|
|
|
await nuxt.callHook('app:templates', app)
|
|
|
|
|
2021-07-28 11:35:24 +00:00
|
|
|
// Normalize templates
|
|
|
|
app.templates = app.templates.map(tmpl => normalizeTemplate(tmpl))
|
|
|
|
|
2021-07-15 10:18:34 +00:00
|
|
|
// Compile templates into vfs
|
2023-07-30 16:14:42 +00:00
|
|
|
// TODO: remove utils in v4
|
2021-07-15 10:18:34 +00:00
|
|
|
const templateContext = { utils: templateUtils, nuxt, app }
|
2023-07-30 11:41:01 +00:00
|
|
|
const filteredTemplates = (app.templates as Array<ReturnType<typeof normalizeTemplate>>)
|
2022-10-24 08:53:02 +00:00
|
|
|
.filter(template => !options.filter || options.filter(template))
|
2023-07-30 11:41:01 +00:00
|
|
|
|
2023-07-30 16:14:42 +00:00
|
|
|
const writes: Array<() => void> = []
|
|
|
|
await Promise.allSettled(filteredTemplates
|
2022-10-24 08:53:02 +00:00
|
|
|
.map(async (template) => {
|
|
|
|
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!)
|
2023-07-30 16:14:42 +00:00
|
|
|
const mark = performance.mark(fullPath)
|
|
|
|
const contents = await compileTemplate(template, templateContext).catch((e) => {
|
|
|
|
console.error(`[nuxt] Could not compile template \`${template.filename}\`.`)
|
|
|
|
throw e
|
|
|
|
})
|
|
|
|
|
2022-10-24 08:53:02 +00:00
|
|
|
nuxt.vfs[fullPath] = contents
|
|
|
|
|
|
|
|
const aliasPath = '#build/' + template.filename!.replace(/\.\w+$/, '')
|
|
|
|
nuxt.vfs[aliasPath] = contents
|
|
|
|
|
|
|
|
// In case a non-normalized absolute path is called for on Windows
|
|
|
|
if (process.platform === 'win32') {
|
|
|
|
nuxt.vfs[fullPath.replace(/\//g, '\\')] = contents
|
|
|
|
}
|
|
|
|
|
2023-07-30 16:14:42 +00:00
|
|
|
const perf = performance.measure(fullPath, mark?.name) // TODO: remove when Node 14 reaches EOL
|
|
|
|
const setupTime = perf ? Math.round((perf.duration * 100)) / 100 : 0 // TODO: remove when Node 14 reaches EOL
|
|
|
|
|
|
|
|
if (nuxt.options.debug || setupTime > 500) {
|
|
|
|
console.info(`[nuxt] compiled \`${template.filename}\` in ${setupTime}ms`)
|
|
|
|
}
|
|
|
|
|
2022-10-24 08:53:02 +00:00
|
|
|
if (template.write) {
|
2023-07-30 16:14:42 +00:00
|
|
|
writes.push(() => {
|
|
|
|
mkdirSync(dirname(fullPath), { recursive: true })
|
|
|
|
writeFileSync(fullPath, contents, 'utf8')
|
|
|
|
})
|
2022-10-24 08:53:02 +00:00
|
|
|
}
|
|
|
|
}))
|
2021-05-20 11:42:41 +00:00
|
|
|
|
2023-07-30 16:14:42 +00:00
|
|
|
// Write template files in single synchronous step to avoid (possible) additional
|
|
|
|
// runtime overhead of cascading HMRs from vite/webpack
|
|
|
|
for (const write of writes) { write() }
|
|
|
|
|
2023-07-30 11:41:01 +00:00
|
|
|
await nuxt.callHook('app:templatesGenerated', app, filteredTemplates, options)
|
2021-05-20 11:42:41 +00:00
|
|
|
}
|
2020-08-19 12:38:18 +00:00
|
|
|
|
2023-06-27 09:38:40 +00:00
|
|
|
async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
2021-05-20 11:42:41 +00:00
|
|
|
// Resolve main (app.vue)
|
2021-10-12 12:51:41 +00:00
|
|
|
if (!app.mainComponent) {
|
2022-07-29 11:12:50 +00:00
|
|
|
app.mainComponent = await findPath(
|
2022-12-06 11:30:14 +00:00
|
|
|
nuxt.options._layers.flatMap(layer => [
|
|
|
|
join(layer.config.srcDir, 'App'),
|
|
|
|
join(layer.config.srcDir, 'app')
|
|
|
|
])
|
2022-07-29 11:12:50 +00:00
|
|
|
)
|
2020-08-17 15:25:06 +00:00
|
|
|
}
|
2021-10-12 12:51:41 +00:00
|
|
|
if (!app.mainComponent) {
|
2023-05-27 16:24:21 +00:00
|
|
|
app.mainComponent = (await tryResolveModule('@nuxt/ui-templates/templates/welcome.vue', nuxt.options.modulesDir))!
|
2021-01-18 12:22:38 +00:00
|
|
|
}
|
2021-05-20 11:42:41 +00:00
|
|
|
|
2022-03-17 10:57:02 +00:00
|
|
|
// Resolve root component
|
|
|
|
if (!app.rootComponent) {
|
|
|
|
app.rootComponent = await findPath(['~/app.root', resolve(nuxt.options.appDir, 'components/nuxt-root.vue')])
|
|
|
|
}
|
2021-10-12 12:51:41 +00:00
|
|
|
|
2022-03-11 08:22:16 +00:00
|
|
|
// Resolve error component
|
|
|
|
if (!app.errorComponent) {
|
2022-12-06 09:44:03 +00:00
|
|
|
app.errorComponent = (await findPath(
|
|
|
|
nuxt.options._layers.map(layer => join(layer.config.srcDir, 'error'))
|
|
|
|
)) ?? resolve(nuxt.options.appDir, 'components/nuxt-error-page.vue')
|
2022-03-11 08:22:16 +00:00
|
|
|
}
|
|
|
|
|
2022-03-16 20:36:30 +00:00
|
|
|
// Resolve layouts/ from all config layers
|
2022-03-14 10:47:24 +00:00
|
|
|
app.layouts = {}
|
2022-03-16 20:36:30 +00:00
|
|
|
for (const config of nuxt.options._layers.map(layer => layer.config)) {
|
2022-03-14 13:53:31 +00:00
|
|
|
const layoutFiles = await resolveFiles(config.srcDir, `${config.dir?.layouts || 'layouts'}/*{${nuxt.options.extensions.join(',')}}`)
|
2022-03-14 10:47:24 +00:00
|
|
|
for (const file of layoutFiles) {
|
|
|
|
const name = getNameFromPath(file)
|
|
|
|
app.layouts[name] = app.layouts[name] || { name, file }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-27 12:10:29 +00:00
|
|
|
// Resolve middleware/ from all config layers
|
|
|
|
app.middleware = []
|
|
|
|
for (const config of nuxt.options._layers.map(layer => layer.config)) {
|
|
|
|
const middlewareFiles = await resolveFiles(config.srcDir, `${config.dir?.middleware || 'middleware'}/*{${nuxt.options.extensions.join(',')}}`)
|
|
|
|
app.middleware.push(...middlewareFiles.map((file) => {
|
|
|
|
const name = getNameFromPath(file)
|
|
|
|
return { name, path: file, global: hasSuffix(file, '.global') }
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2021-05-20 11:42:41 +00:00
|
|
|
// Resolve plugins
|
2022-03-16 20:36:30 +00:00
|
|
|
app.plugins = [
|
|
|
|
...nuxt.options.plugins.map(normalizePlugin)
|
|
|
|
]
|
|
|
|
for (const config of nuxt.options._layers.map(layer => layer.config)) {
|
2022-03-09 10:51:32 +00:00
|
|
|
app.plugins.push(...[
|
2022-03-16 20:36:30 +00:00
|
|
|
...(config.plugins || []),
|
2022-08-12 17:47:58 +00:00
|
|
|
...config.srcDir
|
|
|
|
? await resolveFiles(config.srcDir, [
|
2022-10-07 07:13:12 +00:00
|
|
|
`${config.dir?.plugins || 'plugins'}/*.{ts,js,mjs,cjs,mts,cts}`,
|
2023-03-01 12:08:58 +00:00
|
|
|
`${config.dir?.plugins || 'plugins'}/*/index.*{ts,js,mjs,cjs,mts,cts}` // TODO: remove, only scan top-level plugins #18418
|
2022-08-12 17:47:58 +00:00
|
|
|
])
|
|
|
|
: []
|
2022-03-09 10:51:32 +00:00
|
|
|
].map(plugin => normalizePlugin(plugin as NuxtPlugin)))
|
|
|
|
}
|
2022-08-04 15:15:42 +00:00
|
|
|
|
|
|
|
// Normalize and de-duplicate plugins and middleware
|
|
|
|
app.middleware = uniqueBy(await resolvePaths(app.middleware, 'path'), 'name')
|
|
|
|
app.plugins = uniqueBy(await resolvePaths(app.plugins, 'src'), 'src')
|
2021-05-20 11:42:41 +00:00
|
|
|
|
2022-08-17 15:23:13 +00:00
|
|
|
// Resolve app.config
|
|
|
|
app.configs = []
|
|
|
|
for (const config of nuxt.options._layers.map(layer => layer.config)) {
|
|
|
|
const appConfigPath = await findPath(resolve(config.srcDir, 'app.config'))
|
|
|
|
if (appConfigPath) {
|
|
|
|
app.configs.push(appConfigPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-20 11:42:41 +00:00
|
|
|
// Extend app
|
|
|
|
await nuxt.callHook('app:resolve', app)
|
2022-08-04 15:15:42 +00:00
|
|
|
|
|
|
|
// Normalize and de-duplicate plugins and middleware
|
|
|
|
app.middleware = uniqueBy(await resolvePaths(app.middleware, 'path'), 'name')
|
|
|
|
app.plugins = uniqueBy(await resolvePaths(app.plugins, 'src'), 'src')
|
|
|
|
}
|
|
|
|
|
2022-12-06 11:30:14 +00:00
|
|
|
function resolvePaths<Item extends Record<string, any>> (items: Item[], key: { [K in keyof Item]: Item[K] extends string ? K : never }[keyof Item]) {
|
2022-08-04 15:15:42 +00:00
|
|
|
return Promise.all(items.map(async (item) => {
|
|
|
|
if (!item[key]) { return item }
|
|
|
|
return {
|
|
|
|
...item,
|
|
|
|
[key]: await resolvePath(resolveAlias(item[key]))
|
|
|
|
}
|
|
|
|
}))
|
2021-05-20 11:42:41 +00:00
|
|
|
}
|
2023-06-19 23:00:03 +00:00
|
|
|
|
|
|
|
export async function annotatePlugins (nuxt: Nuxt, plugins: NuxtPlugin[]) {
|
|
|
|
const _plugins: NuxtPlugin[] = []
|
|
|
|
for (const plugin of plugins) {
|
|
|
|
try {
|
|
|
|
const code = plugin.src in nuxt.vfs ? nuxt.vfs[plugin.src] : await fsp.readFile(plugin.src!, 'utf-8')
|
|
|
|
_plugins.push({
|
2023-06-23 23:01:17 +00:00
|
|
|
...await extractMetadata(code),
|
2023-06-19 23:00:03 +00:00
|
|
|
...plugin
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
console.warn(`[nuxt] Could not resolve \`${plugin.src}\`.`)
|
|
|
|
_plugins.push(plugin)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return _plugins.sort((a, b) => (a.order ?? orderMap.default) - (b.order ?? orderMap.default))
|
|
|
|
}
|