Skip to content

Commit

Permalink
feat: introduce source maps for templates
Browse files Browse the repository at this point in the history
The main use case for the generated source maps is to give
errors a meaningful context in terms of the original source
that the user wrote.

Related changes that are included in this commit:

* renamed virtual folders used for jit:
  * ng://<module type>/module.ngfactory.js
  * ng://<module type>/<comp type>.ngfactory.js
  * ng://<module type>/<comp type>.html (for inline templates)
* error logging:
  * all errors that happen in templates are logged
    from the place of the nearest element.
  * instead of logging error messages and stacks separately,
    we log the actual error. This is needed so that browsers apply
    source maps to the stack correctly.
  * error type and error is logged as one log entry.

Note that long-stack-trace zone has a bug that 
disables source maps for stack traces,
see angular/zone.js#661.

BREAKING CHANGE:

- DebugNode.source no more returns the source location of a node.  

Closes 14013
  • Loading branch information
tbosch committed Mar 13, 2017
1 parent 5f9fb91 commit c890dbf
Show file tree
Hide file tree
Showing 48 changed files with 1,196 additions and 515 deletions.
4 changes: 2 additions & 2 deletions packages/compiler/src/aot/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName, templateSourceUrl} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
import {CompileMetadataResolver} from '../metadata_resolver';
Expand Down Expand Up @@ -189,7 +189,7 @@ export class AotCompiler {

const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
identifierName(compMeta.type));
templateSourceUrl(ngModule.type, compMeta, compMeta.template));
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const viewResult =
this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, usedPipes);
Expand Down
47 changes: 45 additions & 2 deletions packages/compiler/src/compile_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {ChangeDetectionStrategy, ComponentFactory, RendererType2, SchemaMetadata, Type, ViewEncapsulation, ɵLifecycleHooks, ɵreflector, ɵstringify as stringify} from '@angular/core';

import {StaticSymbol} from './aot/static_symbol';
import {CssSelector} from './selector';
import {splitAtColon} from './util';
Expand Down Expand Up @@ -243,6 +244,7 @@ export class CompileTemplateMetadata {
encapsulation: ViewEncapsulation;
template: string;
templateUrl: string;
isInline: boolean;
styles: string[];
styleUrls: string[];
externalStylesheets: CompileStylesheetMetadata[];
Expand All @@ -251,7 +253,7 @@ export class CompileTemplateMetadata {
interpolation: [string, string];
constructor(
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
ngContentSelectors, interpolation}: {
ngContentSelectors, interpolation, isInline}: {
encapsulation?: ViewEncapsulation,
template?: string,
templateUrl?: string,
Expand All @@ -261,6 +263,7 @@ export class CompileTemplateMetadata {
ngContentSelectors?: string[],
animations?: any[],
interpolation?: [string, string],
isInline?: boolean
} = {}) {
this.encapsulation = encapsulation;
this.template = template;
Expand All @@ -274,6 +277,7 @@ export class CompileTemplateMetadata {
throw new Error(`'interpolation' should have a start and an end symbol.`);
}
this.interpolation = interpolation;
this.isInline = isInline;
}

toSummary(): CompileTemplateSummary {
Expand Down Expand Up @@ -510,7 +514,8 @@ export function createHostComponentMeta(
styles: [],
styleUrls: [],
ngContentSelectors: [],
animations: []
animations: [],
isInline: true,
}),
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
Expand Down Expand Up @@ -738,3 +743,41 @@ export function flatten<T>(list: Array<T|T[]>): T[] {
return (<T[]>flat).concat(flatItem);
}, []);
}

/**
* Note: Using `location.origin` as prefix helps displaying them as a hierarchy in chrome.
* It also helps long-stack-trace zone when rewriting stack traces to not break
* source maps (as now all scripts have the same origin).
*/
function ngJitFolder() {
return 'ng://';
}

