feat(ivy): implement initial ngcc package transformer (#24897)

PR Close #24897
This commit is contained in:
Pete Bacon Darwin 2018-07-16 10:24:32 +01:00 committed by Igor Minar
parent 5b32aa4486
commit 43d0e3dd72
2 changed files with 128 additions and 0 deletions

View File

@ -6,9 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/
import {resolve} from 'path';
import {PackageTransformer} from './transform/package_transformer';
export function mainNgcc(args: string[]): number {
const packagePath = resolve(args[0]);
// TODO: find all the package tyoes to transform
// TODO: error/warning logging/handling etc
const transformer = new PackageTransformer();
transformer.transform(packagePath, 'fesm2015');
return 0;
}

View File

@ -0,0 +1,121 @@
/**
* @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 {writeFileSync} from 'fs';
import {dirname, relative, resolve} from 'path';
import {mkdir} from 'shelljs';
import * as ts from 'typescript';
import {Analyzer} from '../analyzer';
import {Esm2015ReflectionHost} from '../host/esm2015_host';
import {Esm5ReflectionHost} from '../host/esm5_host';
import {NgccReflectionHost} from '../host/ngcc_host';
import {Esm2015FileParser} from '../parsing/esm2015_parser';
import {Esm5FileParser} from '../parsing/esm5_parser';
import {FileParser} from '../parsing/file_parser';
import {getEntryPoints} from '../parsing/utils';
import {Esm2015Renderer} from '../rendering/esm2015_renderer';
import {Esm5Renderer} from '../rendering/esm5_renderer';
import {FileInfo, Renderer} from '../rendering/renderer';
/**
* A Package is stored in a directory on disk and that directory can contain one or more package
formats - e.g. fesm2015, UMD, etc.
*
* Each of these formats exposes one or more entry points, which are source files that need to be
* parsed to identify the decorated exported classes that need to be analyzed and compiled by one or
* more `DecoratorHandler` objects.
*
* Each entry point to a package is identified by a `SourceFile` that can be parsed and analyzed
* to identify classes that need to be transformed; and then finally rendered and written to disk.
* The actual file which needs to be transformed depends upon the package format.
*
* - Flat file packages have all the classes in a single file.
* - Other packages may re-export classes from other non-entry point files.
* - Some formats may contain multiple "modules" in a single file.
*/
export class PackageTransformer {
transform(packagePath: string, format: string): void {
const sourceNodeModules = this.findNodeModulesPath(packagePath);
const targetNodeModules = sourceNodeModules.replace(/node_modules$/, 'node_modules_ngtsc');
const entryPointPaths = getEntryPoints(packagePath, format);
entryPointPaths.forEach(entryPointPath => {
const options: ts.CompilerOptions = {allowJs: true, rootDir: entryPointPath};
const host = ts.createCompilerHost(options);
const packageProgram = ts.createProgram([entryPointPath], options, host);
const entryPointFile = packageProgram.getSourceFile(entryPointPath) !;
const typeChecker = packageProgram.getTypeChecker();
const reflectionHost = this.getHost(format, packageProgram);
const parser = this.getFileParser(format, packageProgram, reflectionHost);
const analyzer = new Analyzer(typeChecker, reflectionHost);
const renderer = this.getRenderer(format, packageProgram, reflectionHost);
const parsedFiles = parser.parseFile(entryPointFile);
parsedFiles.forEach(parsedFile => {
const analyzedFile = analyzer.analyzeFile(parsedFile);
const targetPath = resolve(
targetNodeModules, relative(sourceNodeModules, analyzedFile.sourceFile.fileName));
const {source, map} = renderer.renderFile(analyzedFile, targetPath);
this.writeFile(source);
if (map) {
this.writeFile(map);
}
});
});
}
getHost(format: string, program: ts.Program): NgccReflectionHost {
switch (format) {
case 'esm2015':
case 'fesm2015':
return new Esm2015ReflectionHost(program.getTypeChecker());
case 'fesm5':
return new Esm5ReflectionHost(program.getTypeChecker());
default:
throw new Error(`Relection host for "${format}" not yet implemented.`);
}
}
getFileParser(format: string, program: ts.Program, host: NgccReflectionHost): FileParser {
switch (format) {
case 'esm2015':
case 'fesm2015':
return new Esm2015FileParser(program, host);
case 'fesm5':
return new Esm5FileParser(program, host);
default:
throw new Error(`File parser for "${format}" not yet implemented.`);
}
}
getRenderer(format: string, program: ts.Program, host: NgccReflectionHost): Renderer {
switch (format) {
case 'esm2015':
case 'fesm2015':
return new Esm2015Renderer(host);
case 'fesm5':
return new Esm5Renderer(host);
default:
throw new Error(`Renderer for "${format}" not yet implemented.`);
}
}
findNodeModulesPath(src: string): string {
while (src && !/node_modules$/.test(src)) {
src = dirname(src);
}
return src;
}
writeFile(file: FileInfo): void {
mkdir('-p', dirname(file.path));
writeFileSync(file.path, file.contents, 'utf8');
}
}