fix(compiler): remove TypeScript from linker (#61618)

This commit removes the direct dependency on TypeScript within the linker, addressing a performance overhead that was adding between 500ms to 1s to compilation times for applications.

The primary cause of this overhead was the linker's direct reliance on TypeScript's which was caused by importing from barrel files. While convenient, barrel files are detrimental to code splitting and code motion. They force the bundling of all exported modules, even if only a subset is actually used.

By removing the usage of this barrel file and restructuring the imports to be more granular, we can avoid unnecessary TypeScript imports.
 Furthermore, TypeScript has now been changed to an optional peer dependency as using only the linker does not require TypeScript.

PR Close #61618
This commit is contained in:
Alan Agius 2025-05-22 18:15:25 +00:00 committed by Jessica Janiuk
parent 82327f2092
commit e9fcbb8af1
18 changed files with 67 additions and 23 deletions

View file

@ -3,7 +3,7 @@
# This file should be checked into version control along with the pnpm-lock.yaml file.
.npmrc=-1406867100
package.json=-1316846371
packages/compiler-cli/package.json=1094415146
packages/compiler-cli/package.json=-1344632265
packages/compiler/package.json=1190056499
pnpm-lock.yaml=1584116426
pnpm-workspace.yaml=353334404

View file

@ -24,6 +24,7 @@ integration/ng_elements/node_modules
integration/ng_update/node_modules
integration/ng_update_migrations/node_modules
integration/ng-add-localize/node_modules
integration/no_ts_linker/node_modules
integration/nodenext_resolution/node_modules
integration/platform-server/node_modules
integration/platform-server-zoneless/node_modules

View file

@ -0,0 +1,5 @@
load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
)

View file

@ -0,0 +1,16 @@
{
"name": "angular-integration",
"description": "Assert that the linker has no dependency on TypeScript.",
"version": "0.0.0",
"license": "MIT",
"type": "module",
"dependencies": {
"@angular/compiler": "file:../../dist/packages-dist/compiler",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/core": "file:../../dist/packages-dist/core",
"rxjs": "file:../../node_modules/rxjs"
},
"scripts": {
"test": "node ./test.mjs"
}
}

View file

@ -0,0 +1,18 @@
import assert from 'node:assert';
(async () => {
// Verify that TypeScript is not installed.
await assert.rejects(
() => import('typescript'),
({code, message}) => {
assert.strictEqual(code, 'ERR_MODULE_NOT_FOUND');
assert.match(message, new RegExp(`Cannot find package 'typescript'`));
return true;
},
);
// This validates that the linker has no dependency on TypeScript.
await import('@angular/compiler-cli/linker');
await import('@angular/compiler-cli/linker/babel');
})();

View file

@ -16,7 +16,7 @@ import {
SourceMapRange,
TemplateLiteral,
VariableDeclarationType,
} from '../../../../src/ngtsc/translator';
} from '../../../../src/ngtsc/translator/src/api/ast_factory';
/**
* A Babel flavored implementation of the AstFactory.

View file

@ -7,11 +7,11 @@
*/
import {ConfigAPI, PluginObj} from '@babel/core';
import {NodeJSFileSystem} from '../../../src/ngtsc/file_system';
import {ConsoleLogger, LogLevel} from '../../../src/ngtsc/logging';
import {LinkerOptions} from '../../src/file_linker/linker_options';
import {createEs2015LinkerPlugin} from './es2015_linker_plugin';
import {NodeJSFileSystem} from '../../../src/ngtsc/file_system/src/node_js_file_system';
/**
* This is the Babel plugin definition that is provided as a default export from the package, such

View file

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {LinkerOptions} from '../..';
import {ReadonlyFileSystem} from '../../../src/ngtsc/file_system';
import {ReadonlyFileSystem} from '../../../src/ngtsc/file_system/src/types';
import {Logger} from '../../../src/ngtsc/logging';
export interface LinkerPluginOptions extends Partial<LinkerOptions> {

View file

@ -7,7 +7,6 @@
*/
import {R3PartialDeclaration} from '@angular/compiler';
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
import {AstObject} from '../ast/ast_value';
import {DeclarationScope} from './declaration_scope';
@ -15,6 +14,7 @@ import {EmitScope} from './emit_scopes/emit_scope';
import {LocalEmitScope} from './emit_scopes/local_emit_scope';
import {LinkerEnvironment} from './linker_environment';
import {createLinkerMap, PartialLinkerSelector} from './partial_linkers/partial_linker_selector';
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system/src/types';
export const NO_STATEMENTS: Readonly<any[]> = [] as const;

