fix(@ngtools/webpack): diagnose generated files and resolve sourcemaps

Generated files were not diagnosed in AOT which led to errors not being shown properly. Also,
in order to show the proper error source we now resolve the sourcemap of any errors we find.

Fixes #5264
Fixed #4538
This commit is contained in:
Hans Larsen 2017-03-23 16:36:56 -07:00 committed by Hans
parent 5c1593319e
commit 5acf10b7aa

View File

@ -2,6 +2,7 @@
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import * as SourceMap from 'source-map';
const {__NGTOOLS_PRIVATE_API_2} = require('@angular/compiler-cli');
const ContextElementDependency = require('webpack/lib/dependencies/ContextElementDependency');
@ -36,6 +37,9 @@ export interface AotPluginOptions {
}
const inlineSourceMapRe = /\/\/# sourceMappingURL=data:application\/json;base64,([\s\S]+)$/;
export class AotPlugin implements Tapable {
private _options: AotPluginOptions;
@ -342,6 +346,30 @@ export class AotPlugin implements Tapable {
});
}
private _translateSourceMap(sourceText: string, fileName: string,
{line, character}: {line: number, character: number}) {
const match = sourceText.match(inlineSourceMapRe);
if (!match) {
return {line, character, fileName};
}
// On any error, return line and character.
try {
const sourceMapJson = JSON.parse(Buffer.from(match[1], 'base64').toString());
const consumer = new SourceMap.SourceMapConsumer(sourceMapJson);
const original = consumer.originalPositionFor({ line, column: character });
return {
line: original.line,
character: original.column,
fileName: original.source || fileName
};
} catch (e) {
return {line, character, fileName};
}
}
diagnose(fileName: string) {
if (this._diagnoseFiles[fileName]) {
return;
@ -349,9 +377,9 @@ export class AotPlugin implements Tapable {
this._diagnoseFiles[fileName] = true;
const sourceFile = this._program.getSourceFile(fileName);
if (!sourceFile) {
return;
}
if (!sourceFile) {
return;
}
const diagnostics: ts.Diagnostic[] = []
.concat(
@ -364,9 +392,14 @@ export class AotPlugin implements Tapable {
if (diagnostics.length > 0) {
const message = diagnostics
.map(diagnostic => {
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
const sourceText = diagnostic.file.getFullText();
let {line, character, fileName} = this._translateSourceMap(sourceText,
diagnostic.file.fileName, position);
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`;
return `${fileName} (${line + 1},${character + 1}): ${message}`;
})
.join('\n');
this._compilation.errors.push(message);
@ -404,6 +437,15 @@ export class AotPlugin implements Tapable {
});
})
.then(() => {
// Get the ngfactory that were created by the previous step, and add them to the root
// file path (if those files exists).
const newRootFilePath = this._compilerHost.getChangedFilePaths()
.filter(x => x.match(/\.ngfactory\.ts$/));
// Remove files that don't exist anymore, and add new files.
this._rootFilePath = this._rootFilePath
.filter(x => this._compilerHost.fileExists(x))
.concat(newRootFilePath);
// Create a new Program, based on the old one. This will trigger a resolution of all
// transitive modules, which include files that might just have been generated.
// This needs to happen after the code generator has been created for generated files