diff --git a/docs/content/3.api/4.advanced/2.kit.md b/docs/content/3.api/4.advanced/2.kit.md index e7e43f096b..d0691cd48a 100644 --- a/docs/content/3.api/4.advanced/2.kit.md +++ b/docs/content/3.api/4.advanced/2.kit.md @@ -68,6 +68,7 @@ description: Nuxt Kit provides composable utilities to help interacting with Nux [source code](https://github.com/nuxt/framework/blob/main/packages/kit/src/template.ts) - `addTemplate(templateOptions)` +- `updateTemplates({ filter?: ResolvedNuxtTemplate => boolean })` ### Nitro diff --git a/packages/kit/src/template.ts b/packages/kit/src/template.ts index 8d9e8f87a8..01e2f481ac 100644 --- a/packages/kit/src/template.ts +++ b/packages/kit/src/template.ts @@ -71,3 +71,12 @@ export function normalizeTemplate (template: NuxtTemplate | string): Resolv return template as ResolvedNuxtTemplate } + +/** + * Trigger rebuilding Nuxt templates + * + * You can pass a filter within the options to selectively regenerate a subset of templates. + */ +export function updateTemplates (options?: { filter?: (template: ResolvedNuxtTemplate) => boolean }) { + return useNuxt().hooks.callHook('builder:generateApp', options) +} diff --git a/packages/nuxt/src/components/module.ts b/packages/nuxt/src/components/module.ts index b7fc286119..6f4b9419d7 100644 --- a/packages/nuxt/src/components/module.ts +++ b/packages/nuxt/src/components/module.ts @@ -1,6 +1,6 @@ import { statSync } from 'node:fs' import { relative, resolve } from 'pathe' -import { defineNuxtModule, resolveAlias, addTemplate, addPluginTemplate } from '@nuxt/kit' +import { defineNuxtModule, resolveAlias, addTemplate, addPluginTemplate, updateTemplates } from '@nuxt/kit' import type { Component, ComponentsDir, ComponentsOptions } from '@nuxt/schema' import { distDir } from '../dirs' import { componentsPluginTemplate, componentsTemplate, componentsTypeTemplate } from './templates' @@ -122,12 +122,12 @@ export default defineNuxtModule({ nuxt.hook('vite:extendConfig', (config, { isClient }) => { const mode = isClient ? 'client' : 'server' - ;(config.resolve!.alias as any)['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`) + ; (config.resolve!.alias as any)['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`) }) nuxt.hook('webpack:config', (configs) => { for (const config of configs) { const mode = config.name === 'server' ? 'server' : 'client' - ;(config.resolve!.alias as any)['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`) + ; (config.resolve!.alias as any)['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`) } }) @@ -173,7 +173,14 @@ export default defineNuxtModule({ } const fPath = resolve(nuxt.options.srcDir, path) if (componentDirs.find(dir => fPath.startsWith(dir.path))) { - await nuxt.callHook('builder:generateApp') + await updateTemplates({ + filter: template => [ + 'components.plugin.mjs', + 'components.d.ts', + 'components.server.mjs', + 'components.client.mjs' + ].includes(template.filename) + }) } }) diff --git a/packages/nuxt/src/core/app.ts b/packages/nuxt/src/core/app.ts index aa05a3ce2f..2bf753c58c 100644 --- a/packages/nuxt/src/core/app.ts +++ b/packages/nuxt/src/core/app.ts @@ -1,7 +1,7 @@ import { promises as fsp } from 'node:fs' import { dirname, resolve } from 'pathe' import defu from 'defu' -import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate } from '@nuxt/schema' +import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from '@nuxt/schema' import { findPath, resolveFiles, normalizePlugin, normalizeTemplate, compileTemplate, templateUtils, tryResolveModule, resolvePath, resolveAlias } from '@nuxt/kit' import * as defaultTemplates from './templates' @@ -16,7 +16,7 @@ export function createApp (nuxt: Nuxt, options: Partial = {}): NuxtApp } as unknown as NuxtApp) as NuxtApp } -export async function generateApp (nuxt: Nuxt, app: NuxtApp) { +export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?: (template: ResolvedNuxtTemplate) => boolean } = {}) { // Resolve app await resolveApp(nuxt, app) @@ -31,25 +31,27 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp) { // Compile templates into vfs const templateContext = { utils: templateUtils, nuxt, app } - await Promise.all(app.templates.map(async (template) => { - const contents = await compileTemplate(template, templateContext) + await Promise.all((app.templates as Array>) + .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 fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename!) + nuxt.vfs[fullPath] = contents - const aliasPath = '#build/' + template.filename!.replace(/\.\w+$/, '') - nuxt.vfs[aliasPath] = 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 - } + // 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') - } - })) + if (template.write) { + await fsp.mkdir(dirname(fullPath), { recursive: true }) + await fsp.writeFile(fullPath, contents, 'utf8') + } + })) await nuxt.callHook('app:templatesGenerated', app) } diff --git a/packages/nuxt/src/core/builder.ts b/packages/nuxt/src/core/builder.ts index 2e0028f8a6..1c1ba94531 100644 --- a/packages/nuxt/src/core/builder.ts +++ b/packages/nuxt/src/core/builder.ts @@ -23,7 +23,11 @@ export async function build (nuxt: Nuxt) { await generateApp() } }) - nuxt.hook('builder:generateApp', generateApp) + nuxt.hook('builder:generateApp', (options) => { + // Bypass debounce if we are selectively invalidating templates + if (options) { return _generateApp(nuxt, app, options) } + return generateApp() + }) } await nuxt.callHook('build:before', { nuxt }, nuxt.options.build) diff --git a/packages/nuxt/src/imports/module.ts b/packages/nuxt/src/imports/module.ts index 7f4a1dd49b..0884285a16 100644 --- a/packages/nuxt/src/imports/module.ts +++ b/packages/nuxt/src/imports/module.ts @@ -1,4 +1,4 @@ -import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, resolveAlias, useNuxt, addPluginTemplate, logger } from '@nuxt/kit' +import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, resolveAlias, useNuxt, addPluginTemplate, logger, updateTemplates } from '@nuxt/kit' import { isAbsolute, join, relative, resolve, normalize } from 'pathe' import { createUnimport, Import, scanDirExports, toImports, Unimport } from 'unimport' import { ImportsOptions, ImportPresetWithDeprecation } from '@nuxt/schema' @@ -77,7 +77,6 @@ export default defineNuxtModule>({ nuxt.options.alias['#imports'] = join(nuxt.options.buildDir, 'imports') // Transpile and injection - // @ts-ignore temporary disabled due to #746 if (nuxt.options.dev && options.global) { // Add all imports to globalThis in development mode addPluginTemplate({ @@ -117,10 +116,17 @@ export default defineNuxtModule>({ }) // Watch composables/ directory + const templates = [ + 'types/imports.d.ts', + 'imports.d.ts', + 'imports.mjs' + ] nuxt.hook('builder:watch', async (_, path) => { const _resolved = resolve(nuxt.options.srcDir, path) if (composablesDirs.find(dir => _resolved.startsWith(dir))) { - await nuxt.callHook('builder:generateApp') + await updateTemplates({ + filter: template => templates.includes(template.filename) + }) } }) diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index 2846e5f577..a059281e6b 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -1,5 +1,5 @@ import { existsSync } from 'node:fs' -import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath, addComponent } from '@nuxt/kit' +import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath, addComponent, updateTemplates } from '@nuxt/kit' import { relative, resolve } from 'pathe' import { genString, genImport, genObjectFromRawEntries } from 'knitwork' import escapeRE from 'escape-string-regexp' @@ -48,7 +48,7 @@ export default defineNuxtModule({ const pathPattern = new RegExp(`(^|\\/)(${dirs.map(escapeRE).join('|')})/`) if (event !== 'change' && path.match(pathPattern)) { - await nuxt.callHook('builder:generateApp') + await updateTemplates() } }) diff --git a/packages/schema/src/types/hooks.ts b/packages/schema/src/types/hooks.ts index d620c72400..d0dfb6b696 100644 --- a/packages/schema/src/types/hooks.ts +++ b/packages/schema/src/types/hooks.ts @@ -6,7 +6,7 @@ import type { InlineConfig as ViteInlineConfig, ViteDevServer } from 'vite' import type { Manifest } from 'vue-bundle-renderer' import type { EventHandler } from 'h3' import type { ModuleContainer } from './module' -import type { NuxtTemplate, Nuxt, NuxtApp } from './nuxt' +import type { NuxtTemplate, Nuxt, NuxtApp, ResolvedNuxtTemplate } from './nuxt' import type { Preset as ImportPreset, Import } from 'unimport' import type { NuxtConfig, NuxtOptions } from './config' import type { Nitro, NitroConfig } from 'nitropack' @@ -66,6 +66,10 @@ export interface ImportPresetWithDeprecation extends ImportPreset { names?: string[] } +export interface GenerateAppOptions { + filter?: (template: ResolvedNuxtTemplate) => boolean +} + export interface NuxtHooks { // Kit 'kit:compatibility': (compatibility: NuxtCompatibility, issues: NuxtCompatibilityIssues) => HookResult @@ -74,7 +78,7 @@ export interface NuxtHooks { 'app:resolve': (app: NuxtApp) => HookResult 'app:templates': (app: NuxtApp) => HookResult 'app:templatesGenerated': (app: NuxtApp) => HookResult - 'builder:generateApp': () => HookResult + 'builder:generateApp': (options?: GenerateAppOptions) => HookResult 'pages:extend': (pages: NuxtPage[]) => HookResult 'build:manifest': (manifest: Manifest) => HookResult 'server:devHandler': (handler: EventHandler) => HookResult