View file

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system/src/types';
import {SourceFile, SourceFileLoader} from '../../../src/ngtsc/sourcemaps';
/**

View file

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ReadonlyFileSystem} from '../../../src/ngtsc/file_system';
import {ReadonlyFileSystem} from '../../../src/ngtsc/file_system/src/types';
import {Logger} from '../../../src/ngtsc/logging';
import {SourceFileLoader} from '../../../src/ngtsc/sourcemaps';
import {AstFactory} from '../../../src/ngtsc/translator';

View file

@ -33,7 +33,6 @@ import {
} from '@angular/compiler';
import semver from 'semver';
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
import {Range} from '../../ast/ast_host';
import {AstObject, AstValue} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error';
@ -42,6 +41,7 @@ import {GetSourceFileFn} from '../get_source_file';
import {toR3DirectiveMeta} from './partial_directive_linker_1';
import {LinkedDefinition, PartialLinker} from './partial_linker';
import {extractForwardRef, PLACEHOLDER_VERSION} from './util';
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system/src/types';
function makeDirectiveMetadata<TExpression>(
directiveExpr: AstObject<R3DeclareDirectiveDependencyMetadata, TExpression>,

View file

@ -26,13 +26,13 @@ import {
R3QueryMetadata,
} from '@angular/compiler';
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
import {Range} from '../../ast/ast_host';
import {AstObject, AstValue} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error';
import {LinkedDefinition, PartialLinker} from './partial_linker';
import {extractForwardRef, getDefaultStandaloneValue, wrapReference} from './util';
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system/src/types';
/**
* A `PartialLinker` that is designed to process `ɵɵngDeclareDirective()` call expressions.

View file

@ -7,7 +7,6 @@
*/
import semver from 'semver';
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
import {Logger} from '../../../../src/ngtsc/logging';
import {createGetSourceFile} from '../get_source_file';
import {LinkerEnvironment} from '../linker_environment';
@ -23,6 +22,7 @@ import {PartialLinker} from './partial_linker';
import {PartialNgModuleLinkerVersion1} from './partial_ng_module_linker_1';
import {PartialPipeLinkerVersion1} from './partial_pipe_linker_1';
import {PLACEHOLDER_VERSION} from './util';
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system/src/types';
export const ɵɵngDeclareDirective = 'ɵɵngDeclareDirective';
export const ɵɵngDeclareClassMetadata = 'ɵɵngDeclareClassMetadata';

View file

@ -6,14 +6,13 @@
* found in the LICENSE file at https://angular.dev/license
*/
import * as o from '@angular/compiler';
import {
AstFactory,
Context,
ExpressionTranslatorVisitor,
ImportGenerator,
TranslatorOptions,
} from '../../../src/ngtsc/translator';
} from '../../../src/ngtsc/translator/src/translator';
import {Context} from '../../../src/ngtsc/translator/src/context';
import {ImportGenerator} from '../../../src/ngtsc/translator/src/api/import_generator';
import {AstFactory} from '../../../src/ngtsc/translator/src/api/ast_factory';
/**
* Generic translator helper class, which exposes methods for translating expressions and

View file

@ -55,6 +55,11 @@
"@angular/compiler": "0.0.0-PLACEHOLDER",
"typescript": ">=5.8 <5.9"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"devDependencies": {
"@angular/compiler": "workspace:*"
},

View file

@ -19,12 +19,12 @@ import {
import {ImportGenerator} from './api/import_generator';
import {Context} from './context';
const UNARY_OPERATORS = new Map<o.UnaryOperator, UnaryOperator>([
const UNARY_OPERATORS = /* @__PURE__ */ new Map<o.UnaryOperator, UnaryOperator>([
[o.UnaryOperator.Minus, '-'],
[o.UnaryOperator.Plus, '+'],
]);
const BINARY_OPERATORS = new Map<o.BinaryOperator, BinaryOperator>([
const BINARY_OPERATORS = /* @__PURE__ */ new Map<o.BinaryOperator, BinaryOperator>([
[o.BinaryOperator.And, '&&'],
[o.BinaryOperator.Bigger, '>'],
[o.BinaryOperator.BiggerEquals, '>='],

View file

@ -34,13 +34,13 @@ enum PureAnnotation {
TERSER = '@__PURE__',
}
const UNARY_OPERATORS: Record<UnaryOperator, ts.PrefixUnaryOperator> = {
const UNARY_OPERATORS: Record<UnaryOperator, ts.PrefixUnaryOperator> = /* @__PURE__ */ (() => ({
'+': ts.SyntaxKind.PlusToken,
'-': ts.SyntaxKind.MinusToken,
'!': ts.SyntaxKind.ExclamationToken,
};
}))();
const BINARY_OPERATORS: Record<BinaryOperator, ts.BinaryOperator> = {
const BINARY_OPERATORS: Record<BinaryOperator, ts.BinaryOperator> = /* @__PURE__ */ (() => ({
'&&': ts.SyntaxKind.AmpersandAmpersandToken,
'>': ts.SyntaxKind.GreaterThanToken,
'>=': ts.SyntaxKind.GreaterThanEqualsToken,
@ -61,13 +61,13 @@ const BINARY_OPERATORS: Record<BinaryOperator, ts.BinaryOperator> = {
'+': ts.SyntaxKind.PlusToken,
'??': ts.SyntaxKind.QuestionQuestionToken,
'in': ts.SyntaxKind.InKeyword,
};
}))();
const VAR_TYPES: Record<VariableDeclarationType, ts.NodeFlags> = {
const VAR_TYPES: Record<VariableDeclarationType, ts.NodeFlags> = /* @__PURE__ */ (() => ({
'const': ts.NodeFlags.Const,
'let': ts.NodeFlags.Let,
'var': ts.NodeFlags.None,
};
}))();
/**
* A TypeScript flavoured implementation of the AstFactory.