From 52509a3ad8edd2d40f73a49d739172581ffe4f70 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:03:36 -0400 Subject: [PATCH] refactor(@angular/build): allow in-memory caching of server main code bundling during rebuilds In addition to the other main code bundling steps, the server main code will also be cached and invalidated based on file changes in watch mode. --- .../tools/esbuild/application-code-bundle.ts | 206 +++++++++--------- 1 file changed, 104 insertions(+), 102 deletions(-) diff --git a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts index 15ec37e0125a..a14c13706607 100644 --- a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts +++ b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts @@ -231,7 +231,7 @@ export function createServerMainCodeBundleOptions( target: string[], sourceFileCache: SourceFileCache, stylesheetBundler: ComponentStylesheetBundler, -): BuildOptions { +): BundlerOptionsFactory { const { serverEntryPoint: mainServerEntryPoint, workspaceRoot, @@ -246,127 +246,129 @@ export function createServerMainCodeBundleOptions( 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.', ); - const pluginOptions = createCompilerPluginOptions(options, sourceFileCache); - - const mainServerNamespace = 'angular:main-server'; - const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills'; - const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest'; - const zoneless = isZonelessApp(polyfills); - const entryPoints: Record = { - 'main.server': mainServerNamespace, - }; + return (loadResultCache) => { + const pluginOptions = createCompilerPluginOptions(options, sourceFileCache, loadResultCache); - const ssrEntryPoint = ssrOptions?.entry; - const isOldBehaviour = !outputMode; + const mainServerNamespace = 'angular:main-server'; + const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills'; + const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest'; + const zoneless = isZonelessApp(polyfills); + const entryPoints: Record = { + 'main.server': mainServerNamespace, + }; - if (ssrEntryPoint && isOldBehaviour) { - // Old behavior: 'server.ts' was bundled together with the SSR (Server-Side Rendering) code. - // This approach combined server-side logic and rendering into a single bundle. - entryPoints['server'] = ssrEntryPoint; - } + const ssrEntryPoint = ssrOptions?.entry; + const isOldBehaviour = !outputMode; - const buildOptions: BuildOptions = { - ...getEsBuildServerCommonOptions(options), - target, - inject: [mainServerInjectPolyfillsNamespace, mainServerInjectManifestNamespace], - entryPoints, - supported: getFeatureSupport(target, zoneless), - plugins: [ - createWasmPlugin({ allowAsync: zoneless, cache: sourceFileCache?.loadResultCache }), - createSourcemapIgnorelistPlugin(), - createCompilerPlugin( - // JS/TS options - { ...pluginOptions, noopTypeScriptCompilation: true }, - // Component stylesheet bundler - stylesheetBundler, - ), - ], - }; + if (ssrEntryPoint && isOldBehaviour) { + // Old behavior: 'server.ts' was bundled together with the SSR (Server-Side Rendering) code. + // This approach combined server-side logic and rendering into a single bundle. + entryPoints['server'] = ssrEntryPoint; + } - buildOptions.plugins ??= []; + const buildOptions: BuildOptions = { + ...getEsBuildServerCommonOptions(options), + target, + inject: [mainServerInjectPolyfillsNamespace, mainServerInjectManifestNamespace], + entryPoints, + supported: getFeatureSupport(target, zoneless), + plugins: [ + createWasmPlugin({ allowAsync: zoneless, cache: loadResultCache }), + createSourcemapIgnorelistPlugin(), + createCompilerPlugin( + // JS/TS options + { ...pluginOptions, noopTypeScriptCompilation: true }, + // Component stylesheet bundler + stylesheetBundler, + ), + ], + }; - if (externalPackages) { - buildOptions.packages = 'external'; - } else { - buildOptions.plugins.push(createRxjsEsmResolutionPlugin()); - } + buildOptions.plugins ??= []; - // Mark manifest and polyfills file as external as these are generated by a different bundle step. - (buildOptions.external ??= []).push(...SERVER_GENERATED_EXTERNALS); - const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral; + if (externalPackages) { + buildOptions.packages = 'external'; + } else { + buildOptions.plugins.push(createRxjsEsmResolutionPlugin()); + } - if (!isNodePlatform) { - // `@angular/platform-server` lazily depends on `xhr2` for XHR usage with the HTTP client. - // Since `xhr2` has Node.js dependencies, it cannot be used when targeting non-Node.js platforms. - // Note: The framework already issues a warning when using XHR with SSR. - buildOptions.external.push('xhr2'); - } + // Mark manifest and polyfills file as external as these are generated by a different bundle step. + (buildOptions.external ??= []).push(...SERVER_GENERATED_EXTERNALS); + const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral; - buildOptions.plugins.push( - createServerBundleMetadata(), - createVirtualModulePlugin({ - namespace: mainServerInjectPolyfillsNamespace, - cache: sourceFileCache?.loadResultCache, - loadContent: () => ({ - contents: `import './polyfills.server.mjs';`, - loader: 'js', - resolveDir: workspaceRoot, - }), - }), - createVirtualModulePlugin({ - namespace: mainServerInjectManifestNamespace, - cache: sourceFileCache?.loadResultCache, - loadContent: async () => { - const contents: string[] = [ - // Configure `@angular/ssr` manifest. - `import manifest from './${SERVER_APP_MANIFEST_FILENAME}';`, - `import { ɵsetAngularAppManifest } from '@angular/ssr';`, - `ɵsetAngularAppManifest(manifest);`, - ]; + if (!isNodePlatform) { + // `@angular/platform-server` lazily depends on `xhr2` for XHR usage with the HTTP client. + // Since `xhr2` has Node.js dependencies, it cannot be used when targeting non-Node.js platforms. + // Note: The framework already issues a warning when using XHR with SSR. + buildOptions.external.push('xhr2'); + } - return { - contents: contents.join('\n'), + buildOptions.plugins.push( + createServerBundleMetadata(), + createVirtualModulePlugin({ + namespace: mainServerInjectPolyfillsNamespace, + cache: loadResultCache, + loadContent: () => ({ + contents: `import './polyfills.server.mjs';`, loader: 'js', resolveDir: workspaceRoot, - }; - }, - }), - createVirtualModulePlugin({ - namespace: mainServerNamespace, - cache: sourceFileCache?.loadResultCache, - loadContent: async () => { - const mainServerEntryPointJsImport = entryFileToWorkspaceRelative( - workspaceRoot, - mainServerEntryPoint, - ); + }), + }), + createVirtualModulePlugin({ + namespace: mainServerInjectManifestNamespace, + cache: loadResultCache, + loadContent: async () => { + const contents: string[] = [ + // Configure `@angular/ssr` manifest. + `import manifest from './${SERVER_APP_MANIFEST_FILENAME}';`, + `import { ɵsetAngularAppManifest } from '@angular/ssr';`, + `ɵsetAngularAppManifest(manifest);`, + ]; + + return { + contents: contents.join('\n'), + loader: 'js', + resolveDir: workspaceRoot, + }; + }, + }), + createVirtualModulePlugin({ + namespace: mainServerNamespace, + cache: loadResultCache, + loadContent: async () => { + const mainServerEntryPointJsImport = entryFileToWorkspaceRelative( + workspaceRoot, + mainServerEntryPoint, + ); - const contents: string[] = [ - // Re-export all symbols including default export from 'main.server.ts' - `export { default } from '${mainServerEntryPointJsImport}';`, - `export * from '${mainServerEntryPointJsImport}';`, + const contents: string[] = [ + // Re-export all symbols including default export from 'main.server.ts' + `export { default } from '${mainServerEntryPointJsImport}';`, + `export * from '${mainServerEntryPointJsImport}';`, - // Add @angular/ssr exports - `export { + // Add @angular/ssr exports + `export { ɵdestroyAngularServerApp, ɵextractRoutesAndCreateRouteTree, ɵgetOrCreateAngularServerApp, } from '@angular/ssr';`, - ]; + ]; - return { - contents: contents.join('\n'), - loader: 'js', - resolveDir: workspaceRoot, - }; - }, - }), - ); + return { + contents: contents.join('\n'), + loader: 'js', + resolveDir: workspaceRoot, + }; + }, + }), + ); - if (options.plugins) { - buildOptions.plugins.push(...options.plugins); - } + if (options.plugins) { + buildOptions.plugins.push(...options.plugins); + } - return buildOptions; + return buildOptions; + }; } export function createSsrEntryCodeBundleOptions(