diff --git a/packages/@ngtools/webpack/src/compiler_host.ts b/packages/@ngtools/webpack/src/compiler_host.ts index b1ff333a8944..1cccd61887f8 100644 --- a/packages/@ngtools/webpack/src/compiler_host.ts +++ b/packages/@ngtools/webpack/src/compiler_host.ts @@ -68,6 +68,10 @@ export class VirtualFileStats extends VirtualStats { set content(v: string) { this._content = v; this._mtime = new Date(); + this._sourceFile = null; + } + setSourceFile(sourceFile: ts.SourceFile) { + this._sourceFile = sourceFile; } getSourceFile(languageVersion: ts.ScriptTarget, setParentNodes: boolean) { if (!this._sourceFile) { @@ -96,6 +100,8 @@ export class WebpackCompilerHost implements ts.CompilerHost { private _basePath: string; private _setParentNodes: boolean; + private _cache: boolean = false; + constructor(private _options: ts.CompilerOptions, basePath: string) { this._setParentNodes = true; this._delegate = ts.createCompilerHost(this._options, this._setParentNodes); @@ -129,6 +135,10 @@ export class WebpackCompilerHost implements ts.CompilerHost { this._changed = true; } + enableCaching() { + this._cache = true; + } + populateWebpackResolver(resolver: any) { const fs = resolver.fileSystem; if (!this._changed) { @@ -156,21 +166,33 @@ export class WebpackCompilerHost implements ts.CompilerHost { this._changed = false; } + invalidate(fileName: string): void { + this._files[fileName] = null; + } + fileExists(fileName: string): boolean { fileName = this._resolve(fileName); - return fileName in this._files || this._delegate.fileExists(fileName); + return this._files[fileName] != null || this._delegate.fileExists(fileName); } readFile(fileName: string): string { fileName = this._resolve(fileName); - return (fileName in this._files) - ? this._files[fileName].content - : this._delegate.readFile(fileName); + if (this._files[fileName] == null) { + const result = this._delegate.readFile(fileName); + if (result !== undefined && this._cache) { + this._setFileContent(fileName, result); + return result; + } else { + return result; + } + } + return this._files[fileName].content; } directoryExists(directoryName: string): boolean { directoryName = this._resolve(directoryName); - return (directoryName in this._directories) || this._delegate.directoryExists(directoryName); + return (this._directories[directoryName] != null) + || this._delegate.directoryExists(directoryName); } getFiles(path: string): string[] { @@ -198,8 +220,11 @@ export class WebpackCompilerHost implements ts.CompilerHost { getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: OnErrorFn) { fileName = this._resolve(fileName); - if (!(fileName in this._files)) { - return this._delegate.getSourceFile(fileName, languageVersion, onError); + if (this._files[fileName] == null) { + const content = this.readFile(fileName); + if (!this._cache) { + return ts.createSourceFile(fileName, content, languageVersion, this._setParentNodes); + } } return this._files[fileName].getSourceFile(languageVersion, this._setParentNodes); diff --git a/packages/@ngtools/webpack/src/plugin.ts b/packages/@ngtools/webpack/src/plugin.ts index bb309206cad7..cde52330d928 100644 --- a/packages/@ngtools/webpack/src/plugin.ts +++ b/packages/@ngtools/webpack/src/plugin.ts @@ -165,6 +165,10 @@ export class AotPlugin implements Tapable { this._program = ts.createProgram( this._rootFilePath, this._compilerOptions, this._compilerHost); + // We enable caching of the filesystem in compilerHost _after_ the program has been created, + // because we don't want SourceFile instances to be cached past this point. + this._compilerHost.enableCaching(); + if (options.entryModule) { this._entryModule = options.entryModule; } else if ((tsConfig.raw['angularCompilerOptions'] as any) @@ -194,6 +198,10 @@ export class AotPlugin implements Tapable { apply(compiler: any) { this._compiler = compiler; + compiler.plugin('invalid', (fileName: string, timestamp: number) => { + this._compilerHost.invalidate(fileName); + }); + // Add lazy modules to the context module for @angular/core/src/linker compiler.plugin('context-module-factory', (cmf: any) => { cmf.plugin('after-resolve', (result: any, callback: (err?: any, request?: any) => void) => { @@ -251,6 +259,7 @@ export class AotPlugin implements Tapable { if (this._compilation._ngToolsWebpackPluginInstance) { return cb(new Error('An @ngtools/webpack plugin already exist for this compilation.')); } + this._compilation._ngToolsWebpackPluginInstance = this; this._resourceLoader = new WebpackResourceLoader(compilation); @@ -284,18 +293,20 @@ export class AotPlugin implements Tapable { this._rootFilePath, this._compilerOptions, this._compilerHost, this._program); }) .then(() => { - const diagnostics = this._program.getGlobalDiagnostics(); - if (diagnostics.length > 0) { - const message = diagnostics - .map(diagnostic => { - const {line, character} = diagnostic.file.getLineAndCharacterOfPosition( - diagnostic.start); - const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); - return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`; - }) - .join('\n'); - - throw new Error(message); + if (this._typeCheck) { + const diagnostics = this._program.getGlobalDiagnostics(); + if (diagnostics.length > 0) { + const message = diagnostics + .map(diagnostic => { + const {line, character} = diagnostic.file.getLineAndCharacterOfPosition( + diagnostic.start); + const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); + return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`; + }) + .join('\n'); + + throw new Error(message); + } } }) .then(() => { diff --git a/packages/@ngtools/webpack/src/refactor.ts b/packages/@ngtools/webpack/src/refactor.ts index 105920bb65a2..57a2e6937277 100644 --- a/packages/@ngtools/webpack/src/refactor.ts +++ b/packages/@ngtools/webpack/src/refactor.ts @@ -60,12 +60,15 @@ export class TypeScriptFileRefactor { if (!this._program) { return []; } - let diagnostics: ts.Diagnostic[] = this._program.getSyntacticDiagnostics(this._sourceFile) - .concat(this._program.getSemanticDiagnostics(this._sourceFile)); + let diagnostics: ts.Diagnostic[] = []; // only concat the declaration diagnostics if the tsconfig config sets it to true. if (this._program.getCompilerOptions().declaration == true) { diagnostics = diagnostics.concat(this._program.getDeclarationDiagnostics(this._sourceFile)); } + diagnostics = diagnostics.concat( + this._program.getSyntacticDiagnostics(this._sourceFile), + this._program.getSemanticDiagnostics(this._sourceFile)); + return diagnostics; }