From 933c0aa71f956f35ec01163299f69d436970e29b Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 1 May 2024 06:43:35 +0100 Subject: [PATCH] fix(nuxt): compile plugin templates last (#27009) --- packages/nuxt/src/core/app.ts | 102 ++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/packages/nuxt/src/core/app.ts b/packages/nuxt/src/core/app.ts index 87661f8db6..72940b484a 100644 --- a/packages/nuxt/src/core/app.ts +++ b/packages/nuxt/src/core/app.ts @@ -20,6 +20,12 @@ export function createApp (nuxt: Nuxt, options: Partial = {}): NuxtApp } as unknown as NuxtApp) as NuxtApp } +const postTemplates = [ + defaultTemplates.clientPluginTemplate.filename, + defaultTemplates.serverPluginTemplate.filename, + defaultTemplates.pluginsDeclaration.filename, +] + export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?: (template: ResolvedNuxtTemplate) => boolean } = {}) { // Resolve app await resolveApp(nuxt, app) @@ -33,60 +39,74 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?: // Normalize templates app.templates = app.templates.map(tmpl => normalizeTemplate(tmpl)) + // compile plugins first as they are needed within the nuxt.vfs + // in order to annotate templated plugins + const filteredTemplates: Record<'pre' | 'post', Array>> = { + pre: [], + post: [], + } + + for (const template of app.templates as Array>) { + if (options.filter && !options.filter(template)) { continue } + const key = template.filename && postTemplates.includes(template.filename) ? 'post' : 'pre' + filteredTemplates[key].push(template) + } + // Compile templates into vfs // TODO: remove utils in v4 const templateContext = { utils: templateUtils, nuxt, app } - const filteredTemplates = (app.templates as Array>) - .filter(template => !options.filter || options.filter(template)) - const compileTemplate = nuxt.options.experimental.compileTemplate ? _compileTemplate : futureCompileTemplate const writes: Array<() => void> = [] - await Promise.allSettled(filteredTemplates - .map(async (template) => { - const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!) - const mark = performance.mark(fullPath) - const oldContents = nuxt.vfs[fullPath] - const contents = await compileTemplate(template, templateContext).catch((e) => { - logger.error(`Could not compile template \`${template.filename}\`.`) - logger.error(e) - throw e + const changedTemplates: Array> = [] + + async function processTemplate (template: ResolvedNuxtTemplate) { + const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!) + const mark = performance.mark(fullPath) + const oldContents = nuxt.vfs[fullPath] + const contents = await compileTemplate(template, templateContext).catch((e) => { + logger.error(`Could not compile template \`${template.filename}\`.`) + logger.error(e) + throw e + }) + + template.modified = oldContents !== contents + if (template.modified) { + 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 + } + + changedTemplates.push(template) + } + + 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) { + logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`) + } + + if (template.modified && template.write) { + writes.push(() => { + mkdirSync(dirname(fullPath), { recursive: true }) + writeFileSync(fullPath, contents, 'utf8') }) + } + } - template.modified = oldContents !== contents - if (template.modified) { - 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 - } - } - - 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) { - logger.info(`Compiled \`${template.filename}\` in ${setupTime}ms`) - } - - if (template.modified && template.write) { - writes.push(() => { - mkdirSync(dirname(fullPath), { recursive: true }) - writeFileSync(fullPath, contents, 'utf8') - }) - } - })) + await Promise.allSettled(filteredTemplates.pre.map(processTemplate)) + await Promise.allSettled(filteredTemplates.post.map(processTemplate)) // 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() } - const changedTemplates = filteredTemplates.filter(t => t.modified) - if (changedTemplates.length) { await nuxt.callHook('app:templatesGenerated', app, changedTemplates, options) }