Skip to content

Commit b8e9e09

Browse files
committed
refactor(@angular-devkit/build-angular): replace most custom path normalize usage with Node.js builtins
During the build initialization phase, many paths are converted back and forth between multiple normalized forms. These conversions involve potentially expensive string operations. The majority of the custom path `normalize` function from `@angular-devkit/core` usages have now been removed in favor of the Node.js builtin path functions. This change reduces the need to perform additional string manipulation where possible.
1 parent dcc00f4 commit b8e9e09

File tree

8 files changed

+75
-95
lines changed

8 files changed

+75
-95
lines changed

‎packages/angular_devkit/build_angular/src/builders/app-shell/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
createBuilder,
1313
targetFromTargetString,
1414
} from '@angular-devkit/architect';
15-
import { JsonObject, normalize } from '@angular-devkit/core';
15+
import { JsonObject } from '@angular-devkit/core';
1616
import * as fs from 'fs';
1717
import * as path from 'path';
1818
import { normalizeOptimization } from '../../utils';
@@ -114,8 +114,8 @@ async function _renderUniversal(
114114

115115
if (browserOptions.serviceWorker) {
116116
await augmentAppWithServiceWorker(
117-
normalize(projectRoot),
118-
normalize(outputPath),
117+
projectRoot,
118+
outputPath,
119119
browserOptions.baseHref || '/',
120120
browserOptions.ngswConfigPath,
121121
);

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
1010
import { EmittedFiles, WebpackLoggingCallback, runWebpack } from '@angular-devkit/build-webpack';
11-
import { logging, normalize } from '@angular-devkit/core';
11+
import { logging } from '@angular-devkit/core';
1212
import * as fs from 'fs';
1313
import * as path from 'path';
1414
import { Observable, from } from 'rxjs';
@@ -106,9 +106,9 @@ async function initialize(
106106
if (options.assets?.length && !adjustedOptions.assets?.length) {
107107
normalizeAssetPatterns(
108108
options.assets,
109-
normalize(context.workspaceRoot),
110-
normalize(projectRoot),
111-
projectSourceRoot === undefined ? undefined : normalize(projectSourceRoot),
109+
context.workspaceRoot,
110+
projectRoot,
111+
projectSourceRoot,
112112
).forEach(({ output }) => {
113113
if (output.startsWith('..')) {
114114
throw new Error('An asset cannot be written to a location outside of the output path.');
@@ -268,9 +268,9 @@ export function buildWebpackBrowser(
268268
await copyAssets(
269269
normalizeAssetPatterns(
270270
options.assets,
271-
normalize(context.workspaceRoot),
272-
normalize(projectRoot),
273-
projectSourceRoot === undefined ? undefined : normalize(projectSourceRoot),
271+
context.workspaceRoot,
272+
projectRoot,
273+
projectSourceRoot,
274274
),
275275
Array.from(outputPaths.values()),
276276
context.workspaceRoot,
@@ -347,8 +347,8 @@ export function buildWebpackBrowser(
347347
for (const [locale, outputPath] of outputPaths.entries()) {
348348
try {
349349
await augmentAppWithServiceWorker(
350-
normalize(projectRoot),
351-
normalize(outputPath),
350+
projectRoot,
351+
outputPath,
352352
getLocaleBaseHref(i18n, locale) || options.baseHref || '/',
353353
options.ngswConfigPath,
354354
);

‎packages/angular_devkit/build_angular/src/builders/karma/index.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
*/
88

99
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
10-
import { getSystemPath, join, normalize } from '@angular-devkit/core';
1110
import { Config, ConfigOptions } from 'karma';
12-
import { resolve as fsResolve } from 'path';
11+
import * as path from 'path';
1312
import { Observable, from } from 'rxjs';
1413
import { defaultIfEmpty, switchMap } from 'rxjs/operators';
1514
import { Configuration } from 'webpack';
@@ -119,12 +118,13 @@ export function execute(
119118
}
120119

121120
const projectMetadata = await context.getProjectMetadata(projectName);
122-
const projectSourceRoot = getSystemPath(
123-
join(
124-
normalize(context.workspaceRoot),
125-
(projectMetadata.root as string | undefined) ?? '',
126-
(projectMetadata.sourceRoot as string | undefined) ?? '',
127-
),
121+
const projectRoot = path.join(
122+
context.workspaceRoot,
123+
(projectMetadata.root as string | undefined) ?? '',
124+
);
125+
const projectSourceRoot = path.join(
126+
projectRoot,
127+
(projectMetadata.sourceRoot as string | undefined) ?? '',
128128
);
129129

130130
const files = await findTests(options.include, context.workspaceRoot, projectSourceRoot);
@@ -144,7 +144,7 @@ export function execute(
144144
}
145145

146146
rules.unshift({
147-
test: fsResolve(context.workspaceRoot, options.main),
147+
test: path.resolve(context.workspaceRoot, options.main),
148148
use: {
149149
// cannot be a simple path as it differs between environments
150150
loader: SingleTestTransformLoader,
@@ -163,7 +163,7 @@ export function execute(
163163
};
164164

165165
const config = await karma.config.parseConfig(
166-
fsResolve(context.workspaceRoot, options.karmaConfig),
166+
path.resolve(context.workspaceRoot, options.karmaConfig),
167167
transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions,
168168
{ promiseConfig: true, throwErrors: true },
169169
);

‎packages/angular_devkit/build_angular/src/utils/normalize-asset-patterns.ts

+16-25
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {
10-
BaseException,
11-
Path,
12-
basename,
13-
dirname,
14-
getSystemPath,
15-
join,
16-
normalize,
17-
relative,
18-
resolve,
19-
} from '@angular-devkit/core';
9+
import { BaseException } from '@angular-devkit/core';
2010
import { statSync } from 'fs';
11+
import * as path from 'path';
2112
import { AssetPattern, AssetPatternClass } from '../builders/browser/schema';
2213

2314
export class MissingAssetSourceRootException extends BaseException {
@@ -28,34 +19,34 @@ export class MissingAssetSourceRootException extends BaseException {
2819

2920
export function normalizeAssetPatterns(
3021
assetPatterns: AssetPattern[],
31-
root: Path,
32-
projectRoot: Path,
33-
maybeSourceRoot: Path | undefined,
22+
workspaceRoot: string,
23+
projectRoot: string,
24+
projectSourceRoot: string | undefined,
3425
): AssetPatternClass[] {
35-
// When sourceRoot is not available, we default to ${projectRoot}/src.
36-
const sourceRoot = maybeSourceRoot || join(projectRoot, 'src');
37-
const resolvedSourceRoot = resolve(root, sourceRoot);
38-
3926
if (assetPatterns.length === 0) {
4027
return [];
4128
}
4229

30+
// When sourceRoot is not available, we default to ${projectRoot}/src.
31+
const sourceRoot = projectSourceRoot || path.join(projectRoot, 'src');
32+
const resolvedSourceRoot = path.resolve(workspaceRoot, sourceRoot);
33+
4334
return assetPatterns.map((assetPattern) => {
4435
// Normalize string asset patterns to objects.
4536
if (typeof assetPattern === 'string') {
46-
const assetPath = normalize(assetPattern);
47-
const resolvedAssetPath = resolve(root, assetPath);
37+
const assetPath = path.normalize(assetPattern);
38+
const resolvedAssetPath = path.resolve(workspaceRoot, assetPath);
4839

4940
// Check if the string asset is within sourceRoot.
5041
if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) {
5142
throw new MissingAssetSourceRootException(assetPattern);
5243
}
5344

54-
let glob: string, input: Path;
45+
let glob: string, input: string;
5546
let isDirectory = false;
5647

5748
try {
58-
isDirectory = statSync(getSystemPath(resolvedAssetPath)).isDirectory();
49+
isDirectory = statSync(resolvedAssetPath).isDirectory();
5950
} catch {
6051
isDirectory = true;
6152
}
@@ -67,13 +58,13 @@ export function normalizeAssetPatterns(
6758
input = assetPath;
6859
} else {
6960
// Files are their own glob.
70-
glob = basename(assetPath);
61+
glob = path.basename(assetPath);
7162
// Input directory is their original dirname.
72-
input = dirname(assetPath);
63+
input = path.dirname(assetPath);
7364
}
7465

7566
// Output directory for both is the relative path from source root to input.
76-
const output = relative(resolvedSourceRoot, resolve(root, input));
67+
const output = path.relative(resolvedSourceRoot, path.resolve(workspaceRoot, input));
7768

7869
// Return the asset pattern in object format.
7970
return { glob, input, output };

‎packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { json, normalize } from '@angular-devkit/core';
9+
import { json } from '@angular-devkit/core';
1010
import {
1111
AssetPatternClass,
1212
Schema as BrowserBuilderSchema,
@@ -48,14 +48,11 @@ export function normalizeBrowserSchema(
4848
cache: normalizeCacheOptions(metadata, workspaceRoot),
4949
assets: normalizeAssetPatterns(
5050
options.assets || [],
51-
normalize(workspaceRoot),
52-
normalize(projectRoot),
53-
projectSourceRoot ? normalize(projectSourceRoot) : undefined,
54-
),
55-
fileReplacements: normalizeFileReplacements(
56-
options.fileReplacements || [],
57-
normalize(workspaceRoot),
51+
workspaceRoot,
52+
projectRoot,
53+
projectSourceRoot,
5854
),
55+
fileReplacements: normalizeFileReplacements(options.fileReplacements || [], workspaceRoot),
5956
optimization: normalizeOptimization(options.optimization),
6057
sourceMap: normalizedSourceMapOptions,
6158
preserveSymlinks:

‎packages/angular_devkit/build_angular/src/utils/normalize-file-replacements.ts

+21-25
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { BaseException, Path, getSystemPath, join, normalize } from '@angular-devkit/core';
9+
import { BaseException } from '@angular-devkit/core';
1010
import { existsSync } from 'fs';
11+
import * as path from 'path';
1112
import { FileReplacement } from '../builders/browser/schema';
1213

1314
export class MissingFileReplacementException extends BaseException {
@@ -17,29 +18,29 @@ export class MissingFileReplacementException extends BaseException {
1718
}
1819

1920
export interface NormalizedFileReplacement {
20-
replace: Path;
21-
with: Path;
21+
replace: string;
22+
with: string;
2223
}
2324

2425
export function normalizeFileReplacements(
2526
fileReplacements: FileReplacement[],
26-
root: Path,
27+
workspaceRoot: string,
2728
): NormalizedFileReplacement[] {
2829
if (fileReplacements.length === 0) {
2930
return [];
3031
}
3132

3233
const normalizedReplacement = fileReplacements.map((replacement) =>
33-
normalizeFileReplacement(replacement, root),
34+
normalizeFileReplacement(replacement, workspaceRoot),
3435
);
3536

3637
for (const { replace, with: replacementWith } of normalizedReplacement) {
37-
if (!existsSync(getSystemPath(replacementWith))) {
38-
throw new MissingFileReplacementException(getSystemPath(replacementWith));
38+
if (!existsSync(replacementWith)) {
39+
throw new MissingFileReplacementException(replacementWith);
3940
}
4041

41-
if (!existsSync(getSystemPath(replace))) {
42-
throw new MissingFileReplacementException(getSystemPath(replace));
42+
if (!existsSync(replace)) {
43+
throw new MissingFileReplacementException(replace);
4344
}
4445
}
4546

@@ -48,27 +49,22 @@ export function normalizeFileReplacements(
4849

4950
function normalizeFileReplacement(
5051
fileReplacement: FileReplacement,
51-
root?: Path,
52+
root: string,
5253
): NormalizedFileReplacement {
53-
let replacePath: Path;
54-
let withPath: Path;
54+
let replacePath: string;
55+
let withPath: string;
5556
if (fileReplacement.src && fileReplacement.replaceWith) {
56-
replacePath = normalize(fileReplacement.src);
57-
withPath = normalize(fileReplacement.replaceWith);
57+
replacePath = fileReplacement.src;
58+
withPath = fileReplacement.replaceWith;
5859
} else if (fileReplacement.replace && fileReplacement.with) {
59-
replacePath = normalize(fileReplacement.replace);
60-
withPath = normalize(fileReplacement.with);
60+
replacePath = fileReplacement.replace;
61+
withPath = fileReplacement.with;
6162
} else {
6263
throw new Error(`Invalid file replacement: ${JSON.stringify(fileReplacement)}`);
6364
}
6465

65-
// TODO: For 7.x should this only happen if not absolute?
66-
if (root) {
67-
replacePath = join(root, replacePath);
68-
}
69-
if (root) {
70-
withPath = join(root, withPath);
71-
}
72-
73-
return { replace: replacePath, with: withPath };
66+
return {
67+
replace: path.join(root, replacePath),
68+
with: path.join(root, withPath),
69+
};
7470
}

‎packages/angular_devkit/build_angular/src/utils/service-worker.ts

+10-13
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { Path, getSystemPath, normalize } from '@angular-devkit/core';
109
import type { Config, Filesystem } from '@angular/service-worker/config';
1110
import * as crypto from 'crypto';
1211
import { createReadStream, promises as fs, constants as fsConstants } from 'fs';
@@ -62,17 +61,15 @@ class CliFilesystem implements Filesystem {
6261
}
6362

6463
export async function augmentAppWithServiceWorker(
65-
appRoot: Path,
66-
outputPath: Path,
64+
appRoot: string,
65+
outputPath: string,
6766
baseHref: string,
6867
ngswConfigPath?: string,
6968
): Promise<void> {
70-
const distPath = getSystemPath(normalize(outputPath));
71-
7269
// Determine the configuration file path
7370
const configPath = ngswConfigPath
74-
? getSystemPath(normalize(ngswConfigPath))
75-
: path.join(getSystemPath(appRoot), 'ngsw-config.json');
71+
? path.normalize(ngswConfigPath)
72+
: path.join(appRoot, 'ngsw-config.json');
7673

7774
// Read the configuration file
7875
let config: Config | undefined;
@@ -83,7 +80,7 @@ export async function augmentAppWithServiceWorker(
8380
if (error.code === 'ENOENT') {
8481
throw new Error(
8582
'Error: Expected to find an ngsw-config.json configuration file' +
86-
` in the ${getSystemPath(appRoot)} folder. Either provide one or` +
83+
` in the ${appRoot} folder. Either provide one or` +
8784
' disable Service Worker in the angular.json configuration file.',
8885
);
8986
} else {
@@ -101,20 +98,20 @@ export async function augmentAppWithServiceWorker(
10198
).Generator;
10299

103100
// Generate the manifest
104-
const generator = new GeneratorConstructor(new CliFilesystem(distPath), baseHref);
101+
const generator = new GeneratorConstructor(new CliFilesystem(outputPath), baseHref);
105102
const output = await generator.process(config);
106103

107104
// Write the manifest
108105
const manifest = JSON.stringify(output, null, 2);
109-
await fs.writeFile(path.join(distPath, 'ngsw.json'), manifest);
106+
await fs.writeFile(path.join(outputPath, 'ngsw.json'), manifest);
110107

111108
// Find the service worker package
112109
const workerPath = require.resolve('@angular/service-worker/ngsw-worker.js');
113110

114111
// Write the worker code
115112
await fs.copyFile(
116113
workerPath,
117-
path.join(distPath, 'ngsw-worker.js'),
114+
path.join(outputPath, 'ngsw-worker.js'),
118115
fsConstants.COPYFILE_FICLONE,
119116
);
120117

@@ -123,12 +120,12 @@ export async function augmentAppWithServiceWorker(
123120
try {
124121
await fs.copyFile(
125122
safetyPath,
126-
path.join(distPath, 'worker-basic.min.js'),
123+
path.join(outputPath, 'worker-basic.min.js'),
127124
fsConstants.COPYFILE_FICLONE,
128125
);
129126
await fs.copyFile(
130127
safetyPath,
131-
path.join(distPath, 'safety-worker.js'),
128+
path.join(outputPath, 'safety-worker.js'),
132129
fsConstants.COPYFILE_FICLONE,
133130
);
134131
} catch (error) {

0 commit comments

Comments
 (0)