|
| 1 | +/** |
| 2 | + * @license |
| 3 | + * Copyright Google LLC All Rights Reserved. |
| 4 | + * |
| 5 | + * Use of this source code is governed by an MIT-style license that can be |
| 6 | + * found in the LICENSE file at https://angular.io/license |
| 7 | + */ |
| 8 | + |
| 9 | +import type { BuildOptions } from 'esbuild'; |
| 10 | +import assert from 'node:assert'; |
| 11 | +import path from 'node:path'; |
| 12 | +import type { NormalizedApplicationBuildOptions } from '../../builders/application/options'; |
| 13 | +import { allowMangle } from '../../utils/environment-options'; |
| 14 | +import { SourceFileCache, createCompilerPlugin } from './angular/compiler-plugin'; |
| 15 | +import { createCompilerPluginOptions } from './compiler-plugin-options'; |
| 16 | +import { createExternalPackagesPlugin } from './external-packages-plugin'; |
| 17 | +import { createRxjsEsmResolutionPlugin } from './rxjs-esm-resolution-plugin'; |
| 18 | +import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin'; |
| 19 | +import { getFeatureSupport } from './utils'; |
| 20 | +import { createVirtualModulePlugin } from './virtual-module-plugin'; |
| 21 | + |
| 22 | +export function createBrowserCodeBundleOptions( |
| 23 | + options: NormalizedApplicationBuildOptions, |
| 24 | + target: string[], |
| 25 | + sourceFileCache?: SourceFileCache, |
| 26 | +): BuildOptions { |
| 27 | + const { workspaceRoot, entryPoints, outputNames, jit } = options; |
| 28 | + |
| 29 | + const { pluginOptions, styleOptions } = createCompilerPluginOptions( |
| 30 | + options, |
| 31 | + target, |
| 32 | + sourceFileCache, |
| 33 | + ); |
| 34 | + |
| 35 | + const buildOptions: BuildOptions = { |
| 36 | + ...getEsBuildCommonOptions(options), |
| 37 | + platform: 'browser', |
| 38 | + // Note: `es2015` is needed for RxJS v6. If not specified, `module` would |
| 39 | + // match and the ES5 distribution would be bundled and ends up breaking at |
| 40 | + // runtime with the RxJS testing library. |
| 41 | + // More details: https://github.com/angular/angular-cli/issues/25405. |
| 42 | + mainFields: ['es2020', 'es2015', 'browser', 'module', 'main'], |
| 43 | + entryNames: outputNames.bundles, |
| 44 | + entryPoints, |
| 45 | + target, |
| 46 | + supported: getFeatureSupport(target), |
| 47 | + plugins: [ |
| 48 | + createSourcemapIngorelistPlugin(), |
| 49 | + createCompilerPlugin( |
| 50 | + // JS/TS options |
| 51 | + pluginOptions, |
| 52 | + // Component stylesheet options |
| 53 | + styleOptions, |
| 54 | + ), |
| 55 | + ], |
| 56 | + }; |
| 57 | + |
| 58 | + if (options.externalPackages) { |
| 59 | + buildOptions.plugins ??= []; |
| 60 | + buildOptions.plugins.push(createExternalPackagesPlugin()); |
| 61 | + } |
| 62 | + |
| 63 | + const polyfills = options.polyfills ? [...options.polyfills] : []; |
| 64 | + if (jit) { |
| 65 | + polyfills.push('@angular/compiler'); |
| 66 | + } |
| 67 | + |
| 68 | + if (polyfills?.length) { |
| 69 | + const namespace = 'angular:polyfills'; |
| 70 | + buildOptions.entryPoints = { |
| 71 | + ...buildOptions.entryPoints, |
| 72 | + 'polyfills': namespace, |
| 73 | + }; |
| 74 | + |
| 75 | + buildOptions.plugins?.unshift( |
| 76 | + createVirtualModulePlugin({ |
| 77 | + namespace, |
| 78 | + loadContent: () => ({ |
| 79 | + contents: polyfills.map((file) => `import '${file.replace(/\\/g, '/')}';`).join('\n'), |
| 80 | + loader: 'js', |
| 81 | + resolveDir: workspaceRoot, |
| 82 | + }), |
| 83 | + }), |
| 84 | + ); |
| 85 | + } |
| 86 | + |
| 87 | + return buildOptions; |
| 88 | +} |
| 89 | + |
| 90 | +/** |
| 91 | + * Create an esbuild 'build' options object for the server bundle. |
| 92 | + * @param options The builder's user-provider normalized options. |
| 93 | + * @returns An esbuild BuildOptions object. |
| 94 | + */ |
| 95 | +export function createServerCodeBundleOptions( |
| 96 | + options: NormalizedApplicationBuildOptions, |
| 97 | + target: string[], |
| 98 | + sourceFileCache: SourceFileCache, |
| 99 | +): BuildOptions { |
| 100 | + const { jit, serverEntryPoint, workspaceRoot } = options; |
| 101 | + |
| 102 | + assert( |
| 103 | + serverEntryPoint, |
| 104 | + 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.', |
| 105 | + ); |
| 106 | + |
| 107 | + const { pluginOptions, styleOptions } = createCompilerPluginOptions( |
| 108 | + options, |
| 109 | + target, |
| 110 | + sourceFileCache, |
| 111 | + ); |
| 112 | + |
| 113 | + const namespace = 'angular:server-entry'; |
| 114 | + |
| 115 | + const buildOptions: BuildOptions = { |
| 116 | + ...getEsBuildCommonOptions(options), |
| 117 | + platform: 'node', |
| 118 | + outExtension: { '.js': '.mjs' }, |
| 119 | + // Note: `es2015` is needed for RxJS v6. If not specified, `module` would |
| 120 | + // match and the ES5 distribution would be bundled and ends up breaking at |
| 121 | + // runtime with the RxJS testing library. |
| 122 | + // More details: https://github.com/angular/angular-cli/issues/25405. |
| 123 | + mainFields: ['es2020', 'es2015', 'module', 'main'], |
| 124 | + entryNames: '[name]', |
| 125 | + target, |
| 126 | + banner: { |
| 127 | + // Note: Needed as esbuild does not provide require shims / proxy from ESModules. |
| 128 | + // See: https://github.com/evanw/esbuild/issues/1921. |
| 129 | + js: [ |
| 130 | + `import { createRequire } from 'node:module';`, |
| 131 | + `globalThis['require'] ??= createRequire(import.meta.url);`, |
| 132 | + ].join('\n'), |
| 133 | + }, |
| 134 | + entryPoints: { |
| 135 | + 'server': namespace, |
| 136 | + }, |
| 137 | + supported: getFeatureSupport(target), |
| 138 | + plugins: [ |
| 139 | + createSourcemapIngorelistPlugin(), |
| 140 | + createCompilerPlugin( |
| 141 | + // JS/TS options |
| 142 | + { ...pluginOptions, noopTypeScriptCompilation: true }, |
| 143 | + // Component stylesheet options |
| 144 | + styleOptions, |
| 145 | + ), |
| 146 | + createVirtualModulePlugin({ |
| 147 | + namespace, |
| 148 | + loadContent: () => { |
| 149 | + const importAndExportDec: string[] = [ |
| 150 | + `import '@angular/platform-server/init';`, |
| 151 | + `import './${path.relative(workspaceRoot, serverEntryPoint).replace(/\\/g, '/')}';`, |
| 152 | + `export { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';`, |
| 153 | + ]; |
| 154 | + |
| 155 | + if (jit) { |
| 156 | + importAndExportDec.unshift(`import '@angular/compiler';`); |
| 157 | + } |
| 158 | + |
| 159 | + return { |
| 160 | + contents: importAndExportDec.join('\n'), |
| 161 | + loader: 'js', |
| 162 | + resolveDir: workspaceRoot, |
| 163 | + }; |
| 164 | + }, |
| 165 | + }), |
| 166 | + ], |
| 167 | + }; |
| 168 | + |
| 169 | + buildOptions.plugins ??= []; |
| 170 | + if (options.externalPackages) { |
| 171 | + buildOptions.plugins.push(createExternalPackagesPlugin()); |
| 172 | + } else { |
| 173 | + buildOptions.plugins.push(createRxjsEsmResolutionPlugin()); |
| 174 | + } |
| 175 | + |
| 176 | + return buildOptions; |
| 177 | +} |
| 178 | + |
| 179 | +function getEsBuildCommonOptions(options: NormalizedApplicationBuildOptions): BuildOptions { |
| 180 | + const { |
| 181 | + workspaceRoot, |
| 182 | + outExtension, |
| 183 | + optimizationOptions, |
| 184 | + sourcemapOptions, |
| 185 | + tsconfig, |
| 186 | + externalDependencies, |
| 187 | + outputNames, |
| 188 | + preserveSymlinks, |
| 189 | + jit, |
| 190 | + } = options; |
| 191 | + |
| 192 | + return { |
| 193 | + absWorkingDir: workspaceRoot, |
| 194 | + bundle: true, |
| 195 | + format: 'esm', |
| 196 | + assetNames: outputNames.media, |
| 197 | + conditions: ['es2020', 'es2015', 'module'], |
| 198 | + resolveExtensions: ['.ts', '.tsx', '.mjs', '.js'], |
| 199 | + metafile: true, |
| 200 | + legalComments: options.extractLicenses ? 'none' : 'eof', |
| 201 | + logLevel: options.verbose ? 'debug' : 'silent', |
| 202 | + minifyIdentifiers: optimizationOptions.scripts && allowMangle, |
| 203 | + minifySyntax: optimizationOptions.scripts, |
| 204 | + minifyWhitespace: optimizationOptions.scripts, |
| 205 | + pure: ['forwardRef'], |
| 206 | + outdir: workspaceRoot, |
| 207 | + outExtension: outExtension ? { '.js': `.${outExtension}` } : undefined, |
| 208 | + sourcemap: sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true), |
| 209 | + splitting: true, |
| 210 | + tsconfig, |
| 211 | + external: externalDependencies, |
| 212 | + write: false, |
| 213 | + preserveSymlinks, |
| 214 | + define: { |
| 215 | + // Only set to false when script optimizations are enabled. It should not be set to true because |
| 216 | + // Angular turns `ngDevMode` into an object for development debugging purposes when not defined |
| 217 | + // which a constant true value would break. |
| 218 | + ...(optimizationOptions.scripts ? { 'ngDevMode': 'false' } : undefined), |
| 219 | + 'ngJitMode': jit ? 'true' : 'false', |
| 220 | + }, |
| 221 | + }; |
| 222 | +} |
0 commit comments