Skip to content

Commit 5bf0a07

Browse files
clydindgp1130
authored andcommitted
refactor(@angular-devkit/build-angular): isolate typescript usage to compilation worker thread in application builder
To improve initialization type for the application builder, loading of the typescript package as been removed from the main thread. The parallel TS/NG compilation will instead load typescript during its worker thread startup. The typescript package is currently large and can take several hundred milliseconds to load.
1 parent 6029c75 commit 5bf0a07

File tree

5 files changed

+33
-26
lines changed

5 files changed

+33
-26
lines changed

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import type ng from '@angular/compiler-cli';
1010
import type { PartialMessage } from 'esbuild';
11-
import ts from 'typescript';
11+
import type ts from 'typescript';
1212
import { loadEsmModule } from '../../../../utils/load-esm';
1313
import { profileAsync, profileSync } from '../../profiling';
1414
import type { AngularHostOptions } from '../angular-host';
@@ -22,6 +22,7 @@ export interface EmitFileResult {
2222

2323
export abstract class AngularCompilation {
2424
static #angularCompilerCliModule?: typeof ng;
25+
static #typescriptModule?: typeof ts;
2526

2627
static async loadCompilerCli(): Promise<typeof ng> {
2728
// This uses a wrapped dynamic import to load `@angular/compiler-cli` which is ESM.
@@ -32,6 +33,12 @@ export abstract class AngularCompilation {
3233
return AngularCompilation.#angularCompilerCliModule;
3334
}
3435

36+
static async loadTypescript(): Promise<typeof ts> {
37+
AngularCompilation.#typescriptModule ??= await import('typescript');
38+
39+
return AngularCompilation.#typescriptModule;
40+
}
41+
3542
protected async loadConfiguration(tsconfig: string): Promise<ng.CompilerOptions> {
3643
const { readConfiguration } = await AngularCompilation.loadCompilerCli();
3744

@@ -71,10 +78,14 @@ export abstract class AngularCompilation {
7178
async diagnoseFiles(): Promise<{ errors?: PartialMessage[]; warnings?: PartialMessage[] }> {
7279
const result: { errors?: PartialMessage[]; warnings?: PartialMessage[] } = {};
7380

81+
// Avoid loading typescript until actually needed.
82+
// This allows for avoiding the load of typescript in the main thread when using the parallel compilation.
83+
const typescript = await AngularCompilation.loadTypescript();
84+
7485
await profileAsync('NG_DIAGNOSTICS_TOTAL', async () => {
7586
for (const diagnostic of await this.collectDiagnostics()) {
76-
const message = convertTypeScriptDiagnostic(diagnostic);
77-
if (diagnostic.category === ts.DiagnosticCategory.Error) {
87+
const message = convertTypeScriptDiagnostic(typescript, diagnostic);
88+
if (diagnostic.category === typescript.DiagnosticCategory.Error) {
7889
(result.errors ??= []).push(message);
7990
} else {
8091
(result.warnings ??= []).push(message);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import type ng from '@angular/compiler-cli';
10-
import ts from 'typescript';
10+
import type ts from 'typescript';
1111
import { AngularHostOptions } from '../angular-host';
1212
import { AngularCompilation } from './angular-compilation';
1313

packages/angular_devkit/build_angular/src/tools/esbuild/angular/compiler-plugin.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import assert from 'node:assert';
1919
import { realpath } from 'node:fs/promises';
2020
import * as path from 'node:path';
2121
import { pathToFileURL } from 'node:url';
22-
import ts from 'typescript';
2322
import { maxWorkers } from '../../../utils/environment-options';
2423
import { JavaScriptTransformer } from '../javascript-transformer';
2524
import { LoadResultCache } from '../load-result-cache';
@@ -234,14 +233,12 @@ export function createCompilerPlugin(
234233
compilerOptions: { allowJs },
235234
referencedFiles,
236235
} = await compilation.initialize(tsconfigPath, hostOptions, (compilerOptions) => {
237-
if (
238-
compilerOptions.target === undefined ||
239-
compilerOptions.target < ts.ScriptTarget.ES2022
240-
) {
236+
// target of 9 is ES2022 (using the number avoids an expensive import of typescript just for an enum)
237+
if (compilerOptions.target === undefined || compilerOptions.target < 9) {
241238
// If 'useDefineForClassFields' is already defined in the users project leave the value as is.
242239
// Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995
243240
// which breaks the deprecated `@Effects` NGRX decorator and potentially other existing code as well.
244-
compilerOptions.target = ts.ScriptTarget.ES2022;
241+
compilerOptions.target = 9;
245242
compilerOptions.useDefineForClassFields ??= false;
246243

247244
// Only add the warning on the initial build

packages/angular_devkit/build_angular/src/tools/esbuild/angular/diagnostics.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@
88

99
import type { PartialMessage, PartialNote } from 'esbuild';
1010
import { platform } from 'node:os';
11-
import {
12-
Diagnostic,
13-
DiagnosticRelatedInformation,
14-
flattenDiagnosticMessageText,
15-
getLineAndCharacterOfPosition,
16-
getPositionOfLineAndCharacter,
17-
} from 'typescript';
11+
import type ts from 'typescript';
1812

1913
/**
2014
* Converts TypeScript Diagnostic related information into an esbuild compatible note object.
@@ -24,11 +18,12 @@ import {
2418
* @returns An esbuild diagnostic message as a PartialMessage object
2519
*/
2620
function convertTypeScriptDiagnosticInfo(
27-
info: DiagnosticRelatedInformation,
21+
typescript: typeof ts,
22+
info: ts.DiagnosticRelatedInformation,
2823
textPrefix?: string,
2924
): PartialNote {
3025
const newLine = platform() === 'win32' ? '\r\n' : '\n';
31-
let text = flattenDiagnosticMessageText(info.messageText, newLine);
26+
let text = typescript.flattenDiagnosticMessageText(info.messageText, newLine);
3227
if (textPrefix) {
3328
text = textPrefix + text;
3429
}
@@ -43,23 +38,23 @@ function convertTypeScriptDiagnosticInfo(
4338

4439
// Calculate the line/column location and extract the full line text that has the diagnostic
4540
if (info.start) {
46-
const { line, character } = getLineAndCharacterOfPosition(info.file, info.start);
41+
const { line, character } = typescript.getLineAndCharacterOfPosition(info.file, info.start);
4742
note.location.line = line + 1;
4843
note.location.column = character;
4944

5045
// The start position for the slice is the first character of the error line
51-
const lineStartPosition = getPositionOfLineAndCharacter(info.file, line, 0);
46+
const lineStartPosition = typescript.getPositionOfLineAndCharacter(info.file, line, 0);
5247

5348
// The end position for the slice is the first character of the next line or the length of
5449
// the entire file if the line is the last line of the file (getPositionOfLineAndCharacter
5550
// will error if a nonexistent line is passed).
56-
const { line: lastLineOfFile } = getLineAndCharacterOfPosition(
51+
const { line: lastLineOfFile } = typescript.getLineAndCharacterOfPosition(
5752
info.file,
5853
info.file.text.length - 1,
5954
);
6055
const lineEndPosition =
6156
line < lastLineOfFile
62-
? getPositionOfLineAndCharacter(info.file, line + 1, 0)
57+
? typescript.getPositionOfLineAndCharacter(info.file, line + 1, 0)
6358
: info.file.text.length;
6459

6560
note.location.lineText = info.file.text.slice(lineStartPosition, lineEndPosition).trimEnd();
@@ -74,7 +69,10 @@ function convertTypeScriptDiagnosticInfo(
7469
* @param diagnostic The TypeScript diagnostic to convert.
7570
* @returns An esbuild diagnostic message as a PartialMessage object
7671
*/
77-
export function convertTypeScriptDiagnostic(diagnostic: Diagnostic): PartialMessage {
72+
export function convertTypeScriptDiagnostic(
73+
typescript: typeof ts,
74+
diagnostic: ts.Diagnostic,
75+
): PartialMessage {
7876
let codePrefix = 'TS';
7977
let code = `${diagnostic.code}`;
8078
if (diagnostic.source === 'ngtsc') {
@@ -84,13 +82,14 @@ export function convertTypeScriptDiagnostic(diagnostic: Diagnostic): PartialMess
8482
}
8583

8684
const message: PartialMessage = convertTypeScriptDiagnosticInfo(
85+
typescript,
8786
diagnostic,
8887
`${codePrefix}${code}: `,
8988
);
9089

9190
if (diagnostic.relatedInformation?.length) {
9291
message.notes = diagnostic.relatedInformation.map((info) =>
93-
convertTypeScriptDiagnosticInfo(info),
92+
convertTypeScriptDiagnosticInfo(typescript, info),
9493
);
9594
}
9695

packages/angular_devkit/build_angular/src/tools/esbuild/angular/source-file-cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { platform } from 'node:os';
1010
import * as path from 'node:path';
1111
import { pathToFileURL } from 'node:url';
12-
import ts from 'typescript';
12+
import type ts from 'typescript';
1313
import { MemoryLoadResultCache } from '../load-result-cache';
1414

1515
const USING_WINDOWS = platform() === 'win32';

0 commit comments

Comments
 (0)