Skip to content

Commit 43d0e3d

Browse files
petebacondarwinIgorMinar
authored andcommitted
feat(ivy): implement initial ngcc package transformer (angular#24897)
PR Close angular#24897
1 parent 5b32aa4 commit 43d0e3d

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed

packages/compiler-cli/src/ngcc/src/main.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import {resolve} from 'path';
9+
import {PackageTransformer} from './transform/package_transformer';
910

1011
export function mainNgcc(args: string[]): number {
1112
const packagePath = resolve(args[0]);
1213

14+
// TODO: find all the package tyoes to transform
15+
// TODO: error/warning logging/handling etc
16+
17+
const transformer = new PackageTransformer();
18+
transformer.transform(packagePath, 'fesm2015');
19+
1320
return 0;
1421
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {writeFileSync} from 'fs';
9+
import {dirname, relative, resolve} from 'path';
10+
import {mkdir} from 'shelljs';
11+
import * as ts from 'typescript';
12+
13+
import {Analyzer} from '../analyzer';
14+
import {Esm2015ReflectionHost} from '../host/esm2015_host';
15+
import {Esm5ReflectionHost} from '../host/esm5_host';
16+
import {NgccReflectionHost} from '../host/ngcc_host';
17+
import {Esm2015FileParser} from '../parsing/esm2015_parser';
18+
import {Esm5FileParser} from '../parsing/esm5_parser';
19+
import {FileParser} from '../parsing/file_parser';
20+
import {getEntryPoints} from '../parsing/utils';
21+
import {Esm2015Renderer} from '../rendering/esm2015_renderer';
22+
import {Esm5Renderer} from '../rendering/esm5_renderer';
23+
import {FileInfo, Renderer} from '../rendering/renderer';
24+
25+
26+
/**
27+
* A Package is stored in a directory on disk and that directory can contain one or more package
28+
formats - e.g. fesm2015, UMD, etc.
29+
*
30+
* Each of these formats exposes one or more entry points, which are source files that need to be
31+
* parsed to identify the decorated exported classes that need to be analyzed and compiled by one or
32+
* more `DecoratorHandler` objects.
33+
*
34+
* Each entry point to a package is identified by a `SourceFile` that can be parsed and analyzed
35+
* to identify classes that need to be transformed; and then finally rendered and written to disk.
36+
37+
* The actual file which needs to be transformed depends upon the package format.
38+
*
39+
* - Flat file packages have all the classes in a single file.
40+
* - Other packages may re-export classes from other non-entry point files.
41+
* - Some formats may contain multiple "modules" in a single file.
42+
*/
43+
export class PackageTransformer {
44+
transform(packagePath: string, format: string): void {
45+
const sourceNodeModules = this.findNodeModulesPath(packagePath);
46+
const targetNodeModules = sourceNodeModules.replace(/node_modules$/, 'node_modules_ngtsc');
47+
const entryPointPaths = getEntryPoints(packagePath, format);
48+
entryPointPaths.forEach(entryPointPath => {
49+
const options: ts.CompilerOptions = {allowJs: true, rootDir: entryPointPath};
50+
const host = ts.createCompilerHost(options);
51+
const packageProgram = ts.createProgram([entryPointPath], options, host);
52+
const entryPointFile = packageProgram.getSourceFile(entryPointPath) !;
53+
const typeChecker = packageProgram.getTypeChecker();
54+
55+
const reflectionHost = this.getHost(format, packageProgram);
56+
const parser = this.getFileParser(format, packageProgram, reflectionHost);
57+
const analyzer = new Analyzer(typeChecker, reflectionHost);
58+
const renderer = this.getRenderer(format, packageProgram, reflectionHost);
59+
60+
const parsedFiles = parser.parseFile(entryPointFile);
61+
parsedFiles.forEach(parsedFile => {
62+
const analyzedFile = analyzer.analyzeFile(parsedFile);
63+
const targetPath = resolve(
64+
targetNodeModules, relative(sourceNodeModules, analyzedFile.sourceFile.fileName));
65+
const {source, map} = renderer.renderFile(analyzedFile, targetPath);
66+
this.writeFile(source);
67+
if (map) {
68+
this.writeFile(map);
69+
}
70+
});
71+
});
72+
}
73+
74+
getHost(format: string, program: ts.Program): NgccReflectionHost {
75+
switch (format) {
76+
case 'esm2015':
77+
case 'fesm2015':
78+
return new Esm2015ReflectionHost(program.getTypeChecker());
79+
case 'fesm5':
80+
return new Esm5ReflectionHost(program.getTypeChecker());
81+
default:
82+
throw new Error(`Relection host for "${format}" not yet implemented.`);
83+
}
84+
}
85+
86+
getFileParser(format: string, program: ts.Program, host: NgccReflectionHost): FileParser {
87+
switch (format) {
88+
case 'esm2015':
89+
case 'fesm2015':
90+
return new Esm2015FileParser(program, host);
91+
case 'fesm5':
92+
return new Esm5FileParser(program, host);
93+
default:
94+
throw new Error(`File parser for "${format}" not yet implemented.`);
95+
}
96+
}
97+
98+
getRenderer(format: string, program: ts.Program, host: NgccReflectionHost): Renderer {
99+
switch (format) {
100+
case 'esm2015':
101+
case 'fesm2015':
102+
return new Esm2015Renderer(host);
103+
case 'fesm5':
104+
return new Esm5Renderer(host);
105+
default:
106+
throw new Error(`Renderer for "${format}" not yet implemented.`);
107+
}
108+
}
109+
110+
findNodeModulesPath(src: string): string {
111+
while (src && !/node_modules$/.test(src)) {
112+
src = dirname(src);
113+
}
114+
return src;
115+
}
116+
117+
writeFile(file: FileInfo): void {
118+
mkdir('-p', dirname(file.path));
119+
writeFileSync(file.path, file.contents, 'utf8');
120+
}
121+
}

0 commit comments

Comments
 (0)