fix(compiler): always use ng:// prefix for sourcemap urls (#15218)

Fixes:
- In G3, filePaths don’t start with a `/` and therefore became relative.
- Always using the `ng://` prefix groups angular resources in the same
  way for AOT and JIT.
This commit is contained in:
Tobias Bosch 2017-03-16 17:33:17 -07:00 committed by Chuck Jazdzewski
parent d2fbbb44ae
commit 994089d36b
4 changed files with 49 additions and 47 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName, templateSourceUrl} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName, sourceUrl, templateSourceUrl} from '../compile_metadata';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers'; import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
import {CompileMetadataResolver} from '../metadata_resolver'; import {CompileMetadataResolver} from '../metadata_resolver';
@ -217,7 +217,7 @@ export class AotCompiler {
return new GeneratedFile( return new GeneratedFile(
srcFileUrl, genFileUrl, srcFileUrl, genFileUrl,
this._outputEmitter.emitStatements( this._outputEmitter.emitStatements(
srcFileUrl, genFileUrl, statements, exportedVars, this._genFilePreamble)); sourceUrl(srcFileUrl), genFileUrl, statements, exportedVars, this._genFilePreamble));
} }
} }

View File

@ -758,42 +758,43 @@ export function flatten<T>(list: Array<T|T[]>): T[] {
}, []); }, []);
} }
/** export function sourceUrl(url: string) {
* Note: Using `location.origin` as prefix helps displaying them as a hierarchy in chrome. // Note: We need 3 "/" so that ng shows up as a separate domain
* It also helps long-stack-trace zone when rewriting stack traces to not break // in the chrome dev tools.
* source maps (as now all scripts have the same origin). return url.replace(/(\w+:\/\/[\w:-]+)?(\/+)?/, 'ng:///');
*/
function ngJitFolder() {
return 'ng://';
} }
export function templateSourceUrl( export function templateSourceUrl(
ngModuleType: CompileIdentifierMetadata, compMeta: {type: CompileIdentifierMetadata}, ngModuleType: CompileIdentifierMetadata, compMeta: {type: CompileIdentifierMetadata},
templateMeta: {isInline: boolean, templateUrl: string}) { templateMeta: {isInline: boolean, templateUrl: string}) {
let url: string;
if (templateMeta.isInline) { if (templateMeta.isInline) {
if (compMeta.type.reference instanceof StaticSymbol) { if (compMeta.type.reference instanceof StaticSymbol) {
// Note: a .ts file might contain multiple components with inline templates, // 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. // 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`; url = `${compMeta.type.reference.filePath}.${compMeta.type.reference.name}.html`;
} else { } else {
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`; url = `${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`;
} }
} else { } else {
return templateMeta.templateUrl; url = templateMeta.templateUrl;
} }
// always prepend ng:// to make angular resources easy to find and not clobber
// user resources.
return sourceUrl(url);
} }
export function sharedStylesheetJitUrl(meta: CompileStylesheetMetadata, id: number) { export function sharedStylesheetJitUrl(meta: CompileStylesheetMetadata, id: number) {
const pathParts = meta.moduleUrl.split(/\/\\/g); const pathParts = meta.moduleUrl.split(/\/\\/g);
const baseName = pathParts[pathParts.length - 1]; const baseName = pathParts[pathParts.length - 1];
return `${ngJitFolder()}/css/${id}${baseName}.ngstyle.js`; return sourceUrl(`css/${id}${baseName}.ngstyle.js`);
} }
export function ngModuleJitUrl(moduleMeta: CompileNgModuleMetadata): string { export function ngModuleJitUrl(moduleMeta: CompileNgModuleMetadata): string {
return `${ngJitFolder()}/${identifierName(moduleMeta.type)}/module.ngfactory.js`; return sourceUrl(`${identifierName(moduleMeta.type)}/module.ngfactory.js`);
} }
export function templateJitUrl( export function templateJitUrl(
ngModuleType: CompileIdentifierMetadata, compMeta: CompileDirectiveMetadata): string { ngModuleType: CompileIdentifierMetadata, compMeta: CompileDirectiveMetadata): string {
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.ngfactory.js`; return sourceUrl(`${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.ngfactory.js`);
} }

View File

@ -73,6 +73,7 @@ describe('compiler (unbundled Angular)', () => {
describe('aot source mapping', () => { describe('aot source mapping', () => {
const componentPath = '/app/app.component.ts'; const componentPath = '/app/app.component.ts';
const ngComponentPath = 'ng:///app/app.component.ts'
let rootDir: MockDirectory; let rootDir: MockDirectory;
let appDir: MockDirectory; let appDir: MockDirectory;
@ -133,14 +134,15 @@ describe('compiler (unbundled Angular)', () => {
} }
describe('inline templates', () => { describe('inline templates', () => {
const templateUrl = `${componentPath}#AppComponent.html`; const ngUrl = `${ngComponentPath}.AppComponent.html`;
function templateDecorator(template: string) { return `template: \`${template}\`,`; } function templateDecorator(template: string) { return `template: \`${template}\`,`; }
declareTests({templateUrl, templateDecorator}); declareTests({ngUrl, templateDecorator});
}); });
describe('external templates', () => { describe('external templates', () => {
const ngUrl = 'ng:///app/app.component.html';
const templateUrl = '/app/app.component.html'; const templateUrl = '/app/app.component.html';
function templateDecorator(template: string) { function templateDecorator(template: string) {
@ -148,18 +150,17 @@ describe('compiler (unbundled Angular)', () => {
return `templateUrl: 'app.component.html',`; return `templateUrl: 'app.component.html',`;
} }
declareTests({templateUrl, templateDecorator}); declareTests({ngUrl, templateDecorator});
}); });
function declareTests( function declareTests({ngUrl, templateDecorator}:
{templateUrl, templateDecorator}: {ngUrl: string, templateDecorator: (template: string) => string}) {
{templateUrl: string, templateDecorator: (template: string) => string}) {
it('should use the right source url in html parse errors', async(() => { it('should use the right source url in html parse errors', async(() => {
appDir['app.component.ts'] = appDir['app.component.ts'] =
createComponentSource(templateDecorator('<div>\n </error>')); createComponentSource(templateDecorator('<div>\n </error>'));
expectPromiseToThrow( expectPromiseToThrow(
compileApp(), new RegExp(`Template parse errors[\\s\\S]*${templateUrl}@1:2`)); compileApp(), new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:2`));
})); }));
it('should use the right source url in template parse errors', async(() => { it('should use the right source url in template parse errors', async(() => {
@ -167,7 +168,7 @@ describe('compiler (unbundled Angular)', () => {
templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>')); templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>'));
expectPromiseToThrow( expectPromiseToThrow(
compileApp(), new RegExp(`Template parse errors[\\s\\S]*${templateUrl}@1:7`)); compileApp(), new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:7`));
})); }));
it('should create a sourceMap for the template', async(() => { it('should create a sourceMap for the template', async(() => {
@ -181,12 +182,12 @@ describe('compiler (unbundled Angular)', () => {
// the generated file contains code that is not mapped to // the generated file contains code that is not mapped to
// the template but rather to the original source file (e.g. import statements, ...) // the template but rather to the original source file (e.g. import statements, ...)
const templateIndex = sourceMap.sources.indexOf(templateUrl); const templateIndex = sourceMap.sources.indexOf(ngUrl);
expect(sourceMap.sourcesContent[templateIndex]).toEqual(template); expect(sourceMap.sourcesContent[templateIndex]).toEqual(template);
// for the mapping to the original source file we don't store the source code // for the mapping to the original source file we don't store the source code
// as we want to keep whatever TypeScript / ... produced for them. // as we want to keep whatever TypeScript / ... produced for them.
const sourceIndex = sourceMap.sources.indexOf(componentPath); const sourceIndex = sourceMap.sources.indexOf(ngComponentPath);
expect(sourceMap.sourcesContent[sourceIndex]).toBe(null); expect(sourceMap.sourcesContent[sourceIndex]).toBe(null);
}); });
})); }));
@ -199,7 +200,7 @@ describe('compiler (unbundled Angular)', () => {
compileApp().then((genFile) => { compileApp().then((genFile) => {
const sourceMap = extractSourceMap(genFile.source); const sourceMap = extractSourceMap(genFile.source);
expect(originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `'span'`))) expect(originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `'span'`)))
.toEqual({line: 2, column: 3, source: templateUrl}); .toEqual({line: 2, column: 3, source: ngUrl});
}); });
})); }));
@ -212,7 +213,7 @@ describe('compiler (unbundled Angular)', () => {
const sourceMap = extractSourceMap(genFile.source); const sourceMap = extractSourceMap(genFile.source);
expect( expect(
originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `someMethod()`))) originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `someMethod()`)))
.toEqual({line: 2, column: 9, source: templateUrl}); .toEqual({line: 2, column: 9, source: ngUrl});
}); });
})); }));
@ -225,7 +226,7 @@ describe('compiler (unbundled Angular)', () => {
const sourceMap = extractSourceMap(genFile.source); const sourceMap = extractSourceMap(genFile.source);
expect( expect(
originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `someMethod()`))) originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `someMethod()`)))
.toEqual({line: 2, column: 9, source: templateUrl}); .toEqual({line: 2, column: 9, source: ngUrl});
}); });
})); }));
@ -235,7 +236,7 @@ describe('compiler (unbundled Angular)', () => {
compileApp().then((genFile) => { compileApp().then((genFile) => {
const sourceMap = extractSourceMap(genFile.source); const sourceMap = extractSourceMap(genFile.source);
expect(originalPositionFor(sourceMap, {line: 1, column: 0})) expect(originalPositionFor(sourceMap, {line: 1, column: 0}))
.toEqual({line: 1, column: 0, source: componentPath}); .toEqual({line: 1, column: 0, source: ngComponentPath});
}); });
})); }));
} }

