fix(compiler): make sourcemaps work in AOT mode

Inlcuded fixes:
- include preamble in generated source map
- always add a mapping for line/col 0 so that the
  generated sourcemap is not sparse
- use a uniue sourceUrl for inline templates even
  in the AOT case
This commit is contained in:
Tobias Bosch
2017-03-15 15:50:30 -07:00
committed by Chuck Jazdzewski
parent c0e05e6f03
commit 492153a986
24 changed files with 299 additions and 123 deletions

View File

@ -33,7 +33,8 @@ export class AotCompiler {
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string,
private _translationFormat: string, private _symbolResolver: StaticSymbolResolver) {}
private _translationFormat: string, private _genFilePreamble: string,
private _symbolResolver: StaticSymbolResolver) {}
clearCache() { this._metadataResolver.clearCache(); }
@ -215,7 +216,8 @@ export class AotCompiler {
exportedVars: string[]): GeneratedFile {
return new GeneratedFile(
srcFileUrl, genFileUrl,
this._outputEmitter.emitStatements(genFileUrl, statements, exportedVars));
this._outputEmitter.emitStatements(
srcFileUrl, genFileUrl, statements, exportedVars, this._genFilePreamble));
}
}

View File

@ -79,6 +79,6 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
const compiler = new AotCompiler(
config, compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver), viewCompiler,
new NgModuleCompiler(), new TypeScriptEmitter(importResolver), summaryResolver,
options.locale, options.i18nFormat, symbolResolver);
options.locale, options.i18nFormat, options.genFilePreamble, symbolResolver);
return {compiler, reflector: staticReflector};
}

View File

@ -11,4 +11,6 @@ export interface AotCompilerOptions {
i18nFormat?: string;
translations?: string;
enableLegacyTemplate?: boolean;
/** preamble for all generated source files */
genFilePreamble?: string;
}

View File

