From e9fcbb8af12e7b4370d2e03e6004f3f2fe02c981 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Thu, 22 May 2025 18:15:25 +0000 Subject: [PATCH] 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 --- .../npm_translate_lock_MzA5NzUwNzMx | 2 +- .bazelignore | 1 + integration/no_ts_linker/BUILD.bazel | 5 +++++ integration/no_ts_linker/package.json | 16 ++++++++++++++++ integration/no_ts_linker/test.mjs | 18 ++++++++++++++++++ .../linker/babel/src/ast/babel_ast_factory.ts | 2 +- .../linker/babel/src/babel_plugin.ts | 2 +- .../linker/babel/src/linker_plugin_options.ts | 2 +- .../linker/src/file_linker/file_linker.ts | 2 +- .../linker/src/file_linker/get_source_file.ts | 2 +- .../src/file_linker/linker_environment.ts | 2 +- .../partial_component_linker_1.ts | 2 +- .../partial_directive_linker_1.ts | 2 +- .../partial_linkers/partial_linker_selector.ts | 2 +- .../linker/src/file_linker/translator.ts | 9 ++++----- packages/compiler-cli/package.json | 5 +++++ .../src/ngtsc/translator/src/translator.ts | 4 ++-- .../translator/src/typescript_ast_factory.ts | 12 ++++++------ 18 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 integration/no_ts_linker/BUILD.bazel create mode 100644 integration/no_ts_linker/package.json create mode 100644 integration/no_ts_linker/test.mjs diff --git a/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx index 277fe48f2b7..a99b3959dcc 100755 --- a/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx +++ b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx @@ -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 diff --git a/.bazelignore b/.bazelignore index 7ac843e6005..01e1dd1e5aa 100644 --- a/.bazelignore +++ b/.bazelignore @@ -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 diff --git a/integration/no_ts_linker/BUILD.bazel b/integration/no_ts_linker/BUILD.bazel new file mode 100644 index 00000000000..33afcf1a1b0 --- /dev/null +++ b/integration/no_ts_linker/BUILD.bazel @@ -0,0 +1,5 @@ +load("//integration:index.bzl", "ng_integration_test") + +ng_integration_test( + name = "test", +) diff --git a/integration/no_ts_linker/package.json b/integration/no_ts_linker/package.json new file mode 100644 index 00000000000..0987cef4dda --- /dev/null +++ b/integration/no_ts_linker/package.json @@ -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" + } +} diff --git a/integration/no_ts_linker/test.mjs b/integration/no_ts_linker/test.mjs new file mode 100644 index 00000000000..ee8d0b13e34 --- /dev/null +++ b/integration/no_ts_linker/test.mjs @@ -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'); +})(); diff --git a/packages/compiler-cli/linker/babel/src/ast/babel_ast_factory.ts b/packages/compiler-cli/linker/babel/src/ast/babel_ast_factory.ts index ded4a6a9495..b7ab005fe8a 100644 --- a/packages/compiler-cli/linker/babel/src/ast/babel_ast_factory.ts +++ b/packages/compiler-cli/linker/babel/src/ast/babel_ast_factory.ts @@ -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. diff --git a/packages/compiler-cli/linker/babel/src/babel_plugin.ts b/packages/compiler-cli/linker/babel/src/babel_plugin.ts index 81a0579a3a6..aef84ccd2ff 100644 --- a/packages/compiler-cli/linker/babel/src/babel_plugin.ts +++ b/packages/compiler-cli/linker/babel/src/babel_plugin.ts @@ -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 diff --git a/packages/compiler-cli/linker/babel/src/linker_plugin_options.ts b/packages/compiler-cli/linker/babel/src/linker_plugin_options.ts index 8aeb5da5b9d..d03b70743a1 100644 --- a/packages/compiler-cli/linker/babel/src/linker_plugin_options.ts +++ b/packages/compiler-cli/linker/babel/src/linker_plugin_options.ts @@ -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 { diff --git a/packages/compiler-cli/linker/src/file_linker/file_linker.ts b/packages/compiler-cli/linker/src/file_linker/file_linker.ts index 7705eab253c..403a35fbfad 100644 --- a/packages/compiler-cli/linker/src/file_linker/file_linker.ts +++ b/packages/compiler-cli/linker/src/file_linker/file_linker.ts @@ -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 = [] as const; diff --git a/packages/compiler-cli/linker/src/file_linker/get_source_file.ts b/packages/compiler-cli/linker/src/file_linker/get_source_file.ts index a3e576a2950..17cbab2cc00 100644 --- a/packages/compiler-cli/linker/src/file_linker/get_source_file.ts +++ b/packages/compiler-cli/linker/src/file_linker/get_source_file.ts @@ -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'; /** diff --git a/packages/compiler-cli/linker/src/file_linker/linker_environment.ts b/packages/compiler-cli/linker/src/file_linker/linker_environment.ts index 0ad7b3c89f1..04f3dafa3d6 100644 --- a/packages/compiler-cli/linker/src/file_linker/linker_environment.ts +++ b/packages/compiler-cli/linker/src/file_linker/linker_environment.ts @@ -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'; diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts index a007b11a588..d69f497b703 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts @@ -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( directiveExpr: AstObject, diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts index b01e7fbd8df..95c05a7c3bd 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts @@ -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. diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts index 72928c61030..e381fcc814e 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts @@ -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'; diff --git a/packages/compiler-cli/linker/src/file_linker/translator.ts b/packages/compiler-cli/linker/src/file_linker/translator.ts index 8395fdb47a5..d529c663d8d 100644 --- a/packages/compiler-cli/linker/src/file_linker/translator.ts +++ b/packages/compiler-cli/linker/src/file_linker/translator.ts @@ -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 diff --git a/packages/compiler-cli/package.json b/packages/compiler-cli/package.json index 73f3b6124ef..27db2f3329e 100644 --- a/packages/compiler-cli/package.json +++ b/packages/compiler-cli/package.json @@ -55,6 +55,11 @@ "@angular/compiler": "0.0.0-PLACEHOLDER", "typescript": ">=5.8 <5.9" }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + }, "devDependencies": { "@angular/compiler": "workspace:*" }, diff --git a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts index 7d14f65f2a8..72e4aad3bbb 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts @@ -19,12 +19,12 @@ import { import {ImportGenerator} from './api/import_generator'; import {Context} from './context'; -const UNARY_OPERATORS = new Map([ +const UNARY_OPERATORS = /* @__PURE__ */ new Map([ [o.UnaryOperator.Minus, '-'], [o.UnaryOperator.Plus, '+'], ]); -const BINARY_OPERATORS = new Map([ +const BINARY_OPERATORS = /* @__PURE__ */ new Map([ [o.BinaryOperator.And, '&&'], [o.BinaryOperator.Bigger, '>'], [o.BinaryOperator.BiggerEquals, '>='], diff --git a/packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts b/packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts index 0c0b7f925c5..a3964d01a04 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts @@ -34,13 +34,13 @@ enum PureAnnotation { TERSER = '@__PURE__', } -const UNARY_OPERATORS: Record = { +const UNARY_OPERATORS: Record = /* @__PURE__ */ (() => ({ '+': ts.SyntaxKind.PlusToken, '-': ts.SyntaxKind.MinusToken, '!': ts.SyntaxKind.ExclamationToken, -}; +}))(); -const BINARY_OPERATORS: Record = { +const BINARY_OPERATORS: Record = /* @__PURE__ */ (() => ({ '&&': ts.SyntaxKind.AmpersandAmpersandToken, '>': ts.SyntaxKind.GreaterThanToken, '>=': ts.SyntaxKind.GreaterThanEqualsToken, @@ -61,13 +61,13 @@ const BINARY_OPERATORS: Record = { '+': ts.SyntaxKind.PlusToken, '??': ts.SyntaxKind.QuestionQuestionToken, 'in': ts.SyntaxKind.InKeyword, -}; +}))(); -const VAR_TYPES: Record = { +const VAR_TYPES: Record = /* @__PURE__ */ (() => ({ 'const': ts.NodeFlags.Const, 'let': ts.NodeFlags.Let, 'var': ts.NodeFlags.None, -}; +}))(); /** * A TypeScript flavoured implementation of the AstFactory.