View File

@ -73,36 +73,36 @@ export function main() {
} }
describe('inline templates', () => { describe('inline templates', () => {
const templateUrl = 'ng:///DynamicTestModule/MyComp.html'; const ngUrl = 'ng:///DynamicTestModule/MyComp.html';
function templateDecorator(template: string) { return {template}; } function templateDecorator(template: string) { return {template}; }
declareTests({templateUrl, templateDecorator}); declareTests({ngUrl, templateDecorator});
}); });
describe('external templates', () => { describe('external templates', () => {
const templateUrl = 'http://localhost:1234:some/url.html'; const ngUrl = 'ng:///some/url.html';
const templateUrl = 'http://localhost:1234/some/url.html';
function templateDecorator(template: string) { function templateDecorator(template: string) {
resourceLoader.expect(templateUrl, template); resourceLoader.expect(templateUrl, template);
return {templateUrl}; return {templateUrl};
} }
declareTests({templateUrl, templateDecorator}); declareTests({ngUrl, templateDecorator});
}); });
function declareTests({templateUrl, templateDecorator}: { function declareTests(
templateUrl: string, {ngUrl, templateDecorator}:
templateDecorator: (template: string) => { [key: string]: any } {ngUrl: string, templateDecorator: (template: string) => { [key: string]: any }}) {
}) {
it('should use the right source url in html parse errors', fakeAsync(() => { it('should use the right source url in html parse errors', fakeAsync(() => {
@Component({...templateDecorator('<div>\n </error>')}) @Component({...templateDecorator('<div>\n </error>')})
class MyComp { class MyComp {
} }
expect(() => compileAndCreateComponent(MyComp)) expect(() => compileAndCreateComponent(MyComp))
.toThrowError(new RegExp( .toThrowError(
`Template parse errors[\\s\\S]*${templateUrl.replace('$', '\\$')}@1:2`)); new RegExp(`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:2`));
})); }));
it('should use the right source url in template parse errors', fakeAsync(() => { it('should use the right source url in template parse errors', fakeAsync(() => {
@ -111,8 +111,8 @@ export function main() {
} }
expect(() => compileAndCreateComponent(MyComp)) expect(() => compileAndCreateComponent(MyComp))
.toThrowError(new RegExp( .toThrowError(
`Template parse errors[\\s\\S]*${templateUrl.replace('$', '\\$')}@1:7`)); new RegExp(`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:7`));
})); }));
it('should create a sourceMap for templates', fakeAsync(() => { it('should create a sourceMap for templates', fakeAsync(() => {
@ -126,7 +126,7 @@ export function main() {
const sourceMap = getSourceMap('ng:///DynamicTestModule/MyComp.ngfactory.js'); const sourceMap = getSourceMap('ng:///DynamicTestModule/MyComp.ngfactory.js');
expect(sourceMap.sources).toEqual([ expect(sourceMap.sources).toEqual([
'ng:///DynamicTestModule/MyComp.ngfactory.js', templateUrl 'ng:///DynamicTestModule/MyComp.ngfactory.js', ngUrl
]); ]);
expect(sourceMap.sourcesContent).toEqual([null, template]); expect(sourceMap.sourcesContent).toEqual([null, template]);
})); }));
@ -155,7 +155,7 @@ export function main() {
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({ expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
line: 2, line: 2,
column: 4, column: 4,
source: templateUrl, source: ngUrl,
}); });
})); }));
@ -179,13 +179,13 @@ export function main() {
expect(getSourcePositionForStack(error.stack)).toEqual({ expect(getSourcePositionForStack(error.stack)).toEqual({
line: 2, line: 2,
column: 12, column: 12,
source: templateUrl, source: ngUrl,
}); });
// The error should be logged from the element // The error should be logged from the element
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({ expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
line: 2, line: 2,
column: 4, column: 4,
source: templateUrl, source: ngUrl,
}); });
})); }));
@ -209,13 +209,13 @@ export function main() {
expect(getSourcePositionForStack(error.stack)).toEqual({ expect(getSourcePositionForStack(error.stack)).toEqual({
line: 2, line: 2,
column: 12, column: 12,
source: templateUrl, source: ngUrl,
}); });
// The error should be logged from the element // The error should be logged from the element
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({ expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
line: 2, line: 2,
column: 4, column: 4,
source: templateUrl, source: ngUrl,
}); });
})); }));