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:

committed by
Chuck Jazdzewski

parent
c0e05e6f03
commit
492153a986
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
}
|
||||
|
@ -11,4 +11,6 @@ export interface AotCompilerOptions {
|
||||
i18nFormat?: string;
|
||||
translations?: string;
|
||||
enableLegacyTemplate?: boolean;
|
||||
/** preamble for all generated source files */
|
||||
genFilePreamble?: string;
|
||||
}
|
||||
|
@ -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`;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user