Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function setupBundlerContexts(
const serverPolyfillBundleOptions = createServerPolyfillBundleOptions(
options,
nodeTargets,
codeBundleCache,
codeBundleCache.loadResultCache,
);

if (serverPolyfillBundleOptions) {
Expand Down
340 changes: 172 additions & 168 deletions packages/angular/build/src/tools/esbuild/application-code-bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,62 +38,64 @@ export function createBrowserCodeBundleOptions(
target: string[],
sourceFileCache: SourceFileCache,
stylesheetBundler: ComponentStylesheetBundler,
): BuildOptions {
const { entryPoints, outputNames, polyfills } = options;

const pluginOptions = createCompilerPluginOptions(options, sourceFileCache);

const zoneless = isZonelessApp(polyfills);

const buildOptions: BuildOptions = {
...getEsBuildCommonOptions(options),
platform: 'browser',
// Note: `es2015` is needed for RxJS v6. If not specified, `module` would
// match and the ES5 distribution would be bundled and ends up breaking at
// runtime with the RxJS testing library.
// More details: https://github.com/angular/angular-cli/issues/25405.
mainFields: ['es2020', 'es2015', 'browser', 'module', 'main'],
entryNames: outputNames.bundles,
entryPoints,
target,
supported: getFeatureSupport(target, zoneless),
plugins: [
createLoaderImportAttributePlugin(),
createWasmPlugin({ allowAsync: zoneless, cache: sourceFileCache?.loadResultCache }),
createSourcemapIgnorelistPlugin(),
createCompilerPlugin(
// JS/TS options
pluginOptions,
// Component stylesheet bundler
stylesheetBundler,
),
],
};
): BundlerOptionsFactory {
return (loadCache) => {
const { entryPoints, outputNames, polyfills } = options;

const pluginOptions = createCompilerPluginOptions(options, sourceFileCache, loadCache);

const zoneless = isZonelessApp(polyfills);

const buildOptions: BuildOptions = {
...getEsBuildCommonOptions(options),
platform: 'browser',
// Note: `es2015` is needed for RxJS v6. If not specified, `module` would
// match and the ES5 distribution would be bundled and ends up breaking at
// runtime with the RxJS testing library.
// More details: https://github.com/angular/angular-cli/issues/25405.
mainFields: ['es2020', 'es2015', 'browser', 'module', 'main'],
entryNames: outputNames.bundles,
entryPoints,
target,
supported: getFeatureSupport(target, zoneless),
plugins: [
createLoaderImportAttributePlugin(),
createWasmPlugin({ allowAsync: zoneless, cache: loadCache }),
createSourcemapIgnorelistPlugin(),
createCompilerPlugin(
// JS/TS options
pluginOptions,
// Component stylesheet bundler
stylesheetBundler,
),
],
};

if (options.plugins) {
buildOptions.plugins?.push(...options.plugins);
}
if (options.plugins) {
buildOptions.plugins?.push(...options.plugins);
}

if (options.externalPackages) {
// Package files affected by a customized loader should not be implicitly marked as external
if (
options.loaderExtensions ||
options.plugins ||
typeof options.externalPackages === 'object'
) {
// Plugin must be added after custom plugins to ensure any added loader options are considered
buildOptions.plugins?.push(
createExternalPackagesPlugin(
options.externalPackages !== true ? options.externalPackages : undefined,
),
);
} else {
// Safe to use the packages external option directly
buildOptions.packages = 'external';
if (options.externalPackages) {
// Package files affected by a customized loader should not be implicitly marked as external
if (
options.loaderExtensions ||
options.plugins ||
typeof options.externalPackages === 'object'
) {
// Plugin must be added after custom plugins to ensure any added loader options are considered
buildOptions.plugins?.push(
createExternalPackagesPlugin(
options.externalPackages !== true ? options.externalPackages : undefined,
),
);
} else {
// Safe to use the packages external option directly
buildOptions.packages = 'external';
}
}
}

return buildOptions;
return buildOptions;
};
}

export function createBrowserPolyfillBundleOptions(
Expand Down Expand Up @@ -158,7 +160,7 @@ export function createBrowserPolyfillBundleOptions(
export function createServerPolyfillBundleOptions(
options: NormalizedApplicationBuildOptions,
target: string[],
sourceFileCache?: SourceFileCache,
loadResultCache: LoadResultCache | undefined,
): BundlerOptionsFactory | undefined {
const serverPolyfills: string[] = [];
const polyfillsFromConfig = new Set(options.polyfills);
Expand All @@ -185,7 +187,7 @@ export function createServerPolyfillBundleOptions(
},
namespace,
false,
sourceFileCache?.loadResultCache,
loadResultCache,
);

if (!polyfillBundleOptions) {
Expand Down Expand Up @@ -372,137 +374,139 @@ export function createSsrEntryCodeBundleOptions(
target: string[],
sourceFileCache: SourceFileCache,
stylesheetBundler: ComponentStylesheetBundler,
): BuildOptions {
): BundlerOptionsFactory {
const { workspaceRoot, ssrOptions, externalPackages } = options;
const serverEntryPoint = ssrOptions?.entry;
assert(
serverEntryPoint,
'createSsrEntryCodeBundleOptions should not be called without a defined serverEntryPoint.',
);

const pluginOptions = createCompilerPluginOptions(options, sourceFileCache);

const ssrEntryNamespace = 'angular:ssr-entry';
const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
const ssrInjectRequireNamespace = 'angular:ssr-entry-inject-require';
const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral;

const inject: string[] = [ssrInjectManifestNamespace];
if (isNodePlatform) {
inject.unshift(ssrInjectRequireNamespace);
}

const buildOptions: BuildOptions = {
...getEsBuildServerCommonOptions(options),
target,
entryPoints: {
// TODO: consider renaming to index
'server': ssrEntryNamespace,
},
supported: getFeatureSupport(target, true),
plugins: [
createSourcemapIgnorelistPlugin(),
createCompilerPlugin(
// JS/TS options
{ ...pluginOptions, noopTypeScriptCompilation: true },
// Component stylesheet bundler
stylesheetBundler,
),
],
inject,
};

buildOptions.plugins ??= [];
return (loadResultCache) => {
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache, loadResultCache);

if (externalPackages) {
buildOptions.packages = 'external';
} else {
buildOptions.plugins.push(createRxjsEsmResolutionPlugin());
}
const ssrEntryNamespace = 'angular:ssr-entry';
const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
const ssrInjectRequireNamespace = 'angular:ssr-entry-inject-require';
const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral;

// Mark manifest file as external. As this will be generated later on.
(buildOptions.external ??= []).push('*/main.server.mjs', ...SERVER_GENERATED_EXTERNALS);
const inject: string[] = [ssrInjectManifestNamespace];
if (isNodePlatform) {
inject.unshift(ssrInjectRequireNamespace);
}

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');
}
const buildOptions: BuildOptions = {
...getEsBuildServerCommonOptions(options),
target,
entryPoints: {
// TODO: consider renaming to index
'server': ssrEntryNamespace,
},
supported: getFeatureSupport(target, true),
plugins: [
createSourcemapIgnorelistPlugin(),
createCompilerPlugin(
// JS/TS options
{ ...pluginOptions, noopTypeScriptCompilation: true },
// Component stylesheet bundler
stylesheetBundler,
),
],
inject,
};

buildOptions.plugins.push(
createServerBundleMetadata({ ssrEntryBundle: true }),
createVirtualModulePlugin({
namespace: ssrInjectRequireNamespace,
cache: sourceFileCache?.loadResultCache,
loadContent: () => {
const contents: string[] = [
// Note: Needed as esbuild does not provide require shims / proxy from ESModules.
// See: https://github.com/evanw/esbuild/issues/1921.
`import { createRequire } from 'node:module';`,
`globalThis['require'] ??= createRequire(import.meta.url);`,
];
buildOptions.plugins ??= [];

return {
contents: contents.join('\n'),
loader: 'js',
resolveDir: workspaceRoot,
};
},
}),
createVirtualModulePlugin({
namespace: ssrInjectManifestNamespace,
cache: sourceFileCache?.loadResultCache,
loadContent: () => {
const contents: string[] = [
// Configure `@angular/ssr` app engine manifest.
`import manifest from './${SERVER_APP_ENGINE_MANIFEST_FILENAME}';`,
`import { ɵsetAngularAppEngineManifest } from '@angular/ssr';`,
`ɵsetAngularAppEngineManifest(manifest);`,
];
if (externalPackages) {
buildOptions.packages = 'external';
} else {
buildOptions.plugins.push(createRxjsEsmResolutionPlugin());
}

return {
contents: contents.join('\n'),
loader: 'js',
resolveDir: workspaceRoot,
};
},
}),
createVirtualModulePlugin({
namespace: ssrEntryNamespace,
cache: sourceFileCache?.loadResultCache,
loadContent: () => {
const serverEntryPointJsImport = entryFileToWorkspaceRelative(
workspaceRoot,
serverEntryPoint,
);
const contents: string[] = [
// Re-export all symbols including default export
`import * as server from '${serverEntryPointJsImport}';`,
`export * from '${serverEntryPointJsImport}';`,
// The below is needed to avoid
// `Import "default" will always be undefined because there is no matching export` warning when no default is present.
`const defaultExportName = 'default';`,
`export default server[defaultExportName]`,
// Mark manifest file as external. As this will be generated later on.
(buildOptions.external ??= []).push('*/main.server.mjs', ...SERVER_GENERATED_EXTERNALS);

// Add @angular/ssr exports
`export { AngularAppEngine } from '@angular/ssr';`,
];
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'),
loader: 'js',
resolveDir: workspaceRoot,
};
},
}),
);
buildOptions.plugins.push(
createServerBundleMetadata({ ssrEntryBundle: true }),
createVirtualModulePlugin({
namespace: ssrInjectRequireNamespace,
cache: loadResultCache,
loadContent: () => {
const contents: string[] = [
// Note: Needed as esbuild does not provide require shims / proxy from ESModules.
// See: https://github.com/evanw/esbuild/issues/1921.
`import { createRequire } from 'node:module';`,
`globalThis['require'] ??= createRequire(import.meta.url);`,
];

return {
contents: contents.join('\n'),
loader: 'js',
resolveDir: workspaceRoot,
};
},
}),
createVirtualModulePlugin({
namespace: ssrInjectManifestNamespace,
cache: loadResultCache,
loadContent: () => {
const contents: string[] = [
// Configure `@angular/ssr` app engine manifest.
`import manifest from './${SERVER_APP_ENGINE_MANIFEST_FILENAME}';`,
`import { ɵsetAngularAppEngineManifest } from '@angular/ssr';`,
`ɵsetAngularAppEngineManifest(manifest);`,
];

return {
contents: contents.join('\n'),
loader: 'js',
resolveDir: workspaceRoot,
};
},
}),
createVirtualModulePlugin({
namespace: ssrEntryNamespace,
cache: loadResultCache,
loadContent: () => {
const serverEntryPointJsImport = entryFileToWorkspaceRelative(
workspaceRoot,
serverEntryPoint,
);
const contents: string[] = [
// Re-export all symbols including default export
`import * as server from '${serverEntryPointJsImport}';`,
`export * from '${serverEntryPointJsImport}';`,
// The below is needed to avoid
// `Import "default" will always be undefined because there is no matching export` warning when no default is present.
`const defaultExportName = 'default';`,
`export default server[defaultExportName]`,

// Add @angular/ssr exports
`export { AngularAppEngine } from '@angular/ssr';`,
];

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;
};
}

function getEsBuildServerCommonOptions(options: NormalizedApplicationBuildOptions): BuildOptions {
Expand Down
Loading