fix(compiler): compile .ngfactory.ts files even if nobody references them. (#16899)

This is especially important for library authors, as they will
not reference the .ngfactory.ts files.

Fixes #16741
This commit is contained in:
Tobias Bosch
2017-05-25 10:00:26 -07:00
committed by Chuck Jazdzewski
parent 966eb2fbd0
commit 573b8611bc
10 changed files with 165 additions and 58 deletions

View File

@ -11,22 +11,27 @@ 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 {CompilerHostAdapter, MetadataBundler} from './bundler';
import {CliOptions} from './cli_options';
import {VinylFile, isVinylFile} from './vinyl_file';
import {MetadataBundler, CompilerHostAdapter} from './bundler';
import {MetadataWriterHost, SyntheticIndexHost} from './compiler_host';
import {privateEntriesToIndex} from './index_writer';
import NgOptions from './options';
import {check, tsc} from './tsc';
import {isVinylFile, VinylFile} from './vinyl_file';
export {UserError} from './tsc';
const DTS = /\.d\.ts$/;
const JS_EXT = /(\.js|)$/;
const TS_EXT = /\.ts$/;
export type CodegenExtension =
(ngOptions: NgOptions, cliOptions: CliOptions, program: ts.Program, host: ts.CompilerHost) =>
Promise<void>;
export interface CodegenExtension {
/**
* Returns the generated file names.
*/
(ngOptions: NgOptions, cliOptions: CliOptions, program: ts.Program,
host: ts.CompilerHost): Promise<string[]>;
}
export function main(
project: string | VinylFile, cliOptions: CliOptions, codegen?: CodegenExtension,
@ -46,10 +51,18 @@ export function main(
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);
let {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);
let rootFileNames: string[] = parsed.fileNames.slice(0);
const createProgram = (host: ts.CompilerHost, oldProgram?: ts.Program) => {
return ts.createProgram(rootFileNames.slice(0), parsed.options, host, oldProgram);
};
const addGeneratedFileName = (genFileName: string) => {
if (genFileName.startsWith(basePath) && TS_EXT.exec(genFileName)) {
rootFileNames.push(genFileName);
}
};
const diagnostics = (parsed.options as any).diagnostics;
if (diagnostics) (ts as any).performance.enable();
@ -83,7 +96,7 @@ export function main(
const libraryIndex = `./${path.basename(indexModule)}`;
const content = privateEntriesToIndex(libraryIndex, metadataBundle.privates);
host = new SyntheticIndexHost(host, {name, content, metadata});
parsed.fileNames.push(name);
addGeneratedFileName(name);
}
const tsickleCompilerHostOptions: tsickle.Options = {
@ -109,12 +122,14 @@ export function main(
check(errors);
if (ngOptions.skipTemplateCodegen || !codegen) {
codegen = () => Promise.resolve(null);
codegen = () => Promise.resolve([]);
}
if (diagnostics) console.time('NG codegen');
return codegen(ngOptions, cliOptions, program, host).then(() => {
return codegen(ngOptions, cliOptions, program, host).then((genFiles) => {
if (diagnostics) console.timeEnd('NG codegen');
// Add the generated files to the configuration so they will become part of the program.
genFiles.forEach(genFileName => addGeneratedFileName(genFileName));
let definitionsHost: ts.CompilerHost = tsickleCompilerHost;
if (!ngOptions.skipMetadataEmit) {
// if tsickle is not not used for emitting, but we do use the MetadataWriterHost,
@ -123,6 +138,7 @@ export function main(
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);

View File

@ -103,16 +103,19 @@ export function validateAngularCompilerOptions(options: AngularCompilerOptions):
}
export class Tsc implements CompilerInterface {
public ngOptions: AngularCompilerOptions;
public parsed: ts.ParsedCommandLine;
private basePath: string;
private parseConfigHost: ts.ParseConfigHost;
constructor(private readFile = ts.sys.readFile, private readDirectory = ts.sys.readDirectory) {}
constructor(private readFile = ts.sys.readFile, private readDirectory = ts.sys.readDirectory) {
this.parseConfigHost = {
useCaseSensitiveFileNames: true,
fileExists: existsSync,
readDirectory: this.readDirectory,
readFile: ts.sys.readFile
};
}
readConfiguration(
project: string|VinylFile, basePath: string, existingOptions?: ts.CompilerOptions) {
this.basePath = basePath;
// Allow a directory containing tsconfig.json as the project value
// Note, TS@next returns an empty array, while earlier versions throw
try {
@ -135,31 +138,21 @@ export class Tsc implements CompilerInterface {
})();
check([error]);
// Do not inline `host` into `parseJsonConfigFileContent` until after
// g3 is updated to the latest TypeScript.
// The issue is that old typescript only has `readDirectory` while
// the newer TypeScript has additional `useCaseSensitiveFileNames` and
// `fileExists`. Inlining will trigger an error of extra parameters.
const host = {
useCaseSensitiveFileNames: true,
fileExists: existsSync,
readDirectory: this.readDirectory,
readFile: ts.sys.readFile
};
this.parsed = ts.parseJsonConfigFileContent(config, host, basePath, existingOptions);
const parsed =
ts.parseJsonConfigFileContent(config, this.parseConfigHost, basePath, existingOptions);
check(this.parsed.errors);
check(parsed.errors);
// Default codegen goes to the current directory
// Parsed options are already converted to absolute paths
this.ngOptions = config.angularCompilerOptions || {};
this.ngOptions.genDir = path.join(basePath, this.ngOptions.genDir || '.');
for (const key of Object.keys(this.parsed.options)) {
this.ngOptions[key] = this.parsed.options[key];
const ngOptions = config.angularCompilerOptions || {};
ngOptions.genDir = path.join(basePath, ngOptions.genDir || '.');
for (const key of Object.keys(parsed.options)) {
ngOptions[key] = parsed.options[key];
}
check(validateAngularCompilerOptions(this.ngOptions));
check(validateAngularCompilerOptions(ngOptions));
return {parsed: this.parsed, ngOptions: this.ngOptions};
return {parsed, ngOptions};
}
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void {