Skip to content

Commit 49fe6df

Browse files
committed
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. (cherry picked from commit aaaa4bc)
1 parent 46e868c commit 49fe6df

File tree

1 file changed

+104
-102
lines changed

1 file changed

+104
-102
lines changed

packages/angular/build/src/tools/esbuild/application-code-bundle.ts

Lines changed: 104 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export function createServerMainCodeBundleOptions(
231231
target: string[],
232232
sourceFileCache: SourceFileCache,
233233
stylesheetBundler: ComponentStylesheetBundler,
234-
): BuildOptions {
234+
): BundlerOptionsFactory {
235235
const {
236236
serverEntryPoint: mainServerEntryPoint,
237237
workspaceRoot,
@@ -246,127 +246,129 @@ export function createServerMainCodeBundleOptions(
246246
'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.',
247247
);
248248

249-
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache);
250-
251-
const mainServerNamespace = 'angular:main-server';
252-
const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills';
253-
const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest';
254-
const zoneless = isZonelessApp(polyfills);
255-
const entryPoints: Record<string, string> = {
256-
'main.server': mainServerNamespace,
257-
};
249+
return (loadResultCache) => {
250+
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache, loadResultCache);
258251

259-
const ssrEntryPoint = ssrOptions?.entry;
260-
const isOldBehaviour = !outputMode;
252+
const mainServerNamespace = 'angular:main-server';
253+
const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills';
254+
const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest';
255+
const zoneless = isZonelessApp(polyfills);
256+
const entryPoints: Record<string, string> = {
257+
'main.server': mainServerNamespace,
258+
};
261259

262-
if (ssrEntryPoint && isOldBehaviour) {
263-
// Old behavior: 'server.ts' was bundled together with the SSR (Server-Side Rendering) code.
264-
// This approach combined server-side logic and rendering into a single bundle.
265-
entryPoints['server'] = ssrEntryPoint;
266-
}
260+
const ssrEntryPoint = ssrOptions?.entry;
261+
const isOldBehaviour = !outputMode;
267262

268-
const buildOptions: BuildOptions = {
269-
...getEsBuildServerCommonOptions(options),
270-
target,
271-
inject: [mainServerInjectPolyfillsNamespace, mainServerInjectManifestNamespace],
272-
entryPoints,
273-
supported: getFeatureSupport(target, zoneless),
274-
plugins: [
275-
createWasmPlugin({ allowAsync: zoneless, cache: sourceFileCache?.loadResultCache }),
276-
createSourcemapIgnorelistPlugin(),
277-
createCompilerPlugin(
278-
// JS/TS options
279-
{ ...pluginOptions, noopTypeScriptCompilation: true },
280-
// Component stylesheet bundler
281-
stylesheetBundler,
282-
),
283-
],
284-
};
263+
if (ssrEntryPoint && isOldBehaviour) {
264+
// Old behavior: 'server.ts' was bundled together with the SSR (Server-Side Rendering) code.
265+
// This approach combined server-side logic and rendering into a single bundle.
266+
entryPoints['server'] = ssrEntryPoint;
267+
}
285268

286-
buildOptions.plugins ??= [];
269+
const buildOptions: BuildOptions = {
270+
...getEsBuildServerCommonOptions(options),
271+
target,
272+
inject: [mainServerInjectPolyfillsNamespace, mainServerInjectManifestNamespace],
273+
entryPoints,
274+
supported: getFeatureSupport(target, zoneless),
275+
plugins: [
276+
createWasmPlugin({ allowAsync: zoneless, cache: loadResultCache }),
277+
createSourcemapIgnorelistPlugin(),
278+
createCompilerPlugin(
279+
// JS/TS options
280+
{ ...pluginOptions, noopTypeScriptCompilation: true },
281+
// Component stylesheet bundler
282+
stylesheetBundler,
283+
),
284+
],
285+
};
287286

288-
if (externalPackages) {
289-
buildOptions.packages = 'external';
290-
} else {
291-
buildOptions.plugins.push(createRxjsEsmResolutionPlugin());
292-
}
287+
buildOptions.plugins ??= [];
293288

294-
// Mark manifest and polyfills file as external as these are generated by a different bundle step.
295-
(buildOptions.external ??= []).push(...SERVER_GENERATED_EXTERNALS);
296-
const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral;
289+
if (externalPackages) {
290+
buildOptions.packages = 'external';
291+
} else {
292+
buildOptions.plugins.push(createRxjsEsmResolutionPlugin());
293+
}
297294

298-
if (!isNodePlatform) {
299-
// `@angular/platform-server` lazily depends on `xhr2` for XHR usage with the HTTP client.
300-
// Since `xhr2` has Node.js dependencies, it cannot be used when targeting non-Node.js platforms.
301-
// Note: The framework already issues a warning when using XHR with SSR.
302-
buildOptions.external.push('xhr2');
303-
}
295+
// Mark manifest and polyfills file as external as these are generated by a different bundle step.
296+
(buildOptions.external ??= []).push(...SERVER_GENERATED_EXTERNALS);
297+
const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral;
304298

305-
buildOptions.plugins.push(
306-
createServerBundleMetadata(),
307-
createVirtualModulePlugin({
308-
namespace: mainServerInjectPolyfillsNamespace,
309-
cache: sourceFileCache?.loadResultCache,
310-
loadContent: () => ({
311-
contents: `import './polyfills.server.mjs';`,
312-
loader: 'js',
313-
resolveDir: workspaceRoot,
314-
}),
315-
}),
316-
createVirtualModulePlugin({
317-
namespace: mainServerInjectManifestNamespace,
318-
cache: sourceFileCache?.loadResultCache,
319-
loadContent: async () => {
320-
const contents: string[] = [
321-
// Configure `@angular/ssr` manifest.
322-
`import manifest from './${SERVER_APP_MANIFEST_FILENAME}';`,
323-
`import { ɵsetAngularAppManifest } from '@angular/ssr';`,
324-
`ɵsetAngularAppManifest(manifest);`,
325-
];
299+
if (!isNodePlatform) {
300+
// `@angular/platform-server` lazily depends on `xhr2` for XHR usage with the HTTP client.
301+
// Since `xhr2` has Node.js dependencies, it cannot be used when targeting non-Node.js platforms.
302+
// Note: The framework already issues a warning when using XHR with SSR.
303+
buildOptions.external.push('xhr2');
304+
}
326305

327-
return {
328-
contents: contents.join('\n'),
306+
buildOptions.plugins.push(
307+
createServerBundleMetadata(),
308+
createVirtualModulePlugin({
309+
namespace: mainServerInjectPolyfillsNamespace,
310+
cache: loadResultCache,
311+
loadContent: () => ({
312+
contents: `import './polyfills.server.mjs';`,
329313
loader: 'js',
330314
resolveDir: workspaceRoot,
331-
};
332-
},
333-
}),
334-
createVirtualModulePlugin({
335-
namespace: mainServerNamespace,
336-
cache: sourceFileCache?.loadResultCache,
337-
loadContent: async () => {
338-
const mainServerEntryPointJsImport = entryFileToWorkspaceRelative(
339-
workspaceRoot,
340-
mainServerEntryPoint,
341-
);
315+
}),
316+
}),
317+
createVirtualModulePlugin({
318+
namespace: mainServerInjectManifestNamespace,
319+
cache: loadResultCache,
320+
loadContent: async () => {
321+
const contents: string[] = [
322+
// Configure `@angular/ssr` manifest.
323+
`import manifest from './${SERVER_APP_MANIFEST_FILENAME}';`,
324+
`import { ɵsetAngularAppManifest } from '@angular/ssr';`,
325+
`ɵsetAngularAppManifest(manifest);`,
326+
];
327+
328+
return {
329+
contents: contents.join('\n'),
330+
loader: 'js',
331+
resolveDir: workspaceRoot,
332+
};
333+
},
334+
}),
335+
createVirtualModulePlugin({
336+
namespace: mainServerNamespace,
337+
cache: loadResultCache,
338+
loadContent: async () => {
339+
const mainServerEntryPointJsImport = entryFileToWorkspaceRelative(
340+
workspaceRoot,
341+
mainServerEntryPoint,
342+
);
342343

343-
const contents: string[] = [
344-
// Re-export all symbols including default export from 'main.server.ts'
345-
`export { default } from '${mainServerEntryPointJsImport}';`,
346-
`export * from '${mainServerEntryPointJsImport}';`,
344+
const contents: string[] = [
345+
// Re-export all symbols including default export from 'main.server.ts'
346+
`export { default } from '${mainServerEntryPointJsImport}';`,
347+
`export * from '${mainServerEntryPointJsImport}';`,
347348

348-
// Add @angular/ssr exports
349-
`export {
349+
// Add @angular/ssr exports
350+
`export {
350351
ɵdestroyAngularServerApp,
351352
ɵextractRoutesAndCreateRouteTree,
352353
ɵgetOrCreateAngularServerApp,
353354
} from '@angular/ssr';`,
354-
];
355+
];
355356

356-
return {
357-
contents: contents.join('\n'),
358-
loader: 'js',
359-
resolveDir: workspaceRoot,
360-
};
361-
},
362-
}),
363-
);
357+
return {
358+
contents: contents.join('\n'),
359+
loader: 'js',
360+
resolveDir: workspaceRoot,
361+
};
362+
},
363+
}),
364+
);
364365

365-
if (options.plugins) {
366-
buildOptions.plugins.push(...options.plugins);
367-
}
366+
if (options.plugins) {
367+
buildOptions.plugins.push(...options.plugins);
368+
}
368369

369-
return buildOptions;
370+
return buildOptions;
371+
};
370372
}
371373

372374
export function createSsrEntryCodeBundleOptions(

0 commit comments

Comments
 (0)