|
7 | 7 | */
|
8 | 8 |
|
9 | 9 | import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
|
10 |
| -import type { BuildOptions, OutputFile } from 'esbuild'; |
| 10 | +import type { OutputFile } from 'esbuild'; |
11 | 11 | import fs from 'node:fs/promises';
|
12 | 12 | import path from 'node:path';
|
13 |
| -import { SourceFileCache, createCompilerPlugin } from '../../tools/esbuild/angular/compiler-plugin'; |
| 13 | +import { SourceFileCache } from '../../tools/esbuild/angular/compiler-plugin'; |
| 14 | +import { createCodeBundleOptions } from '../../tools/esbuild/application-code-bundle'; |
14 | 15 | import { BundlerContext } from '../../tools/esbuild/bundler-context';
|
| 16 | +import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result'; |
15 | 17 | import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker';
|
16 |
| -import { createExternalPackagesPlugin } from '../../tools/esbuild/external-packages-plugin'; |
17 | 18 | import { createGlobalScriptsBundleOptions } from '../../tools/esbuild/global-scripts';
|
18 | 19 | import { createGlobalStylesBundleOptions } from '../../tools/esbuild/global-styles';
|
| 20 | +import { generateIndexHtml } from '../../tools/esbuild/index-html-generator'; |
19 | 21 | import { extractLicenses } from '../../tools/esbuild/license-extractor';
|
20 |
| -import { createSourcemapIngorelistPlugin } from '../../tools/esbuild/sourcemap-ignorelist-plugin'; |
21 | 22 | import { shutdownSassWorkerPool } from '../../tools/esbuild/stylesheets/sass-language';
|
22 | 23 | import {
|
23 | 24 | calculateEstimatedTransferSizes,
|
24 |
| - createOutputFileFromText, |
25 |
| - getFeatureSupport, |
26 | 25 | logBuildStats,
|
27 | 26 | logMessages,
|
28 | 27 | withNoProgress,
|
29 | 28 | withSpinner,
|
30 | 29 | writeResultFiles,
|
31 | 30 | } from '../../tools/esbuild/utils';
|
32 |
| -import { createVirtualModulePlugin } from '../../tools/esbuild/virtual-module-plugin'; |
33 |
| -import type { ChangedFiles } from '../../tools/esbuild/watcher'; |
34 | 31 | import { copyAssets } from '../../utils/copy-assets';
|
35 | 32 | import { assertIsError } from '../../utils/error';
|
36 | 33 | import { transformSupportedBrowsersToTargets } from '../../utils/esbuild-targets';
|
37 |
| -import { IndexHtmlGenerator } from '../../utils/index-file/index-html-generator'; |
38 | 34 | import { augmentAppWithServiceWorkerEsbuild } from '../../utils/service-worker';
|
39 | 35 | import { getSupportedBrowsers } from '../../utils/supported-browsers';
|
40 | 36 | import { logBuilderStatusWarnings } from './builder-status-warnings';
|
41 | 37 | import { BrowserEsbuildOptions, NormalizedBrowserOptions, normalizeOptions } from './options';
|
42 | 38 | import { Schema as BrowserBuilderOptions } from './schema';
|
43 | 39 |
|
44 |
| -interface RebuildState { |
45 |
| - rebuildContexts: BundlerContext[]; |
46 |
| - codeBundleCache?: SourceFileCache; |
47 |
| - fileChanges: ChangedFiles; |
48 |
| -} |
49 |
| - |
50 |
| -/** |
51 |
| - * Represents the result of a single builder execute call. |
52 |
| - */ |
53 |
| -class ExecutionResult { |
54 |
| - readonly outputFiles: OutputFile[] = []; |
55 |
| - readonly assetFiles: { source: string; destination: string }[] = []; |
56 |
| - |
57 |
| - constructor( |
58 |
| - private rebuildContexts: BundlerContext[], |
59 |
| - private codeBundleCache?: SourceFileCache, |
60 |
| - ) {} |
61 |
| - |
62 |
| - addOutputFile(path: string, content: string): void { |
63 |
| - this.outputFiles.push(createOutputFileFromText(path, content)); |
64 |
| - } |
65 |
| - |
66 |
| - get output() { |
67 |
| - return { |
68 |
| - success: this.outputFiles.length > 0, |
69 |
| - }; |
70 |
| - } |
71 |
| - |
72 |
| - get outputWithFiles() { |
73 |
| - return { |
74 |
| - success: this.outputFiles.length > 0, |
75 |
| - outputFiles: this.outputFiles, |
76 |
| - assetFiles: this.assetFiles, |
77 |
| - }; |
78 |
| - } |
79 |
| - |
80 |
| - get watchFiles() { |
81 |
| - return this.codeBundleCache?.referencedFiles ?? []; |
82 |
| - } |
83 |
| - |
84 |
| - createRebuildState(fileChanges: ChangedFiles): RebuildState { |
85 |
| - this.codeBundleCache?.invalidate([...fileChanges.modified, ...fileChanges.removed]); |
86 |
| - |
87 |
| - return { |
88 |
| - rebuildContexts: this.rebuildContexts, |
89 |
| - codeBundleCache: this.codeBundleCache, |
90 |
| - fileChanges, |
91 |
| - }; |
92 |
| - } |
93 |
| - |
94 |
| - async dispose(): Promise<void> { |
95 |
| - await Promise.allSettled(this.rebuildContexts.map((context) => context.dispose())); |
96 |
| - } |
97 |
| -} |
98 |
| - |
99 |
| -// eslint-disable-next-line max-lines-per-function |
100 | 40 | async function execute(
|
101 | 41 | options: NormalizedBrowserOptions,
|
102 | 42 | context: BuilderContext,
|
@@ -189,58 +129,11 @@ async function execute(
|
189 | 129 |
|
190 | 130 | // Generate index HTML file
|
191 | 131 | if (indexHtmlOptions) {
|
192 |
| - // Analyze metafile for initial link-based hints. |
193 |
| - // Skip if the internal externalPackages option is enabled since this option requires |
194 |
| - // dev server cooperation to properly resolve and fetch imports. |
195 |
| - const hints = []; |
196 |
| - if (!options.externalPackages) { |
197 |
| - for (const [key, value] of initialFiles) { |
198 |
| - if (value.entrypoint) { |
199 |
| - // Entry points are already referenced in the HTML |
200 |
| - continue; |
201 |
| - } |
202 |
| - if (value.type === 'script') { |
203 |
| - hints.push({ url: key, mode: 'modulepreload' as const }); |
204 |
| - } else if (value.type === 'style') { |
205 |
| - hints.push({ url: key, mode: 'preload' as const }); |
206 |
| - } |
207 |
| - } |
208 |
| - } |
209 |
| - |
210 |
| - // Create an index HTML generator that reads from the in-memory output files |
211 |
| - const indexHtmlGenerator = new IndexHtmlGenerator({ |
212 |
| - indexPath: indexHtmlOptions.input, |
213 |
| - entrypoints: indexHtmlOptions.insertionOrder, |
214 |
| - sri: options.subresourceIntegrity, |
215 |
| - optimization: optimizationOptions, |
216 |
| - crossOrigin: options.crossOrigin, |
217 |
| - }); |
218 |
| - |
219 |
| - /** Virtual output path to support reading in-memory files. */ |
220 |
| - const virtualOutputPath = '/'; |
221 |
| - indexHtmlGenerator.readAsset = async function (filePath: string): Promise<string> { |
222 |
| - // Remove leading directory separator |
223 |
| - const relativefilePath = path.relative(virtualOutputPath, filePath); |
224 |
| - const file = executionResult.outputFiles.find((file) => file.path === relativefilePath); |
225 |
| - if (file) { |
226 |
| - return file.text; |
227 |
| - } |
228 |
| - |
229 |
| - throw new Error(`Output file does not exist: ${path}`); |
230 |
| - }; |
231 |
| - |
232 |
| - const { content, warnings, errors } = await indexHtmlGenerator.process({ |
233 |
| - baseHref: options.baseHref, |
234 |
| - lang: undefined, |
235 |
| - outputPath: virtualOutputPath, |
236 |
| - files: [...initialFiles].map(([file, record]) => ({ |
237 |
| - name: record.name ?? '', |
238 |
| - file, |
239 |
| - extension: path.extname(file), |
240 |
| - })), |
241 |
| - hints, |
242 |
| - }); |
243 |
| - |
| 132 | + const { errors, warnings, content } = await generateIndexHtml( |
| 133 | + initialFiles, |
| 134 | + executionResult, |
| 135 | + options, |
| 136 | + ); |
244 | 137 | for (const error of errors) {
|
245 | 138 | context.logger.error(error);
|
246 | 139 | }
|
@@ -303,131 +196,6 @@ async function execute(
|
303 | 196 | return executionResult;
|
304 | 197 | }
|
305 | 198 |
|
306 |
| -function createCodeBundleOptions( |
307 |
| - options: NormalizedBrowserOptions, |
308 |
| - target: string[], |
309 |
| - browsers: string[], |
310 |
| - sourceFileCache?: SourceFileCache, |
311 |
| -): BuildOptions { |
312 |
| - const { |
313 |
| - workspaceRoot, |
314 |
| - entryPoints, |
315 |
| - optimizationOptions, |
316 |
| - sourcemapOptions, |
317 |
| - tsconfig, |
318 |
| - outputNames, |
319 |
| - outExtension, |
320 |
| - fileReplacements, |
321 |
| - externalDependencies, |
322 |
| - preserveSymlinks, |
323 |
| - stylePreprocessorOptions, |
324 |
| - advancedOptimizations, |
325 |
| - inlineStyleLanguage, |
326 |
| - jit, |
327 |
| - tailwindConfiguration, |
328 |
| - } = options; |
329 |
| - |
330 |
| - const buildOptions: BuildOptions = { |
331 |
| - absWorkingDir: workspaceRoot, |
332 |
| - bundle: true, |
333 |
| - format: 'esm', |
334 |
| - entryPoints, |
335 |
| - entryNames: outputNames.bundles, |
336 |
| - assetNames: outputNames.media, |
337 |
| - target, |
338 |
| - supported: getFeatureSupport(target), |
339 |
| - mainFields: ['es2020', 'browser', 'module', 'main'], |
340 |
| - conditions: ['es2020', 'es2015', 'module'], |
341 |
| - resolveExtensions: ['.ts', '.tsx', '.mjs', '.js'], |
342 |
| - metafile: true, |
343 |
| - legalComments: options.extractLicenses ? 'none' : 'eof', |
344 |
| - logLevel: options.verbose ? 'debug' : 'silent', |
345 |
| - minify: optimizationOptions.scripts, |
346 |
| - pure: ['forwardRef'], |
347 |
| - outdir: workspaceRoot, |
348 |
| - outExtension: outExtension ? { '.js': `.${outExtension}` } : undefined, |
349 |
| - sourcemap: sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true), |
350 |
| - splitting: true, |
351 |
| - tsconfig, |
352 |
| - external: externalDependencies, |
353 |
| - write: false, |
354 |
| - platform: 'browser', |
355 |
| - preserveSymlinks, |
356 |
| - plugins: [ |
357 |
| - createSourcemapIngorelistPlugin(), |
358 |
| - createCompilerPlugin( |
359 |
| - // JS/TS options |
360 |
| - { |
361 |
| - sourcemap: !!sourcemapOptions.scripts, |
362 |
| - thirdPartySourcemaps: sourcemapOptions.vendor, |
363 |
| - tsconfig, |
364 |
| - jit, |
365 |
| - advancedOptimizations, |
366 |
| - fileReplacements, |
367 |
| - sourceFileCache, |
368 |
| - loadResultCache: sourceFileCache?.loadResultCache, |
369 |
| - }, |
370 |
| - // Component stylesheet options |
371 |
| - { |
372 |
| - workspaceRoot, |
373 |
| - optimization: !!optimizationOptions.styles.minify, |
374 |
| - sourcemap: |
375 |
| - // Hidden component stylesheet sourcemaps are inaccessible which is effectively |
376 |
| - // the same as being disabled. Disabling has the advantage of avoiding the overhead |
377 |
| - // of sourcemap processing. |
378 |
| - !!sourcemapOptions.styles && (sourcemapOptions.hidden ? false : 'inline'), |
379 |
| - outputNames, |
380 |
| - includePaths: stylePreprocessorOptions?.includePaths, |
381 |
| - externalDependencies, |
382 |
| - target, |
383 |
| - inlineStyleLanguage, |
384 |
| - preserveSymlinks, |
385 |
| - browsers, |
386 |
| - tailwindConfiguration, |
387 |
| - }, |
388 |
| - ), |
389 |
| - ], |
390 |
| - define: { |
391 |
| - // Only set to false when script optimizations are enabled. It should not be set to true because |
392 |
| - // Angular turns `ngDevMode` into an object for development debugging purposes when not defined |
393 |
| - // which a constant true value would break. |
394 |
| - ...(optimizationOptions.scripts ? { 'ngDevMode': 'false' } : undefined), |
395 |
| - 'ngJitMode': jit ? 'true' : 'false', |
396 |
| - }, |
397 |
| - }; |
398 |
| - |
399 |
| - if (options.externalPackages) { |
400 |
| - buildOptions.plugins ??= []; |
401 |
| - buildOptions.plugins.push(createExternalPackagesPlugin()); |
402 |
| - } |
403 |
| - |
404 |
| - const polyfills = options.polyfills ? [...options.polyfills] : []; |
405 |
| - if (jit) { |
406 |
| - polyfills.push('@angular/compiler'); |
407 |
| - } |
408 |
| - |
409 |
| - if (polyfills?.length) { |
410 |
| - const namespace = 'angular:polyfills'; |
411 |
| - buildOptions.entryPoints = { |
412 |
| - ...buildOptions.entryPoints, |
413 |
| - ['polyfills']: namespace, |
414 |
| - }; |
415 |
| - |
416 |
| - buildOptions.plugins?.unshift( |
417 |
| - createVirtualModulePlugin({ |
418 |
| - namespace, |
419 |
| - loadContent: () => ({ |
420 |
| - contents: polyfills.map((file) => `import '${file.replace(/\\/g, '/')}';`).join('\n'), |
421 |
| - loader: 'js', |
422 |
| - resolveDir: workspaceRoot, |
423 |
| - }), |
424 |
| - }), |
425 |
| - ); |
426 |
| - } |
427 |
| - |
428 |
| - return buildOptions; |
429 |
| -} |
430 |
| - |
431 | 199 | /**
|
432 | 200 | * Main execution function for the esbuild-based application builder.
|
433 | 201 | * The options are compatible with the Webpack-based builder.
|
|
0 commit comments