@ -772,7 +772,9 @@ export function templateSourceUrl(
templateMeta: {isInline: boolean, templateUrl: string}) {
if (templateMeta.isInline) {
if (compMeta.type.reference instanceof StaticSymbol) {
return compMeta.type.reference.filePath;
// Note: a .ts file might contain multiple components with inline templates,
// so we need to give them unique urls, as these will be used for sourcemaps.
return `${compMeta.type.reference.filePath}#${compMeta.type.reference.name}.html`;
} else {
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`;
}

View File

@ -18,7 +18,9 @@ export const CATCH_ERROR_VAR = o.variable('error');
export const CATCH_STACK_VAR = o.variable('stack');
export abstract class OutputEmitter {
abstract emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string;
abstract emitStatements(
srcFilePath: string, genFilePath: string, stmts: o.Statement[], exportedVars: string[],
preamble?: string): string;
}
class _EmittedLine {
@ -89,13 +91,24 @@ export class EmitterVisitorContext {
.join('\n');
}
toSourceMapGenerator(file: string|null = null, startsAtLine: number = 0): SourceMapGenerator {
const map = new SourceMapGenerator(file);
toSourceMapGenerator(sourceFilePath: string, genFilePath: string, startsAtLine: number = 0):
SourceMapGenerator {
const map = new SourceMapGenerator(genFilePath);
let firstOffsetMapped = false;
const mapFirstOffsetIfNeeded = () => {
if (!firstOffsetMapped) {
map.addSource(sourceFilePath).addMapping(0, sourceFilePath, 0, 0);
firstOffsetMapped = true;
}
};
for (let i = 0; i < startsAtLine; i++) {
map.addLine();
mapFirstOffsetIfNeeded();
}
this.sourceLines.forEach(line => {
this.sourceLines.forEach((line, lineIdx) => {
map.addLine();
const spans = line.srcSpans;
@ -107,13 +120,17 @@ export class EmitterVisitorContext {
col0 += parts[spanIdx].length;
spanIdx++;
}
if (spanIdx < spans.length && lineIdx === 0 && col0 === 0) {
firstOffsetMapped = true;
} else {
mapFirstOffsetIfNeeded();
}
while (spanIdx < spans.length) {
const span = spans[spanIdx];
const source = span.start.file;
const sourceLine = span.start.line;
const sourceCol = span.start.col;
map.addSource(source.url, source.content)
.addMapping(col0, source.url, sourceLine, sourceCol);

View File

@ -18,29 +18,29 @@ import {ImportResolver} from './path_util';
export class JavaScriptEmitter implements OutputEmitter {
constructor(private _importResolver: ImportResolver) {}
emitStatements(genFilePath: string, stmts: o.Statement[], exportedVars: string[]): string {
emitStatements(
srcFilePath: string, genFilePath: string, stmts: o.Statement[], exportedVars: string[],
preamble: string = ''): string {
const converter = new JsEmitterVisitor(genFilePath, this._importResolver);
const ctx = EmitterVisitorContext.createRoot(exportedVars);
converter.visitAllStatements(stmts, ctx);
const srcParts: string[] = [];
const preambleLines = preamble ? preamble.split('\n') : [];
converter.importsWithPrefixes.forEach((prefix, importedFilePath) => {
// Note: can't write the real word for import as it screws up system.js auto detection...
srcParts.push(
preambleLines.push(
`var ${prefix} = req` +
`uire('${this._importResolver.fileNameToModuleName(importedFilePath, genFilePath)}');`);
});
srcParts.push(ctx.toSource());
const prefixLines = converter.importsWithPrefixes.size;
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
const sm =
ctx.toSourceMapGenerator(srcFilePath, genFilePath, preambleLines.length).toJsComment();
const lines = [...preambleLines, ctx.toSource(), sm];
if (sm) {
srcParts.push(sm);
// always add a newline at the end, as some tools have bugs without it.
lines.push('');
}
// always add a newline at the end, as some tools have bugs without it.
srcParts.push('');
return srcParts.join('\n');
return lines.join('\n');
}
}

View File

@ -30,7 +30,7 @@ function evalExpression(
// We don't want to hard code this fact, so we auto detect it via an empty function first.
const emptyFn = new Function(...fnArgNames.concat('return null;')).toString();
const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1;
fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`;
fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, sourceUrl, headerLines).toJsComment()}`;
}
return new Function(...fnArgNames.concat(fnBody))(...fnArgValues);
}

View File

@ -44,40 +44,38 @@ export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.T
export class TypeScriptEmitter implements OutputEmitter {
constructor(private _importResolver: ImportResolver) {}
emitStatements(genFilePath: string, stmts: o.Statement[], exportedVars: string[]): string {
emitStatements(
srcFilePath: string, genFilePath: string, stmts: o.Statement[], exportedVars: string[],
preamble: string = ''): string {
const converter = new _TsEmitterVisitor(genFilePath, this._importResolver);
const ctx = EmitterVisitorContext.createRoot(exportedVars);
converter.visitAllStatements(stmts, ctx);
const srcParts: string[] = [];
const preambleLines = preamble ? preamble.split('\n') : [];
converter.reexports.forEach((reexports, exportedFilePath) => {
const reexportsCode =
reexports.map(reexport => `${reexport.name} as ${reexport.as}`).join(',');
srcParts.push(
preambleLines.push(
`export {${reexportsCode}} from '${this._importResolver.fileNameToModuleName(exportedFilePath, genFilePath)}';`);
});
converter.importsWithPrefixes.forEach((prefix, importedFilePath) => {
// Note: can't write the real word for import as it screws up system.js auto detection...
srcParts.push(
preambleLines.push(
`imp` +
`ort * as ${prefix} from '${this._importResolver.fileNameToModuleName(importedFilePath, genFilePath)}';`);
});
srcParts.push(ctx.toSource());
const prefixLines = converter.reexports.size + converter.importsWithPrefixes.size;
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
const sm =
ctx.toSourceMapGenerator(srcFilePath, genFilePath, preambleLines.length).toJsComment();
const lines = [...preambleLines, ctx.toSource(), sm];
if (sm) {
srcParts.push(sm);
// always add a newline at the end, as some tools have bugs without it.
lines.push('');
}
// always add a newline at the end, as some tools have bugs without it.
srcParts.push('');
return srcParts.join('\n');
return lines.join('\n');
}
}