refactor(ivy): rename ngtsc/factories to ngtsc/shims (#26495)
This simple refactor of the build rules renames the .ngfactory.js shim generator to 'shims' instead of 'factories', in preparation for adding .ngsummary.js shim generation. Testing strategy: this commit does not introduce any new behavior and merely moves files and symbols around. It's sufficient that the existing ngtsc tests pass. PR Close #26495
This commit is contained in:

committed by
Alex Rickabaugh

parent
1700bd6f08
commit
0b885ecaf7
18
packages/compiler-cli/src/ngtsc/shims/BUILD.bazel
Normal file
18
packages/compiler-cli/src/ngtsc/shims/BUILD.bazel
Normal file
@ -0,0 +1,18 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "shims",
|
||||
srcs = glob([
|
||||
"index.ts",
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/shims",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/host",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
],
|
||||
)
|
11
packages/compiler-cli/src/ngtsc/shims/README.md
Normal file
11
packages/compiler-cli/src/ngtsc/shims/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
Deals with the creation of generated factory files.
|
||||
|
||||
Generated factory files create a catch-22 in ngtsc. Their contents depends on static analysis of the current program, yet they're also importable from the current program. This importability gives rise to the requirement that the contents of the generated file must be known before program creation, so that imports of it are valid. However, until the program is created, the analysis to determine the contents of the generated file cannot take place.
|
||||
|
||||
ngc used to get away with this because the analysis phase did not depend on program creation but on the metadata collection / global analysis process.
|
||||
|
||||
ngtsc is forced to take a different approach. A lightweight analysis pipeline which does not rely on the ts.TypeChecker (and thus can run before the program is created) is used to estimate the contents of a generated file, in a way that allows the program to be created. A transformer then operates on this estimated file during emit and replaces the estimated contents with accurate information.
|
||||
|
||||
It is important that this estimate be an overestimate, as type-checking will always be run against the estimated file, and must succeed in every case where it would have succeeded with accurate info.
|
||||
|
||||
This directory contains the utilities for generating, updating, and incorporating these factory files into a ts.Program.
|
13
packages/compiler-cli/src/ngtsc/shims/index.ts
Normal file
13
packages/compiler-cli/src/ngtsc/shims/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/// <reference types="node" />
|
||||
|
||||
export {FactoryGenerator} from './src/generator';
|
||||
export {GeneratedShimsHostWrapper} from './src/host';
|
||||
export {FactoryInfo, generatedFactoryTransform} from './src/transform';
|
63
packages/compiler-cli/src/ngtsc/shims/src/generator.ts
Normal file
63
packages/compiler-cli/src/ngtsc/shims/src/generator.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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 * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
const TS_DTS_SUFFIX = /(\.d)?\.ts$/;
|
||||
|
||||
/**
|
||||
* Generates ts.SourceFiles which contain variable declarations for NgFactories for every exported
|
||||
* class of an input ts.SourceFile.
|
||||
*/
|
||||
export class FactoryGenerator {
|
||||
factoryFor(original: ts.SourceFile, genFilePath: string): ts.SourceFile {
|
||||
const relativePathToSource =
|
||||
'./' + path.posix.basename(original.fileName).replace(TS_DTS_SUFFIX, '');
|
||||
// Collect a list of classes that need to have factory types emitted for them.
|
||||
const symbolNames = original
|
||||
.statements
|
||||
// Pick out top level class declarations...
|
||||
.filter(ts.isClassDeclaration)
|
||||
// which are named, exported, and have decorators.
|
||||
.filter(
|
||||
decl => isExported(decl) && decl.decorators !== undefined &&
|
||||
decl.name !== undefined)
|
||||
// Grab the symbol name.
|
||||
.map(decl => decl.name !.text);
|
||||
|
||||
// For each symbol name, generate a constant export of the corresponding NgFactory.
|
||||
// This will encompass a lot of symbols which don't need factories, but that's okay
|
||||
// because it won't miss any that do.
|
||||
const varLines = symbolNames.map(
|
||||
name => `export const ${name}NgFactory = new i0.ɵNgModuleFactory(${name});`);
|
||||
const sourceText = [
|
||||
// This might be incorrect if the current package being compiled is Angular core, but it's
|
||||
// okay to leave in at type checking time. TypeScript can handle this reference via its path
|
||||
// mapping, but downstream bundlers can't. If the current package is core itself, this will be
|
||||
// replaced in the factory transformer before emit.
|
||||
`import * as i0 from '@angular/core';`,
|
||||
`import {${symbolNames.join(', ')}} from '${relativePathToSource}';`,
|
||||
...varLines,
|
||||
].join('\n');
|
||||
return ts.createSourceFile(
|
||||
genFilePath, sourceText, original.languageVersion, true, ts.ScriptKind.TS);
|
||||
}
|
||||
|
||||
computeFactoryFileMap(files: ReadonlyArray<string>): Map<string, string> {
|
||||
const map = new Map<string, string>();
|
||||
files.filter(sourceFile => !sourceFile.endsWith('.d.ts'))
|
||||
.forEach(sourceFile => map.set(sourceFile.replace(/\.ts$/, '.ngfactory.ts'), sourceFile));
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
function isExported(decl: ts.Declaration): boolean {
|
||||
return decl.modifiers !== undefined &&
|
||||
decl.modifiers.some(mod => mod.kind == ts.SyntaxKind.ExportKeyword);
|
||||
}
|
82
packages/compiler-cli/src/ngtsc/shims/src/host.ts
Normal file
82
packages/compiler-cli/src/ngtsc/shims/src/host.ts
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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 * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {FactoryGenerator} from './generator';
|
||||
|
||||
/**
|
||||
* A wrapper around a `ts.CompilerHost` which supports generated files.
|
||||
*/
|
||||
export class GeneratedShimsHostWrapper implements ts.CompilerHost {
|
||||
constructor(
|
||||
private delegate: ts.CompilerHost, private generator: FactoryGenerator,
|
||||
private factoryToSourceMap: Map<string, string>) {
|
||||
if (delegate.resolveTypeReferenceDirectives) {
|
||||
// Backward compatibility with TypeScript 2.9 and older since return
|
||||
// type has changed from (ts.ResolvedTypeReferenceDirective | undefined)[]
|
||||
// to ts.ResolvedTypeReferenceDirective[] in Typescript 3.0
|
||||
type ts3ResolveTypeReferenceDirectives = (names: string[], containingFile: string) =>
|
||||
ts.ResolvedTypeReferenceDirective[];
|
||||
this.resolveTypeReferenceDirectives = (names: string[], containingFile: string) =>
|
||||
(delegate.resolveTypeReferenceDirectives as ts3ResolveTypeReferenceDirectives) !(
|
||||
names, containingFile);
|
||||
}
|
||||
}
|
||||
|
||||
resolveTypeReferenceDirectives?:
|
||||
(names: string[], containingFile: string) => ts.ResolvedTypeReferenceDirective[];
|
||||
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: ((message: string) => void)|undefined,
|
||||
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
|
||||
const canonical = this.getCanonicalFileName(fileName);
|
||||
if (this.factoryToSourceMap.has(canonical)) {
|
||||
const sourceFileName = this.getCanonicalFileName(this.factoryToSourceMap.get(canonical) !);
|
||||
const sourceFile = this.delegate.getSourceFile(
|
||||
sourceFileName, languageVersion, onError, shouldCreateNewSourceFile);
|
||||
if (sourceFile === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return this.generator.factoryFor(sourceFile, fileName);
|
||||
}
|
||||
return this.delegate.getSourceFile(
|
||||
fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
return this.delegate.getDefaultLibFileName(options);
|
||||
}
|
||||
|
||||
writeFile(
|
||||
fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError: ((message: string) => void)|undefined,
|
||||
sourceFiles: ReadonlyArray<ts.SourceFile>): void {
|
||||
return this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return this.delegate.getCurrentDirectory(); }
|
||||
|
||||
getDirectories(path: string): string[] { return this.delegate.getDirectories(path); }
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return this.delegate.getCanonicalFileName(fileName);
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames(): boolean { return this.delegate.useCaseSensitiveFileNames(); }
|
||||
|
||||
getNewLine(): string { return this.delegate.getNewLine(); }
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
return this.factoryToSourceMap.has(fileName) || this.delegate.fileExists(fileName);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string|undefined { return this.delegate.readFile(fileName); }
|
||||
}
|
78
packages/compiler-cli/src/ngtsc/shims/src/transform.ts
Normal file
78
packages/compiler-cli/src/ngtsc/shims/src/transform.ts
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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 * as ts from 'typescript';
|
||||
|
||||
import {relativePathBetween} from '../../util/src/path';
|
||||
|
||||
const STRIP_NG_FACTORY = /(.*)NgFactory$/;
|
||||
|
||||
export interface FactoryInfo {
|
||||
sourceFilePath: string;
|
||||
moduleSymbolNames: Set<string>;
|
||||
}
|
||||
|
||||
export function generatedFactoryTransform(
|
||||
factoryMap: Map<string, FactoryInfo>,
|
||||
coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory<ts.SourceFile> {
|
||||
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
||||
return (file: ts.SourceFile): ts.SourceFile => {
|
||||
return transformFactorySourceFile(factoryMap, context, coreImportsFrom, file);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function transformFactorySourceFile(
|
||||
factoryMap: Map<string, FactoryInfo>, context: ts.TransformationContext,
|
||||
coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile {
|
||||
// If this is not a generated file, it won't have factory info associated with it.
|
||||
if (!factoryMap.has(file.fileName)) {
|
||||
// Don't transform non-generated code.
|
||||
return file;
|
||||
}
|
||||
|
||||
const {moduleSymbolNames, sourceFilePath} = factoryMap.get(file.fileName) !;
|
||||
|
||||
const clone = ts.getMutableClone(file);
|
||||
|
||||
const transformedStatements = file.statements.map(stmt => {
|
||||
if (coreImportsFrom !== null && ts.isImportDeclaration(stmt) &&
|
||||
ts.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === '@angular/core') {
|
||||
const path = relativePathBetween(sourceFilePath, coreImportsFrom.fileName);
|
||||
if (path !== null) {
|
||||
return ts.updateImportDeclaration(
|
||||
stmt, stmt.decorators, stmt.modifiers, stmt.importClause, ts.createStringLiteral(path));
|
||||
} else {
|
||||
return ts.createNotEmittedStatement(stmt);
|
||||
}
|
||||
} else if (ts.isVariableStatement(stmt) && stmt.declarationList.declarations.length === 1) {
|
||||
const decl = stmt.declarationList.declarations[0];
|
||||
if (ts.isIdentifier(decl.name)) {
|
||||
const match = STRIP_NG_FACTORY.exec(decl.name.text);
|
||||
if (match === null || !moduleSymbolNames.has(match[1])) {
|
||||
// Remove the given factory as it wasn't actually for an NgModule.
|
||||
return ts.createNotEmittedStatement(stmt);
|
||||
}
|
||||
}
|
||||
return stmt;
|
||||
} else {
|
||||
return stmt;
|
||||
}
|
||||
});
|
||||
if (!transformedStatements.some(ts.isVariableStatement)) {
|
||||
// If the resulting file has no factories, include an empty export to
|
||||
// satisfy closure compiler.
|
||||
transformedStatements.push(ts.createVariableStatement(
|
||||
[ts.createModifier(ts.SyntaxKind.ExportKeyword)],
|
||||
ts.createVariableDeclarationList(
|
||||
[ts.createVariableDeclaration('ɵNonEmptyModule', undefined, ts.createTrue())],
|
||||
ts.NodeFlags.Const)));
|
||||
}
|
||||
clone.statements = ts.createNodeArray(transformedStatements);
|
||||
return clone;
|
||||
}
|
Reference in New Issue
Block a user