fix(compiler): remove TypeScript from linker

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.
This commit is contained in:
Alan Agius
2025-05-22 18:15:25 +00:00
parent ad8931cb49
commit 7cf2425fb5
18 changed files with 67 additions and 23 deletions
@@ -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
+1
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
+5
View File
@@ -0,0 +1,5 @@
load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
)
+16
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"
}
}
+18
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');
})();
@@ -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.
@@ -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
@@ -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> {
@@ -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;
@@ -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';
/**
@@ -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';
@@ -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>,
@@ -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.
@@ -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';
@@ -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
+5
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:*"
},
@@ -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, '>='],
@@ -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.