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(