-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
/
app.ts
170 lines (145 loc) · 6.22 KB
/
app.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { promises as fsp } from 'node:fs'
import { dirname, join, resolve } from 'pathe'
import { defu } from 'defu'
import { compileTemplate, findPath, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath, templateUtils, tryResolveModule } from '@nuxt/kit'
import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from 'nuxt/schema'
import * as defaultTemplates from './templates'
import { getNameFromPath, hasSuffix, uniqueBy } from './utils'
import { extractMetadata, orderMap } from './plugins/plugin-metadata'
export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp {
return defu(options, {
dir: nuxt.options.srcDir,
extensions: nuxt.options.extensions,
plugins: [],
components: [],
templates: []
} as unknown as NuxtApp) as NuxtApp
}
export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?: (template: ResolvedNuxtTemplate<any>) => boolean } = {}) {
// Resolve app
await resolveApp(nuxt, app)
// User templates from 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)
// Normalize templates
app.templates = app.templates.map(tmpl => normalizeTemplate(tmpl))
// Compile templates into vfs
const templateContext = { utils: templateUtils, nuxt, app }
await Promise.all((app.templates as Array<ReturnType<typeof normalizeTemplate>>)
.filter(template => !options.filter || options.filter(template))
.map(async (template) => {
const contents = await compileTemplate(template, templateContext)
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!)
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
}
if (template.write) {
await fsp.mkdir(dirname(fullPath), { recursive: true })
await fsp.writeFile(fullPath, contents, 'utf8')
}
}))
await nuxt.callHook('app:templatesGenerated', app)
}
export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
// Resolve main (app.vue)
if (!app.mainComponent) {
app.mainComponent = await findPath(
nuxt.options._layers.flatMap(layer => [
join(layer.config.srcDir, 'App'),
join(layer.config.srcDir, 'app')
])
)
}
if (!app.mainComponent) {
app.mainComponent = (await tryResolveModule('@nuxt/ui-templates/templates/welcome.vue', nuxt.options.modulesDir))!
}
// Resolve root component
if (!app.rootComponent) {
app.rootComponent = await findPath(['~/app.root', resolve(nuxt.options.appDir, 'components/nuxt-root.vue')])
}
// Resolve error component
if (!app.errorComponent) {
app.errorComponent = (await findPath(
nuxt.options._layers.map(layer => join(layer.config.srcDir, 'error'))
)) ?? resolve(nuxt.options.appDir, 'components/nuxt-error-page.vue')
}
// Resolve layouts/ from all config layers
app.layouts = {}
for (const config of nuxt.options._layers.map(layer => layer.config)) {
const layoutFiles = await resolveFiles(config.srcDir, `${config.dir?.layouts || 'layouts'}/*{${nuxt.options.extensions.join(',')}}`)
for (const file of layoutFiles) {
const name = getNameFromPath(file)
app.layouts[name] = app.layouts[name] || { name, file }
}
}
// 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') }
}))
}
// Resolve plugins
app.plugins = [
...nuxt.options.plugins.map(normalizePlugin)
]
for (const config of nuxt.options._layers.map(layer => layer.config)) {
app.plugins.push(...[
...(config.plugins || []),
...config.srcDir
? await resolveFiles(config.srcDir, [
`${config.dir?.plugins || 'plugins'}/*.{ts,js,mjs,cjs,mts,cts}`,
`${config.dir?.plugins || 'plugins'}/*/index.*{ts,js,mjs,cjs,mts,cts}` // TODO: remove, only scan top-level plugins #18418
])
: []
].map(plugin => normalizePlugin(plugin as NuxtPlugin)))
}
// 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')
// 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)
}
}
// Extend app
await nuxt.callHook('app:resolve', app)
// 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')
}
function resolvePaths<Item extends Record<string, any>> (items: Item[], key: { [K in keyof Item]: Item[K] extends string ? K : never }[keyof Item]) {
return Promise.all(items.map(async (item) => {
if (!item[key]) { return item }
return {
...item,
[key]: await resolvePath(resolveAlias(item[key]))
}
}))
}
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({
...extractMetadata(code),
...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))
}