From e0015d3c456d584242269b0765878d598a550888 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 30 Sep 2021 16:32:46 -0400 Subject: [PATCH] refactor(migrations): support use of an ESM `@angular/compiler-cli` package (#43657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, migrations and schematics must be in CommonJS format. However, framework packages will only be ESM from v13 and onward. To support this configuration, dynamic import expressions are now used to load `@angular/compiler-cli` and its new secondary entry point `@angular/compiler-cli/private/migrations`. Dynamic imports within Node.js allow the `@angular/core` migrations’ CommonJS code to load ESM code. Unfortunately, TypeScript will currently, unconditionally down-level dynamic import into a require call. `require` calls cannot load ESM code and will result in a runtime error. To workaround this, a Function constructor is used to prevent TypeScript from changing the dynamic import. Once TypeScript provides support for keeping the dynamic import this workaround can be dropped and replaced with a standard dynamic import. Due to the use of the dynamic import, a reference to the dynamically loaded modules must now be passed to all locations that use values from the modules. PR Close #43657 --- .../schematics/migrations/google3/BUILD.bazel | 4 + .../google3/noMissingInjectableRule.ts | 15 +++- ...decoratedClassesWithDecoratedFieldsRule.ts | 16 +++- .../migrations/missing-injectable/index.ts | 23 ++++- .../missing-injectable/providers_evaluator.ts | 86 +++++++++++-------- .../missing-injectable/transform.ts | 20 +++-- .../migrations/module-with-providers/index.ts | 22 ++++- .../module-with-providers/transform.ts | 17 ++-- .../migrations/static-queries/index.ts | 25 ++++-- .../template_strategy/template_strategy.ts | 16 ++-- .../index.ts | 23 ++++- .../transform.ts | 13 +-- .../undecorated-classes-with-di/BUILD.bazel | 1 - .../create_ngc_program.ts | 7 +- .../decorator_rewrite/decorator_rewriter.ts | 3 +- .../undecorated-classes-with-di/index.ts | 68 +++++++++++---- .../ng_declaration_collector.ts | 46 +++++----- .../undecorated-classes-with-di/transform.ts | 12 ++- packages/core/schematics/utils/BUILD.bazel | 1 + packages/core/schematics/utils/load_esm.ts | 20 +++++ 20 files changed, 303 insertions(+), 135 deletions(-) diff --git a/packages/core/schematics/migrations/google3/BUILD.bazel b/packages/core/schematics/migrations/google3/BUILD.bazel index 0bc9d7a0a9a10..74bfee1edb4cb 100644 --- a/packages/core/schematics/migrations/google3/BUILD.bazel +++ b/packages/core/schematics/migrations/google3/BUILD.bazel @@ -7,6 +7,10 @@ ts_library( visibility = ["//packages/core/schematics/test/google3:__pkg__"], deps = [ "//packages/compiler", + "//packages/compiler-cli/src/ngtsc/annotations", + "//packages/compiler-cli/src/ngtsc/imports", + "//packages/compiler-cli/src/ngtsc/partial_evaluator", + "//packages/compiler-cli/src/ngtsc/reflection", "//packages/core/schematics/migrations/activated-route-snapshot-fragment", "//packages/core/schematics/migrations/can-activate-with-redirect-to", "//packages/core/schematics/migrations/dynamic-queries", diff --git a/packages/core/schematics/migrations/google3/noMissingInjectableRule.ts b/packages/core/schematics/migrations/google3/noMissingInjectableRule.ts index 0329033902139..e1eabb0cac776 100644 --- a/packages/core/schematics/migrations/google3/noMissingInjectableRule.ts +++ b/packages/core/schematics/migrations/google3/noMissingInjectableRule.ts @@ -6,6 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ +import {forwardRefResolver} from '@angular/compiler-cli/src/ngtsc/annotations'; +import {Reference} from '@angular/compiler-cli/src/ngtsc/imports'; +import {DynamicValue, PartialEvaluator} from '@angular/compiler-cli/src/ngtsc/partial_evaluator'; +import {StaticInterpreter} from '@angular/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter'; +import {reflectObjectLiteral, TypeScriptReflectionHost} from '@angular/compiler-cli/src/ngtsc/reflection'; import {RuleFailure, Rules} from 'tslint'; import ts from 'typescript'; @@ -34,7 +39,15 @@ export class Rule extends Rules.TypedRule { sourceFiles.forEach(sourceFile => definitionCollector.visitNode(sourceFile)); const {resolvedModules, resolvedDirectives} = definitionCollector; - const transformer = new MissingInjectableTransform(typeChecker, getUpdateRecorder); + const transformer = new MissingInjectableTransform(typeChecker, getUpdateRecorder, { + Reference, + DynamicValue, + PartialEvaluator, + StaticInterpreter, + TypeScriptReflectionHost, + forwardRefResolver, + reflectObjectLiteral, + }); const updateRecorders = new Map(); [...transformer.migrateModules(resolvedModules), diff --git a/packages/core/schematics/migrations/google3/undecoratedClassesWithDecoratedFieldsRule.ts b/packages/core/schematics/migrations/google3/undecoratedClassesWithDecoratedFieldsRule.ts index 3f7fd6d0b55e8..99492c0efb76c 100644 --- a/packages/core/schematics/migrations/google3/undecoratedClassesWithDecoratedFieldsRule.ts +++ b/packages/core/schematics/migrations/google3/undecoratedClassesWithDecoratedFieldsRule.ts @@ -5,7 +5,11 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - +import {forwardRefResolver} from '@angular/compiler-cli/src/ngtsc/annotations'; +import {Reference} from '@angular/compiler-cli/src/ngtsc/imports'; +import {DynamicValue, PartialEvaluator} from '@angular/compiler-cli/src/ngtsc/partial_evaluator'; +import {StaticInterpreter} from '@angular/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter'; +import {reflectObjectLiteral, TypeScriptReflectionHost} from '@angular/compiler-cli/src/ngtsc/reflection'; import {RuleFailure, Rules} from 'tslint'; import ts from 'typescript'; import {TslintUpdateRecorder} from '../undecorated-classes-with-decorated-fields/google3/tslint_update_recorder'; @@ -23,7 +27,15 @@ export class Rule extends Rules.TypedRule { s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s)); const updateRecorders = new Map(); const transform = - new UndecoratedClassesWithDecoratedFieldsTransform(typeChecker, getUpdateRecorder); + new UndecoratedClassesWithDecoratedFieldsTransform(typeChecker, getUpdateRecorder, { + Reference, + DynamicValue, + PartialEvaluator, + StaticInterpreter, + TypeScriptReflectionHost, + forwardRefResolver, + reflectObjectLiteral, + }); // Migrate all source files in the project. transform.migrate(sourceFiles); diff --git a/packages/core/schematics/migrations/missing-injectable/index.ts b/packages/core/schematics/migrations/missing-injectable/index.ts index 09a024894b4e9..5052f5ff45473 100644 --- a/packages/core/schematics/migrations/missing-injectable/index.ts +++ b/packages/core/schematics/migrations/missing-injectable/index.ts @@ -9,6 +9,8 @@ import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; import {relative} from 'path'; import ts from 'typescript'; + +import {loadCompilerCliMigrationsModule, loadEsmModule} from '../../utils/load_esm'; import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host'; import {NgDefinitionCollector} from './definition_collector'; @@ -28,8 +30,20 @@ export default function(): Rule { 'which don\'t have that decorator set.'); } + let compilerCliMigrationsModule; + try { + // Load ESM `@angular/compiler/private/migrations` using the TypeScript dynamic import + // workaround. Once TypeScript provides support for keeping the dynamic import this workaround + // can be changed to a direct dynamic import. + compilerCliMigrationsModule = await loadCompilerCliMigrationsModule(); + } catch (e) { + throw new SchematicsException( + `Unable to load the '@angular/compiler-cli' package. Details: ${e.message}`); + } + for (const tsconfigPath of [...buildPaths, ...testPaths]) { - failures.push(...runMissingInjectableMigration(tree, tsconfigPath, basePath)); + failures.push(...runMissingInjectableMigration( + tree, tsconfigPath, basePath, compilerCliMigrationsModule)); } if (failures.length) { @@ -41,7 +55,9 @@ export default function(): Rule { } function runMissingInjectableMigration( - tree: Tree, tsconfigPath: string, basePath: string): string[] { + tree: Tree, tsconfigPath: string, basePath: string, + compilerCliMigrationsModule: typeof import('@angular/compiler-cli/private/migrations')): + string[] { const {program} = createMigrationProgram(tree, tsconfigPath, basePath); const failures: string[] = []; const typeChecker = program.getTypeChecker(); @@ -53,7 +69,8 @@ function runMissingInjectableMigration( sourceFiles.forEach(sourceFile => definitionCollector.visitNode(sourceFile)); const {resolvedModules, resolvedDirectives} = definitionCollector; - const transformer = new MissingInjectableTransform(typeChecker, getUpdateRecorder); + const transformer = + new MissingInjectableTransform(typeChecker, getUpdateRecorder, compilerCliMigrationsModule); const updateRecorders = new Map(); [...transformer.migrateModules(resolvedModules), diff --git a/packages/core/schematics/migrations/missing-injectable/providers_evaluator.ts b/packages/core/schematics/migrations/missing-injectable/providers_evaluator.ts index b7078fec3a873..1d7928454c43e 100644 --- a/packages/core/schematics/migrations/missing-injectable/providers_evaluator.ts +++ b/packages/core/schematics/migrations/missing-injectable/providers_evaluator.ts @@ -6,51 +6,69 @@ * found in the LICENSE file at https://angular.io/license */ -import {forwardRefResolver, ResolvedValue, StaticInterpreter} from '@angular/compiler-cli/private/migrations'; import ts from 'typescript'; +import type {ResolvedValue, TypeScriptReflectionHost} from '@angular/compiler-cli/private/migrations'; + export interface ProviderLiteral { node: ts.ObjectLiteralExpression; resolvedValue: ResolvedValue; } /** - * Providers evaluator that extends the ngtsc static interpreter. This is necessary because - * the static interpreter by default only exposes the resolved value, but we are also interested - * in the TypeScript nodes that declare providers. It would be possible to manually traverse the - * AST to collect these nodes, but that would mean that we need to re-implement the static - * interpreter in order to handle all possible scenarios. (e.g. spread operator, function calls, - * callee scope). This can be avoided by simply extending the static interpreter and intercepting - * the "visitObjectLiteralExpression" method. + * A factory function to create an evaluator for providers. This is required to be a + * factory function because the underlying class extends a class that is only available + * from within a dynamically imported module (`@angular/compiler-cli/private/migrations`) + * and is therefore not available at module evaluation time. */ -export class ProvidersEvaluator extends StaticInterpreter { - private _providerLiterals: ProviderLiteral[] = []; +export function createProvidersEvaluator( + compilerCliMigrationsModule: typeof import('@angular/compiler-cli/private/migrations'), + host: TypeScriptReflectionHost, checker: ts.TypeChecker): { + evaluate: + (expr: ts.Expression) => { + resolvedValue: ResolvedValue, literals: ProviderLiteral[] + } +} { + /** + * Providers evaluator that extends the ngtsc static interpreter. This is necessary because + * the static interpreter by default only exposes the resolved value, but we are also interested + * in the TypeScript nodes that declare providers. It would be possible to manually traverse the + * AST to collect these nodes, but that would mean that we need to re-implement the static + * interpreter in order to handle all possible scenarios. (e.g. spread operator, function calls, + * callee scope). This can be avoided by simply extending the static interpreter and intercepting + * the "visitObjectLiteralExpression" method. + */ + class ProvidersEvaluator extends compilerCliMigrationsModule.StaticInterpreter { + private _providerLiterals: ProviderLiteral[] = []; - override visitObjectLiteralExpression(node: ts.ObjectLiteralExpression, context: any) { - const resolvedValue = - super.visitObjectLiteralExpression(node, {...context, insideProviderDef: true}); - // do not collect nested object literals. e.g. a provider could use a - // spread assignment (which resolves to another object literal). In that - // case the referenced object literal is not a provider object literal. - if (!context.insideProviderDef) { - this._providerLiterals.push({node, resolvedValue}); + override visitObjectLiteralExpression(node: ts.ObjectLiteralExpression, context: any) { + const resolvedValue = + super.visitObjectLiteralExpression(node, {...context, insideProviderDef: true}); + // do not collect nested object literals. e.g. a provider could use a + // spread assignment (which resolves to another object literal). In that + // case the referenced object literal is not a provider object literal. + if (!context.insideProviderDef) { + this._providerLiterals.push({node, resolvedValue}); + } + return resolvedValue; } - return resolvedValue; - } - /** - * Evaluates the given expression and returns its statically resolved value - * and a list of object literals which define Angular providers. - */ - evaluate(expr: ts.Expression) { - this._providerLiterals = []; - const resolvedValue = this.visit(expr, { - originatingFile: expr.getSourceFile(), - absoluteModuleName: null, - resolutionContext: expr.getSourceFile().fileName, - scope: new Map(), - foreignFunctionResolver: forwardRefResolver - }); - return {resolvedValue, literals: this._providerLiterals}; + /** + * Evaluates the given expression and returns its statically resolved value + * and a list of object literals which define Angular providers. + */ + evaluate(expr: ts.Expression) { + this._providerLiterals = []; + const resolvedValue = this.visit(expr, { + originatingFile: expr.getSourceFile(), + absoluteModuleName: null, + resolutionContext: expr.getSourceFile().fileName, + scope: new Map(), + foreignFunctionResolver: compilerCliMigrationsModule.forwardRefResolver + }); + return {resolvedValue, literals: this._providerLiterals}; + } } + + return new ProvidersEvaluator(host, checker, /* dependencyTracker */ null); } diff --git a/packages/core/schematics/migrations/missing-injectable/transform.ts b/packages/core/schematics/migrations/missing-injectable/transform.ts index 5fe528cd60fcb..0c996748e3e4c 100644 --- a/packages/core/schematics/migrations/missing-injectable/transform.ts +++ b/packages/core/schematics/migrations/missing-injectable/transform.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {DynamicValue, Reference, ResolvedValue, TypeScriptReflectionHost} from '@angular/compiler-cli/private/migrations'; +import type {ResolvedValue} from '@angular/compiler-cli/private/migrations'; import ts from 'typescript'; import {ImportManager} from '../../utils/import_manager'; import {getAngularDecorators} from '../../utils/ng_decorators'; import {ResolvedDirective, ResolvedNgModule} from './definition_collector'; -import {ProviderLiteral, ProvidersEvaluator} from './providers_evaluator'; +import {createProvidersEvaluator, ProviderLiteral} from './providers_evaluator'; import {UpdateRecorder} from './update_recorder'; /** @@ -33,7 +33,7 @@ export interface AnalysisFailure { export class MissingInjectableTransform { private printer = ts.createPrinter(); private importManager = new ImportManager(this.getUpdateRecorder, this.printer); - private providersEvaluator: ProvidersEvaluator; + private providersEvaluator; /** Set of provider class declarations which were already checked or migrated. */ private visitedProviderClasses = new Set(); @@ -43,9 +43,12 @@ export class MissingInjectableTransform { constructor( private typeChecker: ts.TypeChecker, - private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder) { - this.providersEvaluator = new ProvidersEvaluator( - new TypeScriptReflectionHost(typeChecker), typeChecker, /* dependencyTracker */ null); + private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder, + private compilerCliMigrationsModule: + typeof import('@angular/compiler-cli/private/migrations')) { + this.providersEvaluator = createProvidersEvaluator( + compilerCliMigrationsModule, + new compilerCliMigrationsModule.TypeScriptReflectionHost(typeChecker), typeChecker); } recordChanges() { @@ -214,7 +217,8 @@ export class MissingInjectableTransform { */ private _visitProviderResolvedValue(value: ResolvedValue, module: ResolvedNgModule): AnalysisFailure[] { - if (value instanceof Reference && ts.isClassDeclaration(value.node)) { + if (value instanceof this.compilerCliMigrationsModule.Reference && + ts.isClassDeclaration(value.node)) { this.migrateProviderClass(value.node, module); } else if (value instanceof Map) { // If a "ClassProvider" has the "deps" property set, then we do not need to @@ -227,7 +231,7 @@ export class MissingInjectableTransform { return value.reduce( (res, v) => res.concat(this._visitProviderResolvedValue(v, module)), [] as AnalysisFailure[]); - } else if (value instanceof DynamicValue) { + } else if (value instanceof this.compilerCliMigrationsModule.DynamicValue) { return [{node: value.node, message: `Provider is not statically analyzable.`}]; } return []; diff --git a/packages/core/schematics/migrations/module-with-providers/index.ts b/packages/core/schematics/migrations/module-with-providers/index.ts index 946dc8df8d13a..b315211ac426e 100644 --- a/packages/core/schematics/migrations/module-with-providers/index.ts +++ b/packages/core/schematics/migrations/module-with-providers/index.ts @@ -10,6 +10,7 @@ import {Rule, SchematicContext, SchematicsException, Tree, UpdateRecorder} from import {relative} from 'path'; import ts from 'typescript'; +import {loadCompilerCliMigrationsModule, loadEsmModule} from '../../utils/load_esm'; import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host'; @@ -32,8 +33,20 @@ export default function(): Rule { 'Could not find any tsconfig file. Cannot migrate ModuleWithProviders.'); } + let compilerCliMigrationsModule; + try { + // Load ESM `@angular/compiler/private/migrations` using the TypeScript dynamic import + // workaround. Once TypeScript provides support for keeping the dynamic import this workaround + // can be changed to a direct dynamic import. + compilerCliMigrationsModule = await loadCompilerCliMigrationsModule(); + } catch (e) { + throw new SchematicsException( + `Unable to load the '@angular/compiler-cli' package. Details: ${e.message}`); + } + for (const tsconfigPath of allPaths) { - failures.push(...runModuleWithProvidersMigration(tree, tsconfigPath, basePath)); + failures.push(...runModuleWithProvidersMigration( + tree, tsconfigPath, basePath, compilerCliMigrationsModule)); } if (failures.length) { @@ -44,7 +57,9 @@ export default function(): Rule { }; } -function runModuleWithProvidersMigration(tree: Tree, tsconfigPath: string, basePath: string) { +function runModuleWithProvidersMigration( + tree: Tree, tsconfigPath: string, basePath: string, + compilerCliMigrationsModule: typeof import('@angular/compiler-cli/private/migrations')) { const {program} = createMigrationProgram(tree, tsconfigPath, basePath); const failures: string[] = []; const typeChecker = program.getTypeChecker(); @@ -56,7 +71,8 @@ function runModuleWithProvidersMigration(tree: Tree, tsconfigPath: string, baseP sourceFiles.forEach(sourceFile => collector.visitNode(sourceFile)); const {resolvedModules, resolvedNonGenerics} = collector; - const transformer = new ModuleWithProvidersTransform(typeChecker, getUpdateRecorder); + const transformer = + new ModuleWithProvidersTransform(typeChecker, getUpdateRecorder, compilerCliMigrationsModule); const updateRecorders = new Map(); [...resolvedModules.reduce( diff --git a/packages/core/schematics/migrations/module-with-providers/transform.ts b/packages/core/schematics/migrations/module-with-providers/transform.ts index 02eb225bc5e5d..9064cfb530ad0 100644 --- a/packages/core/schematics/migrations/module-with-providers/transform.ts +++ b/packages/core/schematics/migrations/module-with-providers/transform.ts @@ -7,7 +7,7 @@ */ import {UpdateRecorder} from '@angular-devkit/schematics'; -import {DynamicValue, PartialEvaluator, Reference, ResolvedValue, ResolvedValueMap, TypeScriptReflectionHost} from '@angular/compiler-cli/private/migrations'; +import type {ResolvedValue, ResolvedValueMap} from '@angular/compiler-cli/private/migrations'; import ts from 'typescript'; import {ResolvedNgModule} from './collector'; @@ -22,13 +22,16 @@ const TODO_COMMENT = 'TODO: The following node requires a generic type for `Modu export class ModuleWithProvidersTransform { private printer = ts.createPrinter(); - private partialEvaluator: PartialEvaluator = new PartialEvaluator( - new TypeScriptReflectionHost(this.typeChecker), this.typeChecker, + private partialEvaluator = new this.compilerCliMigrationsModule.PartialEvaluator( + new this.compilerCliMigrationsModule.TypeScriptReflectionHost(this.typeChecker), + this.typeChecker, /* dependencyTracker */ null); constructor( private typeChecker: ts.TypeChecker, - private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder) {} + private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder, + private compilerCliMigrationsModule: + typeof import('@angular/compiler-cli/private/migrations')) {} /** Migrates a given NgModule by walking through the referenced providers and static methods. */ migrateModule(module: ResolvedNgModule): AnalysisFailure[] { @@ -132,10 +135,10 @@ export class ModuleWithProvidersTransform { private _getTypeOfResolvedValue(value: ResolvedValue): string|undefined { if (value instanceof Map && this.isModuleWithProvidersType(value)) { const mapValue = value.get('ngModule')!; - if (mapValue instanceof Reference && ts.isClassDeclaration(mapValue.node) && - mapValue.node.name) { + if (mapValue instanceof this.compilerCliMigrationsModule.Reference && + ts.isClassDeclaration(mapValue.node) && mapValue.node.name) { return mapValue.node.name.text; - } else if (mapValue instanceof DynamicValue) { + } else if (mapValue instanceof this.compilerCliMigrationsModule.DynamicValue) { addTodoToNode(mapValue.node, TODO_COMMENT); this._updateNode(mapValue.node, mapValue.node); } diff --git a/packages/core/schematics/migrations/static-queries/index.ts b/packages/core/schematics/migrations/static-queries/index.ts index 2572698bcf801..bc058565b022d 100644 --- a/packages/core/schematics/migrations/static-queries/index.ts +++ b/packages/core/schematics/migrations/static-queries/index.ts @@ -81,10 +81,22 @@ async function runMigration(tree: Tree, context: SchematicContext) { `Unable to load the '@angular/compiler' package. Details: ${e.message}`); } + let compilerCliModule; + try { + // Load ESM `@angular/compiler-cli` using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + compilerCliModule = + await loadEsmModule('@angular/compiler-cli'); + } catch (e) { + throw new SchematicsException( + `Unable to load the '@angular/compiler' package. Details: ${e.message}`); + } + if (buildProjects.size) { for (let project of Array.from(buildProjects.values())) { - failures.push( - ...await runStaticQueryMigration(tree, project, strategy, logger, compilerModule)); + failures.push(...await runStaticQueryMigration( + tree, project, strategy, logger, compilerModule, compilerCliModule)); } } @@ -94,7 +106,7 @@ async function runMigration(tree: Tree, context: SchematicContext) { const project = await analyzeProject(tree, tsconfigPath, basePath, analyzedFiles, logger); if (project) { failures.push(...await runStaticQueryMigration( - tree, project, SELECTED_STRATEGY.TESTS, logger, compilerModule)); + tree, project, SELECTED_STRATEGY.TESTS, logger, compilerModule, compilerCliModule)); } } @@ -163,8 +175,8 @@ function analyzeProject( */ async function runStaticQueryMigration( tree: Tree, project: AnalyzedProject, selectedStrategy: SELECTED_STRATEGY, - logger: logging.LoggerApi, - compilerModule: typeof import('@angular/compiler')): Promise { + logger: logging.LoggerApi, compilerModule: typeof import('@angular/compiler'), + compilerCliModule: typeof import('@angular/compiler-cli')): Promise { const {sourceFiles, typeChecker, host, queryVisitor, tsconfigPath, basePath} = project; const printer = ts.createPrinter(); const failureMessages: string[] = []; @@ -195,7 +207,8 @@ async function runStaticQueryMigration( } else if (selectedStrategy === SELECTED_STRATEGY.TESTS) { strategy = new QueryTestStrategy(); } else { - strategy = new QueryTemplateStrategy(tsconfigPath, classMetadata, host, compilerModule); + strategy = new QueryTemplateStrategy( + tsconfigPath, classMetadata, host, compilerModule, compilerCliModule); } try { diff --git a/packages/core/schematics/migrations/static-queries/strategies/template_strategy/template_strategy.ts b/packages/core/schematics/migrations/static-queries/strategies/template_strategy/template_strategy.ts index 36ad7d81a4665..054c37fa26feb 100644 --- a/packages/core/schematics/migrations/static-queries/strategies/template_strategy/template_strategy.ts +++ b/packages/core/schematics/migrations/static-queries/strategies/template_strategy/template_strategy.ts @@ -6,8 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {AotCompiler, CompileDirectiveMetadata, CompileMetadataResolver, CompileNgModuleMetadata, CompileStylesheetMetadata, ElementAst, EmbeddedTemplateAst, NgAnalyzedModules, QueryMatch, StaticSymbol, TemplateAst} from '@angular/compiler'; -import {createProgram, Diagnostic, readConfiguration} from '@angular/compiler-cli'; +import type {AotCompiler, CompileDirectiveMetadata, CompileMetadataResolver, CompileNgModuleMetadata, CompileStylesheetMetadata, NgAnalyzedModules, QueryMatch, StaticSymbol, TemplateAst} from '@angular/compiler'; import {resolve} from 'path'; import ts from 'typescript'; @@ -25,14 +24,15 @@ export class QueryTemplateStrategy implements TimingStrategy { constructor( private projectPath: string, private classMetadata: ClassMetadataMap, - private host: ts.CompilerHost, private compilerModule: typeof import('@angular/compiler')) {} + private host: ts.CompilerHost, private compilerModule: typeof import('@angular/compiler'), + private compilerCliModule: typeof import('@angular/compiler-cli')) {} /** * Sets up the template strategy by creating the AngularCompilerProgram. Returns false if * the AOT compiler program could not be created due to failure diagnostics. */ setup() { - const {rootNames, options} = readConfiguration(this.projectPath); + const {rootNames, options} = this.compilerCliModule.readConfiguration(this.projectPath); // https://github.com/angular/angular/commit/ec4381dd401f03bded652665b047b6b90f2b425f made Ivy // the default. This breaks the assumption that "createProgram" from compiler-cli returns the @@ -40,7 +40,7 @@ export class QueryTemplateStrategy implements TimingStrategy { // false. options.enableIvy = false; - const aotProgram = createProgram({rootNames, options, host: this.host}); + const aotProgram = this.compilerCliModule.createProgram({rootNames, options, host: this.host}); // The "AngularCompilerProgram" does not expose the "AotCompiler" instance, nor does it // expose the logic that is necessary to analyze the determined modules. We work around @@ -65,7 +65,7 @@ export class QueryTemplateStrategy implements TimingStrategy { const ngStructuralDiagnostics = aotProgram.getNgStructuralDiagnostics(); if (ngStructuralDiagnostics.length) { - throw this._createDiagnosticsError(ngStructuralDiagnostics); + throw new Error(this.compilerCliModule.formatDiagnostics(ngStructuralDiagnostics, this.host)); } analyzedModules.files.forEach(file => { @@ -180,10 +180,6 @@ export class QueryTemplateStrategy implements TimingStrategy { .template; } - private _createDiagnosticsError(diagnostics: ReadonlyArray) { - return new Error(ts.formatDiagnostics(diagnostics as ts.Diagnostic[], this.host)); - } - private _getViewQueryUniqueKey(filePath: string, className: string, propName: string) { return `${resolve(filePath)}#${className}-${propName}`; } diff --git a/packages/core/schematics/migrations/undecorated-classes-with-decorated-fields/index.ts b/packages/core/schematics/migrations/undecorated-classes-with-decorated-fields/index.ts index 6a778fc5b435b..fea367105eb3a 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-decorated-fields/index.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-decorated-fields/index.ts @@ -10,6 +10,7 @@ import {Rule, SchematicContext, SchematicsException, Tree,} from '@angular-devki import {relative} from 'path'; import ts from 'typescript'; +import {loadCompilerCliMigrationsModule, loadEsmModule} from '../../utils/load_esm'; import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host'; @@ -32,8 +33,20 @@ export default function(): Rule { 'Could not find any tsconfig file. Cannot add an Angular decorator to undecorated classes.'); } + let compilerCliMigrationsModule; + try { + // Load ESM `@angular/compiler/private/migrations` using the TypeScript dynamic import + // workaround. Once TypeScript provides support for keeping the dynamic import this workaround + // can be changed to a direct dynamic import. + compilerCliMigrationsModule = await loadCompilerCliMigrationsModule(); + } catch (e) { + throw new SchematicsException( + `Unable to load the '@angular/compiler-cli' package. Details: ${e.message}`); + } + for (const tsconfigPath of allPaths) { - failures.push(...runUndecoratedClassesMigration(tree, tsconfigPath, basePath)); + failures.push(...runUndecoratedClassesMigration( + tree, tsconfigPath, basePath, compilerCliMigrationsModule)); } if (failures.length) { @@ -45,15 +58,17 @@ export default function(): Rule { } function runUndecoratedClassesMigration( - tree: Tree, tsconfigPath: string, basePath: string): string[] { + tree: Tree, tsconfigPath: string, basePath: string, + compilerCliMigrationsModule: typeof import('@angular/compiler-cli/private/migrations')): + string[] { const failures: string[] = []; const {program} = createMigrationProgram(tree, tsconfigPath, basePath); const typeChecker = program.getTypeChecker(); const sourceFiles = program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program)); const updateRecorders = new Map(); - const transform = - new UndecoratedClassesWithDecoratedFieldsTransform(typeChecker, getUpdateRecorder); + const transform = new UndecoratedClassesWithDecoratedFieldsTransform( + typeChecker, getUpdateRecorder, compilerCliMigrationsModule); // Migrate all source files in the project. transform.migrate(sourceFiles).forEach(({node, message}) => { diff --git a/packages/core/schematics/migrations/undecorated-classes-with-decorated-fields/transform.ts b/packages/core/schematics/migrations/undecorated-classes-with-decorated-fields/transform.ts index d774bda1b3825..6f5c2500787a6 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-decorated-fields/transform.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-decorated-fields/transform.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {PartialEvaluator, reflectObjectLiteral, TypeScriptReflectionHost} from '@angular/compiler-cli/private/migrations'; import ts from 'typescript'; import {ImportManager} from '../../utils/import_manager'; @@ -76,12 +75,16 @@ const AMBIGUOUS_CLASS_TODO = 'Add Angular decorator.'; export class UndecoratedClassesWithDecoratedFieldsTransform { private printer = ts.createPrinter(); private importManager = new ImportManager(this.getUpdateRecorder, this.printer); - private reflectionHost = new TypeScriptReflectionHost(this.typeChecker); - private partialEvaluator = new PartialEvaluator(this.reflectionHost, this.typeChecker, null); + private reflectionHost = + new this.compilerCliMigrationsModule.TypeScriptReflectionHost(this.typeChecker); + private partialEvaluator = new this.compilerCliMigrationsModule.PartialEvaluator( + this.reflectionHost, this.typeChecker, null); constructor( private typeChecker: ts.TypeChecker, - private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder) {} + private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder, + private compilerCliMigrationsModule: + typeof import('@angular/compiler-cli/private/migrations')) {} /** * Migrates the specified source files. The transform adds the abstract `@Directive` @@ -265,7 +268,7 @@ export class UndecoratedClassesWithDecoratedFieldsTransform { if (!ts.isObjectLiteralExpression(metadataExpr)) { return false; } - const metadata = reflectObjectLiteral(metadataExpr); + const metadata = this.compilerCliMigrationsModule.reflectObjectLiteral(metadataExpr); if (!metadata.has('selector')) { return false; } diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/BUILD.bazel b/packages/core/schematics/migrations/undecorated-classes-with-di/BUILD.bazel index 0ac873f0cff8b..138542abe748b 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/BUILD.bazel +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/BUILD.bazel @@ -12,7 +12,6 @@ ts_library( deps = [ "//packages/compiler", "//packages/compiler-cli", - "//packages/compiler-cli/private", "//packages/core", "//packages/core/schematics/utils", "@npm//@angular-devkit/core", diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/create_ngc_program.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/create_ngc_program.ts index 16fba02bbe9ca..a97f4aa16d0f1 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/create_ngc_program.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/create_ngc_program.ts @@ -7,13 +7,14 @@ */ import type {AotCompiler} from '@angular/compiler'; -import {CompilerHost, createProgram, readConfiguration} from '@angular/compiler-cli'; +import type {CompilerHost} from '@angular/compiler-cli'; import ts from 'typescript'; /** Creates an NGC program that can be used to read and parse metadata for files. */ export function createNgcProgram( + compilerCliModule: typeof import('@angular/compiler-cli'), createHost: (options: ts.CompilerOptions) => CompilerHost, tsconfigPath: string) { - const {rootNames, options} = readConfiguration(tsconfigPath); + const {rootNames, options} = compilerCliModule.readConfiguration(tsconfigPath); // https://github.com/angular/angular/commit/ec4381dd401f03bded652665b047b6b90f2b425f made Ivy // the default. This breaks the assumption that "createProgram" from compiler-cli returns the @@ -42,7 +43,7 @@ export function createNgcProgram( host.readResource = () => ''; host.resourceNameToFileName = () => '$fake-file$'; - const ngcProgram = createProgram({rootNames, options, host}); + const ngcProgram = compilerCliModule.createProgram({rootNames, options, host}); // The "AngularCompilerProgram" does not expose the "AotCompiler" instance, nor does it // expose the logic that is necessary to analyze the determined modules. We work around diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/decorator_rewriter.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/decorator_rewriter.ts index 3a59c76b44d2f..12bb8f98a21cf 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/decorator_rewriter.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/decorator_rewriter.ts @@ -7,7 +7,6 @@ * found in the LICENSE file at https://angular.io/license */ import type {AotCompiler} from '@angular/compiler'; -import {PartialEvaluator} from '@angular/compiler-cli/private/migrations'; import ts from 'typescript'; import {ImportManager} from '../../../utils/import_manager'; @@ -35,7 +34,7 @@ export class DecoratorRewriter { constructor( private importManager: ImportManager, private typeChecker: ts.TypeChecker, - private evaluator: PartialEvaluator, private compiler: AotCompiler) {} + private compiler: AotCompiler) {} rewrite(ngDecorator: NgDecorator, newSourceFile: ts.SourceFile): ts.Decorator { const decorator = ngDecorator.node; diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/index.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/index.ts index c7f942040f95e..95479905fed13 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/index.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/index.ts @@ -9,12 +9,11 @@ import {logging} from '@angular-devkit/core'; import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; import type {AotCompiler} from '@angular/compiler'; -import {Diagnostic as NgDiagnostic} from '@angular/compiler-cli'; -import {PartialEvaluator, TypeScriptReflectionHost} from '@angular/compiler-cli/private/migrations'; +import type {Diagnostic as NgDiagnostic} from '@angular/compiler-cli'; import {relative} from 'path'; import ts from 'typescript'; -import {loadEsmModule} from '../../utils/load_esm'; +import {loadCompilerCliMigrationsModule, loadEsmModule} from '../../utils/load_esm'; import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; import {canMigrateFile, createMigrationCompilerHost} from '../../utils/typescript/compiler_host'; @@ -55,9 +54,44 @@ export default function(): Rule { `Unable to load the '@angular/compiler' package. Details: ${e.message}`); } + let compilerCliModule; + try { + // Load ESM `@angular/compiler-cli` using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + compilerCliModule = + await loadEsmModule('@angular/compiler-cli'); + } catch (e) { + throw new SchematicsException( + `Unable to load the '@angular/compiler-cli' package. Details: ${e.message}`); + } + + let coreModule; + try { + // Load ESM `@angular/compiler-cli` using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + coreModule = await loadEsmModule('@angular/core'); + } catch (e) { + throw new SchematicsException( + `Unable to load the '@angular/core' package. Details: ${e.message}`); + } + + let compilerCliMigrationsModule; + try { + // Load ESM `@angular/compiler/private/migrations` using the TypeScript dynamic import + // workaround. Once TypeScript provides support for keeping the dynamic import this workaround + // can be changed to a direct dynamic import. + compilerCliMigrationsModule = await loadCompilerCliMigrationsModule(); + } catch (e) { + throw new SchematicsException( + `Unable to load the '@angular/compiler-cli' package. Details: ${e.message}`); + } + for (const tsconfigPath of buildPaths) { - const result = - runUndecoratedClassesMigration(tree, tsconfigPath, basePath, ctx.logger, compilerModule); + const result = runUndecoratedClassesMigration( + tree, tsconfigPath, basePath, ctx.logger, compilerModule, compilerCliModule, + compilerCliMigrationsModule, coreModule); failures.push(...result.failures); programError = programError || !!result.programError; } @@ -83,10 +117,13 @@ export default function(): Rule { function runUndecoratedClassesMigration( tree: Tree, tsconfigPath: string, basePath: string, logger: logging.LoggerApi, - compilerModule: typeof import('@angular/compiler')): - {failures: string[], programError?: boolean} { + compilerModule: typeof import('@angular/compiler'), + compilerCliModule: typeof import('@angular/compiler-cli'), + compilerCliMigrationsModule: typeof import('@angular/compiler-cli/private/migrations'), + coreModule: typeof import('@angular/core')): {failures: string[], programError?: boolean} { const failures: string[] = []; - const programData = gracefullyCreateProgram(tree, basePath, tsconfigPath, logger); + const programData = + gracefullyCreateProgram(tree, basePath, tsconfigPath, logger, compilerCliModule); // Gracefully exit if the program could not be created. if (programData === null) { @@ -95,9 +132,8 @@ function runUndecoratedClassesMigration( const {program, compiler} = programData; const typeChecker = program.getTypeChecker(); - const partialEvaluator = new PartialEvaluator( - new TypeScriptReflectionHost(typeChecker), typeChecker, /* dependencyTracker */ null); - const declarationCollector = new NgDeclarationCollector(typeChecker, partialEvaluator); + + const declarationCollector = new NgDeclarationCollector(typeChecker, compilerCliMigrationsModule); const sourceFiles = program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program)); @@ -106,7 +142,7 @@ function runUndecoratedClassesMigration( const {decoratedDirectives, decoratedProviders, undecoratedDeclarations} = declarationCollector; const transform = new UndecoratedClassesTransform( - typeChecker, compiler, partialEvaluator, getUpdateRecorder, compilerModule); + typeChecker, compiler, getUpdateRecorder, compilerModule, coreModule); const updateRecorders = new Map(); // Run the migrations for decorated providers and both decorated and undecorated @@ -173,11 +209,13 @@ function getErrorDiagnostics(diagnostics: ReadonlyArray createMigrationCompilerHost(tree, options, basePath), tsconfigPath); + compilerCliModule, (options) => createMigrationCompilerHost(tree, options, basePath), + tsconfigPath); const syntacticDiagnostics = getErrorDiagnostics(ngcProgram.getTsSyntacticDiagnostics()); const structuralDiagnostics = getErrorDiagnostics(ngcProgram.getNgStructuralDiagnostics()); const configDiagnostics = getErrorDiagnostics( diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/ng_declaration_collector.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/ng_declaration_collector.ts index bf3161e3711b9..99218da5228b0 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/ng_declaration_collector.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/ng_declaration_collector.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {PartialEvaluator, Reference, ResolvedValue} from '@angular/compiler-cli/private/migrations'; import ts from 'typescript'; import {getAngularDecorators, NgDecorator} from '../../utils/ng_decorators'; @@ -28,7 +27,16 @@ export class NgDeclarationCollector { /** Set of resolved Angular declarations which are not decorated. */ undecoratedDeclarations = new Set(); - constructor(public typeChecker: ts.TypeChecker, private evaluator: PartialEvaluator) {} + private evaluator; + + constructor( + public typeChecker: ts.TypeChecker, + private compilerCliMigrationsModule: + typeof import('@angular/compiler-cli/private/migrations')) { + this.evaluator = new compilerCliMigrationsModule.PartialEvaluator( + new compilerCliMigrationsModule.TypeScriptReflectionHost(typeChecker), typeChecker, + /* dependencyTracker */ null); + } visitNode(node: ts.Node) { if (ts.isClassDeclaration(node)) { @@ -80,39 +88,29 @@ export class NgDeclarationCollector { } }); + const values = []; + // In case the module specifies the "entryComponents" field, walk through all // resolved entry components and collect the referenced directives. if (entryComponentsNode) { - flattenTypeList(this.evaluator.evaluate(entryComponentsNode)).forEach(ref => { - if (ts.isClassDeclaration(ref.node) && - !hasNgDeclarationDecorator(ref.node, this.typeChecker)) { - this.undecoratedDeclarations.add(ref.node); - } - }); + values.push(this.evaluator.evaluate(entryComponentsNode)); } // In case the module specifies the "declarations" field, walk through all // resolved declarations and collect the referenced directives. if (declarationsNode) { - flattenTypeList(this.evaluator.evaluate(declarationsNode)).forEach(ref => { - if (ts.isClassDeclaration(ref.node) && - !hasNgDeclarationDecorator(ref.node, this.typeChecker)) { - this.undecoratedDeclarations.add(ref.node); - } - }); + values.push(this.evaluator.evaluate(declarationsNode)); } - } -} -/** Flattens a list of type references. */ -function flattenTypeList(value: ResolvedValue): Reference[] { - if (Array.isArray(value)) { - return value.reduce( - (res: Reference[], v: ResolvedValue) => res.concat(flattenTypeList(v)), []); - } else if (value instanceof Reference) { - return [value]; + // Flatten values and analyze references + for (const value of values.flat(Infinity)) { + if (value instanceof this.compilerCliMigrationsModule.Reference && + ts.isClassDeclaration(value.node) && + !hasNgDeclarationDecorator(value.node, this.typeChecker)) { + this.undecoratedDeclarations.add(value.node); + } + } } - return []; } /** Checks whether the given node has the "@Directive" or "@Component" decorator set. */ diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts index dc260a19ec7b6..2d0e81277864a 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts @@ -7,8 +7,6 @@ */ import type {AotCompiler, AotCompilerHost, CompileMetadataResolver, StaticSymbol, StaticSymbolResolver, SummaryResolver} from '@angular/compiler'; -import {PartialEvaluator} from '@angular/compiler-cli/private/migrations'; -import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; import ts from 'typescript'; import {ImportManager} from '../../utils/import_manager'; @@ -39,7 +37,7 @@ export class UndecoratedClassesTransform { private printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed}); private importManager = new ImportManager(this.getUpdateRecorder, this.printer); private decoratorRewriter = - new DecoratorRewriter(this.importManager, this.typeChecker, this.evaluator, this.compiler); + new DecoratorRewriter(this.importManager, this.typeChecker, this.compiler); private compilerHost: AotCompilerHost; private symbolResolver: StaticSymbolResolver; @@ -57,9 +55,9 @@ export class UndecoratedClassesTransform { constructor( private typeChecker: ts.TypeChecker, private compiler: AotCompiler, - private evaluator: PartialEvaluator, private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder, - private compilerModule: typeof import('@angular/compiler')) { + private compilerModule: typeof import('@angular/compiler'), + private coreModule: typeof import('@angular/core')) { this.symbolResolver = compiler['_symbolResolver']; this.compilerHost = compiler['_host']; this.metadataResolver = compiler['_metadataResolver']; @@ -349,12 +347,12 @@ export class UndecoratedClassesTransform { return ts.createPropertyAccess( this.importManager.addImportToSourceFile( targetSourceFile, 'ChangeDetectionStrategy', '@angular/core'), - ChangeDetectionStrategy[value]); + this.coreModule.ChangeDetectionStrategy[value]); } else if (propertyName === 'encapsulation' && typeof value === 'number') { return ts.createPropertyAccess( this.importManager.addImportToSourceFile( targetSourceFile, 'ViewEncapsulation', '@angular/core'), - ViewEncapsulation[value]); + this.coreModule.ViewEncapsulation[value]); } return null; }); diff --git a/packages/core/schematics/utils/BUILD.bazel b/packages/core/schematics/utils/BUILD.bazel index 9254b9673abb4..d709032e7092a 100644 --- a/packages/core/schematics/utils/BUILD.bazel +++ b/packages/core/schematics/utils/BUILD.bazel @@ -7,6 +7,7 @@ ts_library( visibility = ["//packages/core/schematics:__subpackages__"], deps = [ "//packages/compiler", + "//packages/compiler-cli/private", "@npm//@angular-devkit/core", "@npm//@angular-devkit/schematics", "@npm//@types/inquirer", diff --git a/packages/core/schematics/utils/load_esm.ts b/packages/core/schematics/utils/load_esm.ts index 16d3b21189407..5ba0bc6fc2b51 100644 --- a/packages/core/schematics/utils/load_esm.ts +++ b/packages/core/schematics/utils/load_esm.ts @@ -33,3 +33,23 @@ export async function loadEsmModule(modulePath: string|URL): Promise { return namespaceObject; } } + +/** + * Attempt to load the new `@angular/compiler-cli/private/migrations` entry. If not yet present + * the previous deep imports are used to constructor an equivalent object. + * + * @returns A Promise that resolves to the dynamically imported compiler-cli private migrations + * entry or an equivalent object if not available. + */ +export async function loadCompilerCliMigrationsModule(): + Promise { + try { + return await loadEsmModule('@angular/compiler-cli/private/migrations'); + } catch { + // rules_nodejs currently does not support exports field entries. This is a temporary workaround + // that leverages devmode currently being CommonJS. If that changes before rules_nodejs supports + // exports then this workaround needs to be reworked. + // TODO_ESM: This can be removed once Bazel supports exports fields. + return require('@angular/compiler-cli/private/migrations.js'); + } +}