Skip to content

Commit 2ef958a

Browse files
clydinalan-agius4
authored andcommitted
refactor(@angular-devkit/build-angular): remove @ngtools/webpack deep imports from application builder
The application builder previously used several TypeScript compiler host augmentation helper functions and the bootstrap replacement transformer from the `@ngtools/webpack` package. To remove reliance on Webpack-specific functionality. The subset of helpers have been copied into the application builder itself. Some of the helpers will eventually be removed completely pending future updates to the Angular compiler itself.
1 parent 87ba0e5 commit 2ef958a

File tree

4 files changed

+381
-30
lines changed

4 files changed

+381
-30
lines changed

packages/angular_devkit/build_angular/src/tools/esbuild/angular/angular-host.ts

+130-18
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
*/
88

99
import type ng from '@angular/compiler-cli';
10-
import ts from 'typescript';
10+
import { createHash } from 'node:crypto';
11+
import nodePath from 'node:path';
12+
import type ts from 'typescript';
1113

1214
export type AngularCompilerOptions = ng.CompilerOptions;
1315
export type AngularCompilerHost = ng.CompilerHost;
@@ -24,36 +26,146 @@ export interface AngularHostOptions {
2426
processWebWorker(workerFile: string, containingFile: string): string;
2527
}
2628

27-
// Temporary deep import for host augmentation support.
28-
// TODO: Move these to a private exports location or move the implementation into this package.
29-
const {
30-
augmentHostWithCaching,
31-
augmentHostWithReplacements,
32-
augmentProgramWithVersioning,
33-
} = require('@ngtools/webpack/src/ivy/host');
34-
3529
/**
3630
* Patches in-place the `getSourceFiles` function on an instance of a TypeScript
3731
* `Program` to ensure that all returned SourceFile instances have a `version`
3832
* field. The `version` field is required when used with a TypeScript BuilderProgram.
3933
* @param program The TypeScript Program instance to patch.
4034
*/
4135
export function ensureSourceFileVersions(program: ts.Program): void {
42-
augmentProgramWithVersioning(program);
36+
const baseGetSourceFiles = program.getSourceFiles;
37+
// TODO: Update Angular compiler to add versions to all internal files and remove this
38+
program.getSourceFiles = function (...parameters) {
39+
const files: readonly (ts.SourceFile & { version?: string })[] = baseGetSourceFiles(
40+
...parameters,
41+
);
42+
43+
for (const file of files) {
44+
if (file.version === undefined) {
45+
file.version = createHash('sha256').update(file.text).digest('hex');
46+
}
47+
}
48+
49+
return files;
50+
};
51+
}
52+
53+
function augmentHostWithCaching(host: ts.CompilerHost, cache: Map<string, ts.SourceFile>): void {
54+
const baseGetSourceFile = host.getSourceFile;
55+
host.getSourceFile = function (
56+
fileName,
57+
languageVersion,
58+
onError,
59+
shouldCreateNewSourceFile,
60+
...parameters
61+
) {
62+
if (!shouldCreateNewSourceFile && cache.has(fileName)) {
63+
return cache.get(fileName);
64+
}
65+
66+
const file = baseGetSourceFile.call(
67+
host,
68+
fileName,
69+
languageVersion,
70+
onError,
71+
true,
72+
...parameters,
73+
);
74+
75+
if (file) {
76+
cache.set(fileName, file);
77+
}
78+
79+
return file;
80+
};
81+
}
82+
83+
function augmentResolveModuleNames(
84+
typescript: typeof ts,
85+
host: ts.CompilerHost,
86+
resolvedModuleModifier: (
87+
resolvedModule: ts.ResolvedModule | undefined,
88+
moduleName: string,
89+
) => ts.ResolvedModule | undefined,
90+
moduleResolutionCache?: ts.ModuleResolutionCache,
91+
): void {
92+
if (host.resolveModuleNames) {
93+
const baseResolveModuleNames = host.resolveModuleNames;
94+
host.resolveModuleNames = function (moduleNames: string[], ...parameters) {
95+
return moduleNames.map((name) => {
96+
const result = baseResolveModuleNames.call(host, [name], ...parameters);
97+
98+
return resolvedModuleModifier(result[0], name);
99+
});
100+
};
101+
} else {
102+
host.resolveModuleNames = function (
103+
moduleNames: string[],
104+
containingFile: string,
105+
_reusedNames: string[] | undefined,
106+
redirectedReference: ts.ResolvedProjectReference | undefined,
107+
options: ts.CompilerOptions,
108+
) {
109+
return moduleNames.map((name) => {
110+
const result = typescript.resolveModuleName(
111+
name,
112+
containingFile,
113+
options,
114+
host,
115+
moduleResolutionCache,
116+
redirectedReference,
117+
).resolvedModule;
118+
119+
return resolvedModuleModifier(result, name);
120+
});
121+
};
122+
}
123+
}
124+
125+
function normalizePath(path: string): string {
126+
return nodePath.win32.normalize(path).replace(/\\/g, nodePath.posix.sep);
127+
}
128+
129+
function augmentHostWithReplacements(
130+
typescript: typeof ts,
131+
host: ts.CompilerHost,
132+
replacements: Record<string, string>,
133+
moduleResolutionCache?: ts.ModuleResolutionCache,
134+
): void {
135+
if (Object.keys(replacements).length === 0) {
136+
return;
137+
}
138+
139+
const normalizedReplacements: Record<string, string> = {};
140+
for (const [key, value] of Object.entries(replacements)) {
141+
normalizedReplacements[normalizePath(key)] = normalizePath(value);
142+
}
143+
144+
const tryReplace = (resolvedModule: ts.ResolvedModule | undefined) => {
145+
const replacement = resolvedModule && normalizedReplacements[resolvedModule.resolvedFileName];
146+
if (replacement) {
147+
return {
148+
resolvedFileName: replacement,
149+
isExternalLibraryImport: /[/\\]node_modules[/\\]/.test(replacement),
150+
};
151+
} else {
152+
return resolvedModule;
153+
}
154+
};
155+
156+
augmentResolveModuleNames(typescript, host, tryReplace, moduleResolutionCache);
43157
}
44158

