Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 104 additions & 102 deletions packages/angular/build/src/tools/esbuild/application-code-bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export function createServerMainCodeBundleOptions(
target: string[],
sourceFileCache: SourceFileCache,
stylesheetBundler: ComponentStylesheetBundler,
): BuildOptions {
): BundlerOptionsFactory {
const {
serverEntryPoint: mainServerEntryPoint,
workspaceRoot,
Expand All @@ -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<string, string> = {
'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<string, string> = {
'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(
Expand Down