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:

committed by
Chuck Jazdzewski

parent
966eb2fbd0
commit
573b8611bc
@ -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);
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user