"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createBrowserCodeBundleOptions = createBrowserCodeBundleOptions; exports.createBrowserPolyfillBundleOptions = createBrowserPolyfillBundleOptions; exports.createServerPolyfillBundleOptions = createServerPolyfillBundleOptions; exports.createServerMainCodeBundleOptions = createServerMainCodeBundleOptions; exports.createSsrEntryCodeBundleOptions = createSsrEntryCodeBundleOptions; const node_assert_1 = __importDefault(require("node:assert")); const node_crypto_1 = require("node:crypto"); const node_path_1 = require("node:path"); const schema_1 = require("../../builders/application/schema"); const environment_options_1 = require("../../utils/environment-options"); const manifest_1 = require("../../utils/server-rendering/manifest"); const compilation_1 = require("../angular/compilation"); const compiler_plugin_1 = require("./angular/compiler-plugin"); const angular_localize_init_warning_plugin_1 = require("./angular-localize-init-warning-plugin"); const compiler_plugin_options_1 = require("./compiler-plugin-options"); const external_packages_plugin_1 = require("./external-packages-plugin"); const i18n_locale_plugin_1 = require("./i18n-locale-plugin"); const loader_import_attribute_plugin_1 = require("./loader-import-attribute-plugin"); const rxjs_esm_resolution_plugin_1 = require("./rxjs-esm-resolution-plugin"); const server_bundle_metadata_plugin_1 = require("./server-bundle-metadata-plugin"); const sourcemap_ignorelist_plugin_1 = require("./sourcemap-ignorelist-plugin"); const utils_1 = require("./utils"); const virtual_module_plugin_1 = require("./virtual-module-plugin"); const wasm_plugin_1 = require("./wasm-plugin"); function createBrowserCodeBundleOptions(options, target, sourceFileCache, stylesheetBundler, angularCompilation, templateUpdates) { return (loadCache) => { const { entryPoints, outputNames, polyfills } = options; const zoneless = (0, utils_1.isZonelessApp)(polyfills); const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache, loadCache, templateUpdates); const 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: (0, utils_1.getFeatureSupport)(target, zoneless), }; buildOptions.plugins ??= []; buildOptions.plugins.push((0, wasm_plugin_1.createWasmPlugin)({ allowAsync: zoneless, cache: loadCache }), (0, angular_localize_init_warning_plugin_1.createAngularLocalizeInitWarningPlugin)(), (0, compiler_plugin_1.createCompilerPlugin)( // JS/TS options pluginOptions, angularCompilation, // Component stylesheet bundler stylesheetBundler)); if (options.plugins) { buildOptions.plugins.push(...options.plugins); } return buildOptions; }; } function createBrowserPolyfillBundleOptions(options, target, sourceFileCache, stylesheetBundler) { const namespace = 'angular:polyfills'; const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions(options, namespace, true, sourceFileCache.loadResultCache); if (!polyfillBundleOptions) { return; } const { outputNames, polyfills } = options; const hasTypeScriptEntries = polyfills?.some((entry) => /\.[cm]?tsx?$/.test(entry)); const buildOptions = { ...polyfillBundleOptions, 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, target, entryPoints: { 'polyfills': namespace, }, }; // Only add the Angular TypeScript compiler if TypeScript files are provided in the polyfills if (hasTypeScriptEntries) { buildOptions.plugins ??= []; const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache); buildOptions.plugins.push((0, compiler_plugin_1.createCompilerPlugin)( // JS/TS options pluginOptions, // Browser compilation handles the actual Angular code compilation new compilation_1.NoopCompilation(), // Component stylesheet options are unused for polyfills but required by the plugin stylesheetBundler)); } // Use an options factory to allow fully incremental bundling when no TypeScript files are present. // The TypeScript compilation is not currently integrated into the bundler invalidation so // cannot be used with fully incremental bundling yet. return hasTypeScriptEntries ? buildOptions : () => buildOptions; } function createServerPolyfillBundleOptions(options, target, loadResultCache) { const serverPolyfills = []; const polyfillsFromConfig = new Set(options.polyfills); const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral; if (!(0, utils_1.isZonelessApp)(options.polyfills)) { serverPolyfills.push(isNodePlatform ? 'zone.js/node' : 'zone.js'); } if (polyfillsFromConfig.has('@angular/localize') || polyfillsFromConfig.has('@angular/localize/init')) { serverPolyfills.push('@angular/localize/init'); } serverPolyfills.push('@angular/platform-server/init'); const namespace = 'angular:polyfills-server'; const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions({ ...options, polyfills: serverPolyfills, }, namespace, false, loadResultCache); if (!polyfillBundleOptions) { return; } const jsBanner = []; if (polyfillBundleOptions.external?.length) { jsBanner.push(`globalThis['ngServerMode'] = true;`); } if (isNodePlatform) { // Note: Needed as esbuild does not provide require shims / proxy from ESModules. // See: https://github.com/evanw/esbuild/issues/1921. jsBanner.push(`import { createRequire } from 'node:module';`, `globalThis['require'] ??= createRequire(import.meta.url);`); } const buildOptions = { ...polyfillBundleOptions, platform: isNodePlatform ? 'node' : 'neutral', outExtension: { '.js': '.mjs' }, // 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', 'module', 'main'], entryNames: '[name]', banner: { js: jsBanner.join('\n'), }, target, entryPoints: { 'polyfills.server': namespace, }, }; buildOptions.plugins ??= []; buildOptions.plugins.push((0, server_bundle_metadata_plugin_1.createServerBundleMetadata)()); return () => buildOptions; } function createServerMainCodeBundleOptions(options, target, sourceFileCache, stylesheetBundler) { const { serverEntryPoint: mainServerEntryPoint, workspaceRoot, outputMode, externalPackages, ssrOptions, polyfills, } = options; (0, node_assert_1.default)(mainServerEntryPoint, 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.'); return (loadResultCache) => { const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache, loadResultCache); const mainServerNamespace = 'angular:main-server'; const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest'; const zoneless = (0, utils_1.isZonelessApp)(polyfills); const entryPoints = { 'main.server': mainServerNamespace, }; const ssrEntryPoint = ssrOptions?.entry; const isOldBehaviour = !outputMode; 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 buildOptions = { ...getEsBuildServerCommonOptions(options), target, banner: { js: `import './polyfills.server.mjs';`, }, entryPoints, supported: (0, utils_1.getFeatureSupport)(target, zoneless), }; buildOptions.plugins ??= []; buildOptions.plugins.push((0, wasm_plugin_1.createWasmPlugin)({ allowAsync: zoneless, cache: loadResultCache }), (0, angular_localize_init_warning_plugin_1.createAngularLocalizeInitWarningPlugin)(), (0, compiler_plugin_1.createCompilerPlugin)( // JS/TS options pluginOptions, // Browser compilation handles the actual Angular code compilation new compilation_1.NoopCompilation(), // Component stylesheet bundler stylesheetBundler)); if (!externalPackages) { buildOptions.plugins.push((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)()); } // Mark manifest and polyfills file as external as these are generated by a different bundle step. (buildOptions.external ??= []).push(...utils_1.SERVER_GENERATED_EXTERNALS); const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral; 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'); } buildOptions.plugins.push((0, server_bundle_metadata_plugin_1.createServerBundleMetadata)(), (0, virtual_module_plugin_1.createVirtualModulePlugin)({ namespace: mainServerInjectManifestNamespace, cache: loadResultCache, entryPointOnly: false, loadContent: async () => { const contents = [ // Configure `@angular/ssr` manifest. `import manifest from './${manifest_1.SERVER_APP_MANIFEST_FILENAME}';`, `import { ɵsetAngularAppManifest } from '@angular/ssr';`, `ɵsetAngularAppManifest(manifest);`, ]; return { contents: contents.join('\n'), loader: 'js', resolveDir: workspaceRoot, }; }, }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({ namespace: mainServerNamespace, cache: loadResultCache, loadContent: async () => { const mainServerEntryPointJsImport = entryFileToWorkspaceRelative(workspaceRoot, mainServerEntryPoint); const contents = [ // Inject manifest `import '${mainServerInjectManifestNamespace}';`, // Add @angular/ssr exports `export { ɵdestroyAngularServerApp, ɵextractRoutesAndCreateRouteTree, ɵgetOrCreateAngularServerApp, } from '@angular/ssr';`, // Need for HMR `export { ɵresetCompiledComponents } from '@angular/core';`, // Re-export all symbols including default export from 'main.server.ts' `export { default } from '${mainServerEntryPointJsImport}';`, `export * from '${mainServerEntryPointJsImport}';`, ]; return { contents: contents.join('\n'), loader: 'js', resolveDir: workspaceRoot, }; }, })); if (options.plugins) { buildOptions.plugins.push(...options.plugins); } return buildOptions; }; } function createSsrEntryCodeBundleOptions(options, target, sourceFileCache, stylesheetBundler) { const { workspaceRoot, ssrOptions, externalPackages } = options; const serverEntryPoint = ssrOptions?.entry; (0, node_assert_1.default)(serverEntryPoint, 'createSsrEntryCodeBundleOptions should not be called without a defined serverEntryPoint.'); return (loadResultCache) => { const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache, loadResultCache); const ssrEntryNamespace = 'angular:ssr-entry'; const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest'; const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral; const jsBanner = []; if (options.externalDependencies?.length) { jsBanner.push(`globalThis['ngServerMode'] = true;`); } if (isNodePlatform) { // Note: Needed as esbuild does not provide require shims / proxy from ESModules. // See: https://github.com/evanw/esbuild/issues/1921. jsBanner.push(`import { createRequire } from 'node:module';`, `globalThis['require'] ??= createRequire(import.meta.url);`); } const buildOptions = { ...getEsBuildServerCommonOptions(options), target, banner: { js: jsBanner.join('\n'), }, entryPoints: { 'server': ssrEntryNamespace, }, supported: (0, utils_1.getFeatureSupport)(target, true), }; buildOptions.plugins ??= []; buildOptions.plugins.push((0, angular_localize_init_warning_plugin_1.createAngularLocalizeInitWarningPlugin)(), (0, compiler_plugin_1.createCompilerPlugin)( // JS/TS options pluginOptions, // Browser compilation handles the actual Angular code compilation new compilation_1.NoopCompilation(), // Component stylesheet bundler stylesheetBundler)); if (!externalPackages) { buildOptions.plugins.push((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)()); } // Mark manifest file as external. As this will be generated later on. (buildOptions.external ??= []).push('*/main.server.mjs', ...utils_1.SERVER_GENERATED_EXTERNALS); 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'); } buildOptions.plugins.push((0, server_bundle_metadata_plugin_1.createServerBundleMetadata)({ ssrEntryBundle: true }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({ namespace: ssrInjectManifestNamespace, cache: loadResultCache, entryPointOnly: false, loadContent: () => { const contents = [ // Configure `@angular/ssr` app engine manifest. `import manifest from './${manifest_1.SERVER_APP_ENGINE_MANIFEST_FILENAME}';`, `import { ɵsetAngularAppEngineManifest } from '@angular/ssr';`, `ɵsetAngularAppEngineManifest(manifest);`, ]; return { contents: contents.join('\n'), loader: 'js', resolveDir: workspaceRoot, }; }, }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({ namespace: ssrEntryNamespace, cache: loadResultCache, loadContent: () => { const serverEntryPointJsImport = entryFileToWorkspaceRelative(workspaceRoot, serverEntryPoint); const contents = [ // Configure `@angular/ssr` app engine manifest. `import '${ssrInjectManifestNamespace}';`, // 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); } return buildOptions; }; } function getEsBuildServerCommonOptions(options) { const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral; const commonOptions = getEsBuildCommonOptions(options); commonOptions.define ??= {}; commonOptions.define['ngServerMode'] = 'true'; return { ...commonOptions, platform: isNodePlatform ? 'node' : 'neutral', outExtension: { '.js': '.mjs' }, // 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', 'module', 'main'], entryNames: '[name]', }; } function getEsBuildCommonOptions(options) { const { workspaceRoot, outExtension, optimizationOptions, sourcemapOptions, tsconfig, externalDependencies, outputNames, preserveSymlinks, jit, loaderExtensions, jsonLogs, i18nOptions, customConditions, frameworkVersion, } = options; // Ensure unique hashes for i18n translation changes when using post-process inlining. // This hash value is added as a footer to each file and ensures that the output file names (with hashes) // change when translation files have changed. If this is not done the post processed files may have // different content but would retain identical production file names which would lead to browser caching problems. let footer; if (i18nOptions.shouldInline) { // Update file hashes to include translation file content const i18nHash = Object.values(i18nOptions.locales).reduce((data, locale) => data + locale.files.map((file) => file.integrity || '').join('|'), ''); footer = { js: `/**i18n:${(0, node_crypto_1.createHash)('sha256').update(i18nHash).digest('hex')}*/` }; } // Core conditions that are always included const conditions = [ // Required to support rxjs 7.x which will use es5 code if this condition is not present 'es2015', 'es2020', ]; // The pre-linked code is not used with JIT for two reasons: // 1) The pre-linked code may not have the metadata included that is required for JIT // 2) The CLI is otherwise setup to use runtime linking for JIT to match the application template compilation if (!jit) { // The pre-linked package condition is based on the framework version. // Currently this is specific to each patch version of the framework. conditions.push('angular:linked-' + frameworkVersion); } // Append custom conditions if present if (customConditions) { conditions.push(...customConditions); } else { // Include default conditions conditions.push('module', optimizationOptions.scripts ? 'production' : 'development'); } const plugins = [ (0, loader_import_attribute_plugin_1.createLoaderImportAttributePlugin)(), (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(), ]; let packages = 'bundle'; 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 plugins.push((0, external_packages_plugin_1.createExternalPackagesPlugin)(options.externalPackages !== true ? options.externalPackages : undefined)); packages = 'bundle'; } else { // Safe to use the packages external option directly packages = 'external'; } } return { absWorkingDir: workspaceRoot, format: 'esm', bundle: true, packages, assetNames: outputNames.media, conditions, resolveExtensions: ['.ts', '.tsx', '.mjs', '.js', '.cjs'], metafile: true, legalComments: options.extractLicenses ? 'none' : 'eof', logLevel: options.verbose && !jsonLogs ? 'debug' : 'silent', minifyIdentifiers: optimizationOptions.scripts && environment_options_1.allowMangle, minifySyntax: optimizationOptions.scripts, minifyWhitespace: optimizationOptions.scripts, pure: ['forwardRef'], outdir: workspaceRoot, outExtension: outExtension ? { '.js': `.${outExtension}` } : undefined, sourcemap: sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true), sourcesContent: sourcemapOptions.sourcesContent, splitting: true, chunkNames: options.namedChunks ? '[name]-[hash]' : 'chunk-[hash]', tsconfig, external: externalDependencies ? [...externalDependencies] : undefined, write: false, preserveSymlinks, define: { ...options.define, // Only set to false when script optimizations are enabled. It should not be set to true because // Angular turns `ngDevMode` into an object for development debugging purposes when not defined // which a constant true value would break. ...(optimizationOptions.scripts ? { 'ngDevMode': 'false' } : undefined), 'ngJitMode': jit ? 'true' : 'false', 'ngServerMode': 'false', 'ngHmrMode': options.templateUpdates ? 'true' : 'false', }, loader: loaderExtensions, footer, plugins, }; } function getEsBuildCommonPolyfillsOptions(options, namespace, tryToResolvePolyfillsAsRelative, loadResultCache) { const { jit, workspaceRoot, i18nOptions } = options; const buildOptions = getEsBuildCommonOptions(options); buildOptions.splitting = false; buildOptions.plugins ??= []; let polyfills = options.polyfills ? [...options.polyfills] : []; // Angular JIT mode requires the runtime compiler if (jit) { polyfills.unshift('@angular/compiler'); } // Add Angular's global locale data if i18n options are present. // Locale data should go first so that project provided polyfill code can augment if needed. let needLocaleDataPlugin = false; if (i18nOptions.shouldInline) { // Remove localize polyfill as this is not needed for build time i18n. polyfills = polyfills.filter((path) => !path.startsWith('@angular/localize')); // Add locale data for all active locales // TODO: Inject each individually within the inlining process itself for (const locale of i18nOptions.inlineLocales) { polyfills.unshift(`angular:locale/data:${locale}`); } needLocaleDataPlugin = true; } else if (i18nOptions.hasDefinedSourceLocale) { // When not inlining and a source local is present, use the source locale data directly polyfills.unshift(`angular:locale/data:${i18nOptions.sourceLocale}`); needLocaleDataPlugin = true; } if (needLocaleDataPlugin) { buildOptions.plugins.push((0, i18n_locale_plugin_1.createAngularLocaleDataPlugin)()); } if (polyfills.length === 0) { return; } buildOptions.plugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({ namespace, cache: loadResultCache, loadContent: async (_, build) => { let polyfillPaths = polyfills; let warnings; if (tryToResolvePolyfillsAsRelative) { polyfillPaths = await Promise.all(polyfills.map(async (path) => { if (path.startsWith('zone.js') || !(0, node_path_1.extname)(path)) { return path; } const potentialPathRelative = './' + path; const result = await build.resolve(potentialPathRelative, { kind: 'import-statement', resolveDir: workspaceRoot, }); return result.path ? potentialPathRelative : path; })); } // Generate module contents with an import statement per defined polyfill let contents = polyfillPaths .map((file) => `import '${file.replace(/\\/g, '/')}';`) .join('\n'); // The below should be done after loading `$localize` as otherwise the locale will be overridden. if (i18nOptions.shouldInline) { // When inlining, a placeholder is used to allow the post-processing step to inject the $localize locale identifier. contents += '(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";\n'; } else if (i18nOptions.hasDefinedSourceLocale) { // If not inlining translations and source locale is defined, inject the locale specifier. contents += `(globalThis.$localize ??= {}).locale = "${i18nOptions.sourceLocale}";\n`; } return { contents, loader: 'js', warnings, resolveDir: workspaceRoot, }; }, })); return buildOptions; } function entryFileToWorkspaceRelative(workspaceRoot, entryFile) { return ('./' + (0, node_path_1.relative)(workspaceRoot, entryFile) .replace(/.[mc]?ts$/, '') .replace(/\\/g, '/')); }