feat(ivy): implement initial ngcc package transformer (#24897)
PR Close #24897
This commit is contained in:
parent
5b32aa4486
commit
43d0e3dd72
@ -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;
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user