186 lines
7.2 KiB
TypeScript
186 lines
7.2 KiB
TypeScript
/**
|
|
* @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 fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as tsickle from 'tsickle';
|
|
import * as ts from 'typescript';
|
|
|
|
import {check, tsc} from './tsc';
|
|
|
|
import NgOptions from './options';
|
|
import {MetadataWriterHost, SyntheticIndexHost} from './compiler_host';
|
|
import {CliOptions} from './cli_options';
|
|
import {VinylFile, isVinylFile} from './vinyl_file';
|
|
import {MetadataBundler, CompilerHostAdapter} from './bundler';
|
|
import {privateEntriesToIndex} from './index_writer';
|
|
export {UserError} from './tsc';
|
|
|
|
const DTS = /\.d\.ts$/;
|
|
const JS_EXT = /(\.js|)$/;
|
|
|
|
export type CodegenExtension =
|
|
(ngOptions: NgOptions, cliOptions: CliOptions, program: ts.Program, host: ts.CompilerHost) =>
|
|
Promise<void>;
|
|
|
|
export function main(
|
|
project: string | VinylFile, cliOptions: CliOptions, codegen?: CodegenExtension,
|
|
options?: ts.CompilerOptions): Promise<any> {
|
|
try {
|
|
let projectDir = project;
|
|
// project is vinyl like file object
|
|
if (isVinylFile(project)) {
|
|
projectDir = path.dirname(project.path);
|
|
}
|
|
// project is path to project file
|
|
else if (fs.lstatSync(project).isFile()) {
|
|
projectDir = path.dirname(project);
|
|
}
|
|
|
|
// file names in tsconfig are resolved relative to this absolute path
|
|
const basePath = path.resolve(process.cwd(), cliOptions.basePath || projectDir);
|
|
|
|
// read the configuration options from wherever you store them
|
|
const {parsed, ngOptions} = tsc.readConfiguration(project, basePath, options);
|
|
ngOptions.basePath = basePath;
|
|
const createProgram = (host: ts.CompilerHost, oldProgram?: ts.Program) =>
|
|
ts.createProgram(parsed.fileNames, parsed.options, host, oldProgram);
|
|
const diagnostics = (parsed.options as any).diagnostics;
|
|
if (diagnostics) (ts as any).performance.enable();
|
|
|
|
let host = ts.createCompilerHost(parsed.options, true);
|
|
|
|
// If the comilation is a flat module index then produce the flat module index
|
|
// metadata and the synthetic flat module index.
|
|
if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) {
|
|
const files = parsed.fileNames.filter(f => !DTS.test(f));
|
|
if (files.length != 1) {
|
|
check([{
|
|
file: null,
|
|
start: null,
|
|
length: null,
|
|
messageText:
|
|
'Angular compiler option "flatModuleIndex" requires one and only one .ts file in the "files" field.',
|
|
category: ts.DiagnosticCategory.Error,
|
|
code: 0
|
|
}]);
|
|
}
|
|
const file = files[0];
|
|
const indexModule = file.replace(/\.ts$/, '');
|
|
const bundler =
|
|
new MetadataBundler(indexModule, ngOptions.flatModuleId, new CompilerHostAdapter(host));
|
|
if (diagnostics) console.time('NG flat module index');
|
|
const metadataBundle = bundler.getMetadataBundle();
|
|
if (diagnostics) console.timeEnd('NG flat module index');
|
|
const metadata = JSON.stringify(metadataBundle.metadata);
|
|
const name =
|
|
path.join(path.dirname(indexModule), ngOptions.flatModuleOutFile.replace(JS_EXT, '.ts'));
|
|
const libraryIndex = `./${path.basename(indexModule)}`;
|
|
const content = privateEntriesToIndex(libraryIndex, metadataBundle.privates);
|
|
host = new SyntheticIndexHost(host, {name, content, metadata});
|
|
parsed.fileNames.push(name);
|
|
}
|
|
|
|
const tsickleCompilerHostOptions: tsickle.Options = {
|
|
googmodule: false,
|
|
untyped: true,
|
|
convertIndexImportShorthand:
|
|
ngOptions.target === ts.ScriptTarget.ES2015, // This covers ES6 too
|
|
};
|
|
|
|
const tsickleHost: tsickle.TsickleHost = {
|
|
shouldSkipTsickleProcessing: (fileName) => /\.d\.ts$/.test(fileName),
|
|
pathToModuleName: (context, importPath) => '',
|
|
shouldIgnoreWarningsForPath: (filePath) => false,
|
|
fileNameToModuleId: (fileName) => fileName,
|
|
};
|
|
|
|
const tsickleCompilerHost =
|
|
new tsickle.TsickleCompilerHost(host, ngOptions, tsickleCompilerHostOptions, tsickleHost);
|
|
|
|
const program = createProgram(tsickleCompilerHost);
|
|
|
|
const errors = program.getOptionsDiagnostics();
|
|
check(errors);
|
|
|
|
if (ngOptions.skipTemplateCodegen || !codegen) {
|
|
codegen = () => Promise.resolve(null);
|
|
}
|
|
|
|
if (diagnostics) console.time('NG codegen');
|
|
return codegen(ngOptions, cliOptions, program, host).then(() => {
|
|
if (diagnostics) console.timeEnd('NG codegen');
|
|
let definitionsHost: ts.CompilerHost = tsickleCompilerHost;
|
|
if (!ngOptions.skipMetadataEmit) {
|
|
// if tsickle is not not used for emitting, but we do use the MetadataWriterHost,
|
|
// it also needs to emit the js files.
|
|
const emitJsFiles =
|
|
ngOptions.annotationsAs === 'decorators' && !ngOptions.annotateForClosureCompiler;
|
|
definitionsHost = new MetadataWriterHost(tsickleCompilerHost, ngOptions, emitJsFiles);
|
|
}
|
|
// Create a new program since codegen files were created after making the old program
|
|
let programWithCodegen = createProgram(definitionsHost, program);
|
|
tsc.typeCheck(host, programWithCodegen);
|
|
|
|
let programForJsEmit = programWithCodegen;
|
|
|
|
if (ngOptions.annotationsAs !== 'decorators') {
|
|
if (diagnostics) console.time('NG downlevel');
|
|
tsickleCompilerHost.reconfigureForRun(programForJsEmit, tsickle.Pass.DECORATOR_DOWNLEVEL);
|
|
// A program can be re-used only once; save the programWithCodegen to be reused by
|
|
// metadataWriter
|
|
programForJsEmit = createProgram(tsickleCompilerHost);
|
|
check(tsickleCompilerHost.diagnostics);
|
|
if (diagnostics) console.timeEnd('NG downlevel');
|
|
}
|
|
|
|
if (ngOptions.annotateForClosureCompiler) {
|
|
if (diagnostics) console.time('NG JSDoc');
|
|
tsickleCompilerHost.reconfigureForRun(programForJsEmit, tsickle.Pass.CLOSURIZE);
|
|
programForJsEmit = createProgram(tsickleCompilerHost);
|
|
check(tsickleCompilerHost.diagnostics);
|
|
if (diagnostics) console.timeEnd('NG JSDoc');
|
|
}
|
|
|
|
// Emit *.js and *.js.map
|
|
tsc.emit(programForJsEmit);
|
|
|
|
// Emit *.d.ts and maybe *.metadata.json
|
|
// Not in the same emit pass with above, because tsickle erases
|
|
// decorators which we want to read or document.
|
|
// Do this emit second since TypeScript will create missing directories for us
|
|
// in the standard emit.
|
|
tsc.emit(programWithCodegen);
|
|
|
|
if (diagnostics) {
|
|
(ts as any).performance.forEachMeasure(
|
|
(name: string, duration: number) => { console.error(`TS ${name}: ${duration}ms`); });
|
|
}
|
|
});
|
|
} catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
}
|
|
|
|
// CLI entry point
|
|
if (require.main === module) {
|
|
const args = process.argv.slice(2);
|
|
let {options, fileNames, errors} = (ts as any).parseCommandLine(args);
|
|
check(errors);
|
|
const project = options.project || '.';
|
|
// TODO(alexeagle): command line should be TSC-compatible, remove "CliOptions" here
|
|
const cliOptions = new CliOptions(require('minimist')(args));
|
|
main(project, cliOptions, null, options)
|
|
.then((exitCode: any) => process.exit(exitCode))
|
|
.catch((e: any) => {
|
|
console.error(e.stack);
|
|
console.error('Compilation failed');
|
|
process.exit(1);
|
|
});
|
|
}
|