Skip to content

Commit 6bebc45

Browse files
committed
refactor: move esbuild index generator, code bundle option and execution results
This commit extracts code in separate files.
1 parent 466d86d commit 6bebc45

File tree

4 files changed

+304
-242
lines changed

4 files changed

+304
-242
lines changed

packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts

Lines changed: 10 additions & 242 deletions
Original file line numberDiff line numberDiff line change
@@ -7,96 +7,36 @@
77
*/
88

99
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
10-
import type { BuildOptions, OutputFile } from 'esbuild';
10+
import type { OutputFile } from 'esbuild';
1111
import fs from 'node:fs/promises';
1212
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';
1415
import { BundlerContext } from '../../tools/esbuild/bundler-context';
16+
import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result';
1517
import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker';
16-
import { createExternalPackagesPlugin } from '../../tools/esbuild/external-packages-plugin';
1718
import { createGlobalScriptsBundleOptions } from '../../tools/esbuild/global-scripts';
1819
import { createGlobalStylesBundleOptions } from '../../tools/esbuild/global-styles';
20+
import { generateIndexHtml } from '../../tools/esbuild/index-html-generator';
1921
import { extractLicenses } from '../../tools/esbuild/license-extractor';
20-
import { createSourcemapIngorelistPlugin } from '../../tools/esbuild/sourcemap-ignorelist-plugin';
2122
import { shutdownSassWorkerPool } from '../../tools/esbuild/stylesheets/sass-language';
2223
import {
2324
calculateEstimatedTransferSizes,
24-
createOutputFileFromText,
25-
getFeatureSupport,
2625
logBuildStats,
2726
logMessages,
2827
withNoProgress,
2928
withSpinner,
3029
writeResultFiles,
3130
} from '../../tools/esbuild/utils';
32-
import { createVirtualModulePlugin } from '../../tools/esbuild/virtual-module-plugin';
33-
import type { ChangedFiles } from '../../tools/esbuild/watcher';
3431
import { copyAssets } from '../../utils/copy-assets';
3532
import { assertIsError } from '../../utils/error';
3633
import { transformSupportedBrowsersToTargets } from '../../utils/esbuild-targets';
37-
import { IndexHtmlGenerator } from '../../utils/index-file/index-html-generator';
3834
import { augmentAppWithServiceWorkerEsbuild } from '../../utils/service-worker';
3935
import { getSupportedBrowsers } from '../../utils/supported-browsers';
4036
import { logBuilderStatusWarnings } from './builder-status-warnings';
4137
import { BrowserEsbuildOptions, NormalizedBrowserOptions, normalizeOptions } from './options';
4238
import { Schema as BrowserBuilderOptions } from './schema';
4339

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
10040
async function execute(
10141
options: NormalizedBrowserOptions,
10242
context: BuilderContext,
@@ -189,58 +129,11 @@ async function execute(
189129

190130
// Generate index HTML file
191131
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+
);
244137
for (const error of errors) {
245138
context.logger.error(error);
246139
}
@@ -303,131 +196,6 @@ async function execute(
303196
return executionResult;
304197
}
305198

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-
431199
/**
432200
* Main execution function for the esbuild-based application builder.
433201
* The options are compatible with the Webpack-based builder.

0 commit comments

Comments
 (0)