export function templateSourceUrl(
ngModuleType: CompileIdentifierMetadata, compMeta: {type: CompileIdentifierMetadata},
templateMeta: {isInline: boolean, templateUrl: string}) {
if (templateMeta.isInline) {
if (compMeta.type.reference instanceof StaticSymbol) {
return compMeta.type.reference.filePath;
} else {
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`;
}
} else {
return templateMeta.templateUrl;
}
}

export function sharedStylesheetJitUrl(meta: CompileStylesheetMetadata, id: number) {
const pathParts = meta.moduleUrl.split(/\/\\/g);
const baseName = pathParts[pathParts.length - 1];
return `${ngJitFolder()}/css/${id}${baseName}.ngstyle.js`;
}

export function ngModuleJitUrl(moduleMeta: CompileNgModuleMetadata): string {
return `${ngJitFolder()}/${identifierName(moduleMeta.type)}/module.ngfactory.js`;
}

export function templateJitUrl(
ngModuleType: CompileIdentifierMetadata, compMeta: CompileDirectiveMetadata): string {
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.ngfactory.js`;
}
1 change: 1 addition & 0 deletions packages/compiler/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export {CompilerConfig} from './config';
export * from './compile_metadata';
export * from './aot/compiler_factory';
export * from './aot/compiler';
export * from './aot/generated_file';
export * from './aot/compiler_options';
export * from './aot/compiler_host';
export * from './aot/static_reflector';
Expand Down
29 changes: 18 additions & 11 deletions packages/compiler/src/directive_normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import {ViewEncapsulation, ɵstringify as stringify} from '@angular/core';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, templateSourceUrl} from './compile_metadata';
import {CompilerConfig} from './config';
import {CompilerInjectable} from './injectable';
import * as html from './ml_parser/ast';
Expand All @@ -20,6 +20,7 @@ import {UrlResolver} from './url_resolver';
import {SyncAsyncResult, syntaxError} from './util';

export interface PrenormalizedTemplateMetadata {
ngModuleType: any;
componentType: any;
moduleUrl: string;
template?: string;
Expand Down Expand Up @@ -104,28 +105,33 @@ export class DirectiveNormalizer {
}

normalizeLoadedTemplate(
prenomData: PrenormalizedTemplateMetadata, template: string,
prenormData: PrenormalizedTemplateMetadata, template: string,
templateAbsUrl: string): CompileTemplateMetadata {
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
const isInline = !!prenormData.template;
const interpolationConfig = InterpolationConfig.fromArray(prenormData.interpolation);
const rootNodesAndErrors = this._htmlParser.parse(
template, stringify(prenomData.componentType), true, interpolationConfig);
template,
templateSourceUrl(
{reference: prenormData.ngModuleType}, {type: {reference: prenormData.componentType}},
{isInline, templateUrl: templateAbsUrl}),
true, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n');
throw syntaxError(`Template parse errors:\n${errorString}`);
}

const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: prenomData.styles,
styleUrls: prenomData.styleUrls,
moduleUrl: prenomData.moduleUrl
styles: prenormData.styles,
styleUrls: prenormData.styleUrls,
moduleUrl: prenormData.moduleUrl
}));

const visitor = new TemplatePreparseVisitor();
html.visitAll(visitor, rootNodesAndErrors.rootNodes);
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));

let encapsulation = prenomData.encapsulation;
let encapsulation = prenormData.encapsulation;
if (encapsulation == null) {
encapsulation = this._config.defaultEncapsulation;
}
Expand All @@ -143,8 +149,8 @@ export class DirectiveNormalizer {
template,
templateUrl: templateAbsUrl, styles, styleUrls,
ngContentSelectors: visitor.ngContentSelectors,
animations: prenomData.animations,
interpolation: prenomData.interpolation,
animations: prenormData.animations,
interpolation: prenormData.interpolation, isInline
});
}

Expand All @@ -160,7 +166,8 @@ export class DirectiveNormalizer {
externalStylesheets: externalStylesheets,
ngContentSelectors: templateMeta.ngContentSelectors,
animations: templateMeta.animations,
interpolation: templateMeta.interpolation
interpolation: templateMeta.interpolation,
isInline: templateMeta.isInline,
}));
}

Expand Down
4 changes: 3 additions & 1 deletion packages/compiler/src/i18n/digest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@ enum Endian {
Big,
}

