Skip to content

Commit 15e0a88

Browse files
committed
refactor(@angular-devkit/build-angular): update code base structure to facilitate future builders
This commit updates the code base structure in preparation for future works. (cherry picked from commit 466d86d)
1 parent abc49bd commit 15e0a88

File tree

96 files changed

+409
-364
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+409
-364
lines changed

goldens/circular-deps/packages.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[
22
[
3-
"packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts",
4-
"packages/angular_devkit/build_angular/src/webpack/utils/stats.ts"
3+
"packages/angular_devkit/build_angular/src/tools/webpack/utils/stats.ts",
4+
"packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts"
55
],
66
[
77
"packages/angular/cli/src/analytics/analytics-collector.ts",

packages/angular_devkit/build_angular/plugins/karma.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
module.exports = require('../src/webpack/plugins/karma/karma');
9+
module.exports = require('../src/tools/webpack/plugins/karma/karma');

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

Lines changed: 24 additions & 239 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,39 @@
77
*/
88

99
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
10-
import type { BuildOptions, Metafile, OutputFile } from 'esbuild';
11-
import { constants as fsConstants } from 'node:fs';
10+
import type { BuildOptions, OutputFile } from 'esbuild';
1211
import fs from 'node:fs/promises';
1312
import path from 'node:path';
14-
import { promisify } from 'node:util';
15-
import { brotliCompress } from 'node:zlib';
13+
import { SourceFileCache, createCompilerPlugin } from '../../tools/esbuild/angular/compiler-plugin';
14+
import { BundlerContext } from '../../tools/esbuild/bundler-context';
15+
import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker';
16+
import { createExternalPackagesPlugin } from '../../tools/esbuild/external-packages-plugin';
17+
import { createGlobalScriptsBundleOptions } from '../../tools/esbuild/global-scripts';
18+
import { createGlobalStylesBundleOptions } from '../../tools/esbuild/global-styles';
19+
import { extractLicenses } from '../../tools/esbuild/license-extractor';
20+
import { createSourcemapIngorelistPlugin } from '../../tools/esbuild/sourcemap-ignorelist-plugin';
21+
import { shutdownSassWorkerPool } from '../../tools/esbuild/stylesheets/sass-language';
22+
import {
23+
calculateEstimatedTransferSizes,
24+
createOutputFileFromText,
25+
getFeatureSupport,
26+
logBuildStats,
27+
logMessages,
28+
withNoProgress,
29+
withSpinner,
30+
writeResultFiles,
31+
} from '../../tools/esbuild/utils';
32+
import { createVirtualModulePlugin } from '../../tools/esbuild/virtual-module-plugin';
33+
import type { ChangedFiles } from '../../tools/esbuild/watcher';
1634
import { copyAssets } from '../../utils/copy-assets';
1735
import { assertIsError } from '../../utils/error';
1836
import { transformSupportedBrowsersToTargets } from '../../utils/esbuild-targets';
1937
import { IndexHtmlGenerator } from '../../utils/index-file/index-html-generator';
2038
import { augmentAppWithServiceWorkerEsbuild } from '../../utils/service-worker';
21-
import { Spinner } from '../../utils/spinner';
2239
import { getSupportedBrowsers } from '../../utils/supported-browsers';
23-
import { BundleStats, generateBuildStatsTable } from '../../webpack/utils/stats';
24-
import { SourceFileCache, createCompilerPlugin } from './angular/compiler-plugin';
2540
import { logBuilderStatusWarnings } from './builder-status-warnings';
26-
import { checkCommonJSModules } from './commonjs-checker';
27-
import { BundlerContext, InitialFileRecord, logMessages } from './esbuild';
28-
import { createGlobalScriptsBundleOptions } from './global-scripts';
29-
import { createGlobalStylesBundleOptions } from './global-styles';
30-
import { extractLicenses } from './license-extractor';
3141
import { BrowserEsbuildOptions, NormalizedBrowserOptions, normalizeOptions } from './options';
3242
import { Schema as BrowserBuilderOptions } from './schema';
33-
import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
34-
import { shutdownSassWorkerPool } from './stylesheets/sass-language';
35-
import { createVirtualModulePlugin } from './virtual-module-plugin';
36-
import type { ChangedFiles } from './watcher';
37-
38-
const compressAsync = promisify(brotliCompress);
3943

4044
interface RebuildState {
4145
rebuildContexts: BundlerContext[];
@@ -279,51 +283,6 @@ async function execute(
279283
return executionResult;
280284
}
281285

282-
async function writeResultFiles(
283-
outputFiles: OutputFile[],
284-
assetFiles: { source: string; destination: string }[] | undefined,
285-
outputPath: string,
286-
) {
287-
const directoryExists = new Set<string>();
288-
await Promise.all(
289-
outputFiles.map(async (file) => {
290-
// Ensure output subdirectories exist
291-
const basePath = path.dirname(file.path);
292-
if (basePath && !directoryExists.has(basePath)) {
293-
await fs.mkdir(path.join(outputPath, basePath), { recursive: true });
294-
directoryExists.add(basePath);
295-
}
296-
// Write file contents
297-
await fs.writeFile(path.join(outputPath, file.path), file.contents);
298-
}),
299-
);
300-
301-
if (assetFiles?.length) {
302-
await Promise.all(
303-
assetFiles.map(async ({ source, destination }) => {
304-
// Ensure output subdirectories exist
305-
const basePath = path.dirname(destination);
306-
if (basePath && !directoryExists.has(basePath)) {
307-
await fs.mkdir(path.join(outputPath, basePath), { recursive: true });
308-
directoryExists.add(basePath);
309-
}
310-
// Copy file contents
311-
await fs.copyFile(source, path.join(outputPath, destination), fsConstants.COPYFILE_FICLONE);
312-
}),
313-
);
314-
}
315-
}
316-
317-
function createOutputFileFromText(path: string, text: string): OutputFile {
318-
return {
319-
path,
320-
text,
321-
get contents() {
322-
return Buffer.from(this.text, 'utf-8');
323-
},
324-
};
325-
}
326-
327286
function createCodeBundleOptions(
328287
options: NormalizedBrowserOptions,
329288
target: string[],
@@ -418,43 +377,8 @@ function createCodeBundleOptions(
418377
};
419378

420379
if (options.externalPackages) {
421-
// Add a plugin that marks any resolved path as external if it is within a node modules directory.
422-
// This is used instead of the esbuild `packages` option to avoid marking bare specifiers that use
423-
// tsconfig path mapping to resolve to a workspace relative path. This is common for monorepos that
424-
// contain libraries that are built along with the application. These libraries should not be considered
425-
// external even though the imports appear to be packages.
426-
const EXTERNAL_PACKAGE_RESOLUTION = Symbol('EXTERNAL_PACKAGE_RESOLUTION');
427380
buildOptions.plugins ??= [];
428-
buildOptions.plugins.push({
429-
name: 'angular-external-packages',
430-
setup(build) {
431-
build.onResolve({ filter: /./ }, async (args) => {
432-
if (args.pluginData?.[EXTERNAL_PACKAGE_RESOLUTION]) {
433-
return null;
434-
}
435-
436-
const { importer, kind, resolveDir, namespace, pluginData = {} } = args;
437-
pluginData[EXTERNAL_PACKAGE_RESOLUTION] = true;
438-
439-
const result = await build.resolve(args.path, {
440-
importer,
441-
kind,
442-
namespace,
443-
pluginData,
444-
resolveDir,
445-
});
446-
447-
if (result.path && /[\\/]node_modules[\\/]/.test(result.path)) {
448-
return {
449-
path: args.path,
450-
external: true,
451-
};
452-
}
453-
454-
return result;
455-
});
456-
},
457-
});
381+
buildOptions.plugins.push(createExternalPackagesPlugin());
458382
}
459383

460384
const polyfills = options.polyfills ? [...options.polyfills] : [];
@@ -484,82 +408,6 @@ function createCodeBundleOptions(
484408
return buildOptions;
485409
}
486410

487-
/**
488-
* Generates a syntax feature object map for Angular applications based on a list of targets.
489-
* A full set of feature names can be found here: https://esbuild.github.io/api/#supported
490-
* @param target An array of browser/engine targets in the format accepted by the esbuild `target` option.
491-
* @returns An object that can be used with the esbuild build `supported` option.
492-
*/
493-
function getFeatureSupport(target: string[]): BuildOptions['supported'] {
494-
const supported: Record<string, boolean> = {
495-
// Native async/await is not supported with Zone.js. Disabling support here will cause
496-
// esbuild to downlevel async/await and for await...of to a Zone.js supported form. However, esbuild
497-
// does not currently support downleveling async generators. Instead babel is used within the JS/TS
498-
// loader to perform the downlevel transformation.
499-
// NOTE: If esbuild adds support in the future, the babel support for async generators can be disabled.
500-
'async-await': false,
501-
// V8 currently has a performance defect involving object spread operations that can cause signficant
502-
// degradation in runtime performance. By not supporting the language feature here, a downlevel form
503-
// will be used instead which provides a workaround for the performance issue.
504-
// For more details: https://bugs.chromium.org/p/v8/issues/detail?id=11536
505-
'object-rest-spread': false,
506-
// esbuild currently has a defect involving self-referencing a class within a static code block or
507-
// static field initializer. This is not an issue for projects that use the default browserslist as these
508-
// elements are an ES2022 feature which is not support by all browsers in the default list. However, if a
509-
// custom browserslist is used that only has newer browsers than the static code elements may be present.
510-
// This issue is compounded by the default usage of the tsconfig `"useDefineForClassFields": false` option
511-
// present in generated CLI projects which causes static code blocks to be used instead of static fields.
512-
// esbuild currently unconditionally downlevels all static fields in top-level classes so to workaround the
513-
// Angular issue only static code blocks are disabled here.
514-
// For more details: https://github.com/evanw/esbuild/issues/2950
515-
'class-static-blocks': false,
516-
};
517-
518-
// Detect Safari browser versions that have a class field behavior bug
519-
// See: https://github.com/angular/angular-cli/issues/24355#issuecomment-1333477033
520-
// See: https://github.com/WebKit/WebKit/commit/e8788a34b3d5f5b4edd7ff6450b80936bff396f2
521-
let safariClassFieldScopeBug = false;
522-
for (const browser of target) {
523-
let majorVersion;
524-
if (browser.startsWith('ios')) {
525-
majorVersion = Number(browser.slice(3, 5));
526-
} else if (browser.startsWith('safari')) {
527-
majorVersion = Number(browser.slice(6, 8));
528-
} else {
529-
continue;
530-
}
531-
// Technically, 14.0 is not broken but rather does not have support. However, the behavior
532-
// is identical since it would be set to false by esbuild if present as a target.
533-
if (majorVersion === 14 || majorVersion === 15) {
534-
safariClassFieldScopeBug = true;
535-
break;
536-
}
537-
}
538-
// If class field support cannot be used set to false; otherwise leave undefined to allow
539-
// esbuild to use `target` to determine support.
540-
if (safariClassFieldScopeBug) {
541-
supported['class-field'] = false;
542-
supported['class-static-field'] = false;
543-
}
544-
545-
return supported;
546-
}
547-
548-
async function withSpinner<T>(text: string, action: () => T | Promise<T>): Promise<T> {
549-
const spinner = new Spinner(text);
550-
spinner.start();
551-
552-
try {
553-
return await action();
554-
} finally {
555-
spinner.stop();
556-
}
557-
}
558-
559-
async function withNoProgress<T>(test: string, action: () => T | Promise<T>): Promise<T> {
560-
return action();
561-
}
562-
563411
/**
564412
* Main execution function for the esbuild-based application builder.
565413
* The options are compatible with the Webpack-based builder.
@@ -675,7 +523,7 @@ export async function* buildEsbuildBrowserInternal(
675523
}
676524

677525
// Setup a watcher
678-
const { createWatcher } = await import('./watcher');
526+
const { createWatcher } = await import('../../tools/esbuild/watcher');
679527
const watcher = createWatcher({
680528
polling: typeof userOptions.poll === 'number',
681529
interval: userOptions.poll,
@@ -752,66 +600,3 @@ export async function* buildEsbuildBrowserInternal(
752600
}
753601

754602
export default createBuilder(buildEsbuildBrowser);
755-
756-
function logBuildStats(
757-
context: BuilderContext,
758-
metafile: Metafile,
759-
initial: Map<string, InitialFileRecord>,
760-
estimatedTransferSizes?: Map<string, number>,
761-
) {
762-
const stats: BundleStats[] = [];
763-
for (const [file, output] of Object.entries(metafile.outputs)) {
764-
// Only display JavaScript and CSS files
765-
if (!file.endsWith('.js') && !file.endsWith('.css')) {
766-
continue;
767-
}
768-
// Skip internal component resources
769-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
770-
if ((output as any)['ng-component']) {
771-
continue;
772-
}
773-
774-
stats.push({
775-
initial: initial.has(file),
776-
stats: [
777-
file,
778-
initial.get(file)?.name ?? '-',
779-
output.bytes,
780-
estimatedTransferSizes?.get(file) ?? '-',
781-
],
782-
});
783-
}
784-
785-
const tableText = generateBuildStatsTable(stats, true, true, !!estimatedTransferSizes, undefined);
786-
787-
context.logger.info('\n' + tableText + '\n');
788-
}
789-
790-
async function calculateEstimatedTransferSizes(outputFiles: OutputFile[]) {
791-
const sizes = new Map<string, number>();
792-
793-
const pendingCompression = [];
794-
for (const outputFile of outputFiles) {
795-
// Only calculate JavaScript and CSS files
796-
if (!outputFile.path.endsWith('.js') && !outputFile.path.endsWith('.css')) {
797-
continue;
798-
}
799-
800-
// Skip compressing small files which may end being larger once compressed and will most likely not be
801-
// compressed in actual transit.
802-
if (outputFile.contents.byteLength < 1024) {
803-
sizes.set(outputFile.path, outputFile.contents.byteLength);
804-
continue;
805-
}
806-
807-
pendingCompression.push(
808-
compressAsync(outputFile.contents).then((result) =>
809-
sizes.set(outputFile.path, result.byteLength),
810-
),
811-
);
812-
}
813-
814-
await Promise.all(pendingCompression);
815-
816-
return sizes;
817-
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99
import { BuilderContext } from '@angular-devkit/architect';
1010
import { createRequire } from 'node:module';
1111
import path from 'node:path';
12+
import {
13+
globalScriptsByBundleName,
14+
normalizeGlobalStyles,
15+
} from '../../tools/webpack/utils/helpers';
1216
import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } from '../../utils';
1317
import { normalizeCacheOptions } from '../../utils/normalize-cache';
1418
import { generateEntryPoints } from '../../utils/package-chunk-sort';
1519
import { findTailwindConfigurationFile } from '../../utils/tailwind';
1620
import { getIndexInputFile, getIndexOutputFile } from '../../utils/webpack-browser-config';
17-
import { globalScriptsByBundleName, normalizeGlobalStyles } from '../../webpack/utils/helpers';
1821
import { Schema as BrowserBuilderOptions, OutputHashing } from './schema';
1922

2023
export type NormalizedBrowserOptions = Awaited<ReturnType<typeof normalizeOptions>>;

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ import * as fs from 'fs';
1212
import * as path from 'path';
1313
import { Observable, concatMap, from, map, switchMap } from 'rxjs';
1414
import webpack, { StatsCompilation } from 'webpack';
15+
import { getCommonConfig, getStylesConfig } from '../../tools/webpack/configs';
16+
import { markAsyncChunksNonInitial } from '../../tools/webpack/utils/async-chunks';
17+
import { normalizeExtraEntryPoints } from '../../tools/webpack/utils/helpers';
18+
import {
19+
BuildEventStats,
20+
generateBuildEventStats,
21+
statsErrorsToString,
22+
statsHasErrors,
23+
statsHasWarnings,
24+
statsWarningsToString,
25+
webpackStatsLogger,
26+
} from '../../tools/webpack/utils/stats';
1527
import { ExecutionTransformer } from '../../transforms';
1628
import {
1729
deleteOutputDir,
@@ -46,18 +58,6 @@ import {
4658
getIndexInputFile,
4759
getIndexOutputFile,
4860
} from '../../utils/webpack-browser-config';
49-
import { getCommonConfig, getStylesConfig } from '../../webpack/configs';
50-
import { markAsyncChunksNonInitial } from '../../webpack/utils/async-chunks';
51-
import { normalizeExtraEntryPoints } from '../../webpack/utils/helpers';
52-
import {
53-
BuildEventStats,
54-
generateBuildEventStats,
55-
statsErrorsToString,
56-
statsHasErrors,
57-
statsHasWarnings,
58-
statsWarningsToString,
59-
webpackStatsLogger,
60-
} from '../../webpack/utils/stats';
6161
import { Schema as BrowserBuilderSchema } from './schema';
6262

6363
/**

packages/angular_devkit/build_angular/src/builders/browser/tests/options/named-chunks_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
1111

1212
const MAIN_OUTPUT = 'dist/main.js';
1313
const NAMED_LAZY_OUTPUT = 'dist/src_lazy-module_ts.js';
14-
const UNNAMED_LAZY_OUTPUT = 'dist/459.js';
14+
const UNNAMED_LAZY_OUTPUT = 'dist/631.js';
1515

1616
describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {
1717
describe('Option: "namedChunks"', () => {

0 commit comments

Comments
 (0)