Skip to content

Commit 466d86d

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.
1 parent c0fa3cb commit 466d86d

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[];
@@ -299,51 +303,6 @@ async function execute(
299303
return executionResult;
300304
}
301305

302-
async function writeResultFiles(
303-
outputFiles: OutputFile[],
304-
assetFiles: { source: string; destination: string }[] | undefined,
305-
outputPath: string,
306-
) {
307-
const directoryExists = new Set<string>();
308-
await Promise.all(
309-
outputFiles.map(async (file) => {
310-
// Ensure output subdirectories exist
311-
const basePath = path.dirname(file.path);
312-
if (basePath && !directoryExists.has(basePath)) {
313-
await fs.mkdir(path.join(outputPath, basePath), { recursive: true });
314-
directoryExists.add(basePath);
315-
}
316-
// Write file contents
317-
await fs.writeFile(path.join(outputPath, file.path), file.contents);
318-
}),
319-
);
320-
321-
if (assetFiles?.length) {
322-
await Promise.all(
323-
assetFiles.map(async ({ source, destination }) => {
324-
// Ensure output subdirectories exist
325-
const basePath = path.dirname(destination);
326-
if (basePath && !directoryExists.has(basePath)) {
327-
await fs.mkdir(path.join(outputPath, basePath), { recursive: true });
328-
directoryExists.add(basePath);
329-
}
330-
// Copy file contents
331-
await fs.copyFile(source, path.join(outputPath, destination), fsConstants.COPYFILE_FICLONE);
332-
}),
333-
);
334-
}
335-
}
336-
337-
function createOutputFileFromText(path: string, text: string): OutputFile {
338-
return {
339-
path,
340-
text,
341-
get contents() {
342-
return Buffer.from(this.text, 'utf-8');
343-
},
344-
};
345-
}
346-
347306
function createCodeBundleOptions(
348307
options: NormalizedBrowserOptions,
349308
target: string[],
@@ -438,43 +397,8 @@ function createCodeBundleOptions(
438397
};
439398

440399
if (options.externalPackages) {
441-
// Add a plugin that marks any resolved path as external if it is within a node modules directory.
442-
// This is used instead of the esbuild `packages` option to avoid marking bare specifiers that use
443-
// tsconfig path mapping to resolve to a workspace relative path. This is common for monorepos that
444-
// contain libraries that are built along with the application. These libraries should not be considered
445-
// external even though the imports appear to be packages.
446-
const EXTERNAL_PACKAGE_RESOLUTION = Symbol('EXTERNAL_PACKAGE_RESOLUTION');
447400
buildOptions.plugins ??= [];
448-
buildOptions.plugins.push({
449-
name: 'angular-external-packages',
450-
setup(build) {
451-
build.onResolve({ filter: /./ }, async (args) => {
452-
if (args.pluginData?.[EXTERNAL_PACKAGE_RESOLUTION]) {
453-
return null;
454-
}
455-
456-
const { importer, kind, resolveDir, namespace, pluginData = {} } = args;
457-
pluginData[EXTERNAL_PACKAGE_RESOLUTION] = true;
458-
459-
const result = await build.resolve(args.path, {
460-
importer,
461-
kind,
462-
namespace,
463-
pluginData,
464-
resolveDir,
465-
});
466-
467-
if (result.path && /[\\/]node_modules[\\/]/.test(result.path)) {
468-
return {
469-
path: args.path,
470-
external: true,
471-
};
472-
}
473-
474-
return result;
475-
});
476-
},
477-
});
401+
buildOptions.plugins.push(createExternalPackagesPlugin());
478402
}
479403

480404
const polyfills = options.polyfills ? [...options.polyfills] : [];
@@ -504,82 +428,6 @@ function createCodeBundleOptions(
504428
return buildOptions;
505429
}
506430

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

697545
// Setup a watcher
698-
const { createWatcher } = await import('./watcher');
546+
const { createWatcher } = await import('../../tools/esbuild/watcher');
699547
const watcher = createWatcher({
700548
polling: typeof userOptions.poll === 'number',
701549
interval: userOptions.poll,
@@ -772,66 +620,3 @@ export async function* buildEsbuildBrowserInternal(
772620
}
773621

774622
export default createBuilder(buildEsbuildBrowser);
775-
776-
function logBuildStats(
777-
context: BuilderContext,
778-
metafile: Metafile,
779-
initial: Map<string, InitialFileRecord>,
780-
estimatedTransferSizes?: Map<string, number>,
781-
) {
782-
const stats: BundleStats[] = [];
783-
for (const [file, output] of Object.entries(metafile.outputs)) {
784-
// Only display JavaScript and CSS files
785-
if (!file.endsWith('.js') && !file.endsWith('.css')) {
786-
continue;
787-
}
788-
// Skip internal component resources
789-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
790-
if ((output as any)['ng-component']) {
791-
continue;
792-
}
793-
794-
stats.push({
795-
initial: initial.has(file),
796-
stats: [
797-
file,
798-
initial.get(file)?.name ?? '-',
799-
output.bytes,
800-
estimatedTransferSizes?.get(file) ?? '-',
801-
],
802-
});
803-
}
804-
805-
const tableText = generateBuildStatsTable(stats, true, true, !!estimatedTransferSizes, undefined);
806-
807-
context.logger.info('\n' + tableText + '\n');
808-
}
809-
810-
async function calculateEstimatedTransferSizes(outputFiles: OutputFile[]) {
811-
const sizes = new Map<string, number>();
812-
813-
const pendingCompression = [];
814-
for (const outputFile of outputFiles) {
815-
// Only calculate JavaScript and CSS files
816-
if (!outputFile.path.endsWith('.js') && !outputFile.path.endsWith('.css')) {
817-
continue;
818-
}
819-
820-
// Skip compressing small files which may end being larger once compressed and will most likely not be
821-
// compressed in actual transit.
822-
if (outputFile.contents.byteLength < 1024) {
823-
sizes.set(outputFile.path, outputFile.contents.byteLength);
824-
continue;
825-
}
826-
827-
pendingCompression.push(
828-
compressAsync(outputFile.contents).then((result) =>
829-
sizes.set(outputFile.path, result.byteLength),
830-
),
831-
);
832-
}
833-
834-
await Promise.all(pendingCompression);
835-
836-
return sizes;
837-
}

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)