45159
export function createAngularCompilerHost(
160+
typescript: typeof ts,
46161
compilerOptions: AngularCompilerOptions,
47162
hostOptions: AngularHostOptions,
48163
): AngularCompilerHost {
49164
// Create TypeScript compiler host
50-
const host: AngularCompilerHost = ts.createIncrementalCompilerHost(compilerOptions);
51-
// Set the parsing mode to the same as TS 5.3 default for tsc. This provides a parse
165+
const host: AngularCompilerHost = typescript.createIncrementalCompilerHost(compilerOptions);
166+
// Set the parsing mode to the same as TS 5.3+ default for tsc. This provides a parse
52167
// performance improvement by skipping non-type related JSDoc parsing.
53-
// NOTE: The check for this enum can be removed when TS 5.3 support is the minimum.
54-
if (ts.JSDocParsingMode) {
55-
host.jsDocParsingMode = ts.JSDocParsingMode.ParseForTypeErrors;
56-
}
168+
host.jsDocParsingMode = typescript.JSDocParsingMode.ParseForTypeErrors;
57169

58170
// The AOT compiler currently requires this hook to allow for a transformResource hook.
59171
// Once the AOT compiler allows only a transformResource hook, this can be reevaluated.
@@ -90,14 +202,14 @@ export function createAngularCompilerHost(
90202
// Augment TypeScript Host for file replacements option
91203
if (hostOptions.fileReplacements) {
92204
// Provide a resolution cache since overriding resolution prevents automatic creation
93-
const resolutionCache = ts.createModuleResolutionCache(
205+
const resolutionCache = typescript.createModuleResolutionCache(
94206
host.getCurrentDirectory(),
95207
host.getCanonicalFileName.bind(host),
96208
compilerOptions,
97209
);
98210
host.getModuleResolutionCache = () => resolutionCache;
99211

100-
augmentHostWithReplacements(host, hostOptions.fileReplacements, resolutionCache);
212+
augmentHostWithReplacements(typescript, host, hostOptions.fileReplacements, resolutionCache);
101213
}
102214

103215
// Augment TypeScript Host with source file caching if provided

packages/angular_devkit/build_angular/src/tools/esbuild/angular/compilation/aot-compilation.ts

+8-11
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ import {
1717
} from '../angular-host';
1818
import { createWorkerTransformer } from '../web-worker-transformer';
1919
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';
20-
21-
// Temporary deep import for transformer support
22-
// TODO: Move these to a private exports location or move the implementation into this package.
23-
const { mergeTransformers, replaceBootstrap } = require('@ngtools/webpack/src/ivy/transformation');
20+
import { replaceBootstrap } from './jit-bootstrap-transformer';
2421

2522
class AngularCompilationState {
2623
constructor(
@@ -63,7 +60,7 @@ export class AotCompilation extends AngularCompilation {
6360
compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions;
6461

6562
// Create Angular compiler host
66-
const host = createAngularCompilerHost(compilerOptions, hostOptions);
63+
const host = createAngularCompilerHost(ts, compilerOptions, hostOptions);
6764

6865
// Create the Angular specific program that contains the Angular compiler
6966
const angularProgram = profileSync(
@@ -224,12 +221,12 @@ export class AotCompilation extends AngularCompilation {
224221
angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
225222
emittedFiles.set(sourceFile, { filename: sourceFile.fileName, contents });
226223
};
227-
const transformers = mergeTransformers(angularCompiler.prepareEmit().transformers, {
228-
before: [
229-
replaceBootstrap(() => typeScriptProgram.getProgram().getTypeChecker()),
230-
webWorkerTransform,
231-
],
232-
});
224+
const transformers = angularCompiler.prepareEmit().transformers;
225+
transformers.before ??= [];
226+
transformers.before.push(
227+
replaceBootstrap(() => typeScriptProgram.getProgram().getTypeChecker()),
228+
);
229+
transformers.before.push(webWorkerTransform);
233230

234231
// TypeScript will loop until there are no more affected files in the program
235232
while (

0 commit comments

Comments
 (0)