function utf8Encode(str: string): string {
// TODO(vicb): move this to some shared place, as we also need it
// for SourceMaps.
export function utf8Encode(str: string): string {
let encoded: string = '';

for (let index = 0; index < str.length; index++) {
Expand Down
15 changes: 8 additions & 7 deletions packages/compiler/src/jit/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵstringify as stringify} from '@angular/core';

import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileStylesheetMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {CompilerInjectable} from '../injectable';
import {CompileMetadataResolver} from '../metadata_resolver';
Expand Down Expand Up @@ -38,6 +38,7 @@ export class JitCompiler implements Compiler {
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
private _sharedStylesheetCount = 0;

constructor(
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
Expand Down Expand Up @@ -128,7 +129,7 @@ export class JitCompiler implements Compiler {
interpretStatements(compileResult.statements, [compileResult.ngModuleFactoryVar])[0];
} else {
ngModuleFactory = jitStatements(
`/${identifierName(moduleMeta.type)}/module.ngfactory.js`, compileResult.statements,
ngModuleJitUrl(moduleMeta), compileResult.statements,
[compileResult.ngModuleFactoryVar])[0];
}
this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory);
Expand Down Expand Up @@ -251,7 +252,7 @@ export class JitCompiler implements Compiler {
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
identifierName(compMeta.type));
templateSourceUrl(template.ngModule.type, template.compMeta, template.compMeta.template));
const compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
usedPipes);
Expand All @@ -263,10 +264,9 @@ export class JitCompiler implements Compiler {
[viewClass, rendererType] = interpretStatements(
statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
} else {
const sourceUrl =
`/${identifierName(template.ngModule.type)}/${identifierName(template.compType)}/${template.isHost?'host':'component'}.ngfactory.js`;
[viewClass, rendererType] = jitStatements(
sourceUrl, statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
templateJitUrl(template.ngModule.type, template.compMeta), statements,
[compileResult.viewClassVar, compileResult.rendererTypeVar]);
}
template.compiled(viewClass, rendererType);
}
Expand All @@ -289,7 +289,8 @@ export class JitCompiler implements Compiler {
return interpretStatements(result.statements, [result.stylesVar])[0];
} else {
return jitStatements(
`/${result.meta.moduleUrl}.ngstyle.js`, result.statements, [result.stylesVar])[0];
sharedStylesheetJitUrl(result.meta, this._sharedStylesheetCount++), result.statements,
[result.stylesVar])[0];
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions packages/compiler/src/metadata_resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ export class CompileMetadataResolver {
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
}

private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
private _loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean):
Promise<any> {
if (this._directiveCache.has(directiveType)) {
return;
}
Expand Down Expand Up @@ -191,6 +192,7 @@ export class CompileMetadataResolver {

if (metadata.isComponent) {
const templateMeta = this._directiveNormalizer.normalizeTemplate({
ngModuleType,
componentType: directiveType,
moduleUrl: componentModuleUrl(this._reflector, directiveType, annotation),
encapsulation: metadata.template.encapsulation,
Expand Down Expand Up @@ -249,7 +251,8 @@ export class CompileMetadataResolver {
styles: dirMeta.styles,
styleUrls: dirMeta.styleUrls,
animations: animations,
interpolation: dirMeta.interpolation
interpolation: dirMeta.interpolation,
isInline: !!dirMeta.template
});
}

Expand Down Expand Up @@ -378,7 +381,7 @@ export class CompileMetadataResolver {
const loading: Promise<any>[] = [];
if (ngModule) {
ngModule.declaredDirectives.forEach((id) => {
const promise = this._loadDirectiveMetadata(id.reference, isSync);
const promise = this._loadDirectiveMetadata(moduleType, id.reference, isSync);
if (promise) {
loading.push(promise);
}
Expand Down
11 changes: 2 additions & 9 deletions packages/compiler/src/ng_module_compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {CompilerInjectable} from './injectable';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan, typeSourceSpan} from './parse_util';
import {NgModuleProviderAnalyzer} from './provider_analyzer';
import {ProviderAst} from './template_parser/template_ast';

Expand All @@ -37,14 +37,7 @@ export class NgModuleCompileResult {
export class NgModuleCompiler {
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
NgModuleCompileResult {
const moduleUrl = identifierModuleUrl(ngModuleMeta.type);
const sourceFileName = moduleUrl != null ?
`in NgModule ${identifierName(ngModuleMeta.type)} in ${moduleUrl}` :
`in NgModule ${identifierName(ngModuleMeta.type)}`;
const sourceFile = new ParseSourceFile('', sourceFileName);
const sourceSpan = new ParseSourceSpan(
new ParseLocation(sourceFile, null, null, null),
new ParseLocation(sourceFile, null, null, null));
const sourceSpan = typeSourceSpan('NgModule', ngModuleMeta.type);
const deps: ComponentFactoryDependency[] = [];
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
const entryComponentFactories =
Expand Down
7 changes: 6 additions & 1 deletion packages/compiler/src/output/abstract_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,12 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
ctx.print(ast, `}`, useNewLine);
return null;
}

visitCommaExpr(ast: o.CommaExpr, ctx: EmitterVisitorContext): any {
ctx.print(ast, '(');
this.visitAllExpressions(ast.parts, ctx, ',');
ctx.print(ast, ')');
return null;
}
visitAllExpressions(
expressions: o.Expression[], ctx: EmitterVisitorContext, separator: string,
newLine: boolean = false): void {
Expand Down
5 changes: 3 additions & 2 deletions packages/compiler/src/output/js_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ export class JavaScriptEmitter implements OutputEmitter {
srcParts.push(ctx.toSource());

const prefixLines = converter.importsWithPrefixes.size;
const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment();
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
if (sm) {
srcParts.push(sm);
}

// always add a newline at the end, as some tools have bugs without it.
srcParts.push('');
return srcParts.join('\n');
}
}
Expand Down
Loading

0 comments on commit c890dbf

Please sign in to comment.