Skip to content

Commit

Permalink
report: use source maps to show original file name (#10930)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark authored Jan 20, 2021
1 parent afc148e commit e298b3c
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 8 deletions.
21 changes: 21 additions & 0 deletions lighthouse-core/audits/byte-efficiency/legacy-javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,23 @@ class LegacyJavascript extends ByteEfficiencyAudit {
return transferRatio;
}

/**
* @param {LH.Artifacts.Bundle} bundle
* @param {number} generatedLine
* @param {number} generatedColumn
* @return {LH.Audit.Details.SourceLocationValue['original']}
*/
static _findOriginalLocation(bundle, generatedLine, generatedColumn) {
const entry = bundle && bundle.map.findEntry(generatedLine, generatedColumn);
if (!entry) return;

return {
file: entry.sourceURL || '',
line: entry.sourceLineNumber || 0,
column: entry.sourceColumnNumber || 0,
};
}

/**
* @param {LH.Artifacts} artifacts
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
Expand Down Expand Up @@ -434,8 +451,11 @@ class LegacyJavascript extends ByteEfficiencyAudit {
// Not needed, but keeps typescript happy.
totalBytes: 0,
};

const bundle = bundles.find(bundle => bundle.script.src === url);
for (const match of matches) {
const {name, line, column} = match;

/** @type {SubItem} */
const subItem = {
signal: name,
Expand All @@ -444,6 +464,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
url,
line,
column,
original: bundle && this._findOriginalLocation(bundle, line, column),
urlProvider: 'network',
},
};
Expand Down
29 changes: 23 additions & 6 deletions lighthouse-core/report/html/renderer/details-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class DetailsRenderer {

/**
* @param {{text: string, url: string}} details
* @return {Element}
* @return {HTMLElement}
*/
_renderLink(details) {
const allowedProtocols = ['https:', 'http:'];
Expand Down Expand Up @@ -550,22 +550,39 @@ class DetailsRenderer {
}

// Lines are shown as one-indexed.
const line = item.line + 1;
const column = item.column;
const generatedLocation = `${item.url}:${item.line + 1}:${item.column}`;
let sourceMappedOriginalLocation;
if (item.original) {
const file = item.original.file || '<unmapped>';
sourceMappedOriginalLocation = `${file}:${item.original.line + 1}:${item.original.column}`;
}

// We render slightly differently based on presence of source map and provenance of URL.
let element;
if (item.urlProvider === 'network') {
if (item.urlProvider === 'network' && sourceMappedOriginalLocation) {
element = this._renderLink({
url: item.url,
text: sourceMappedOriginalLocation,
});
element.title = `maps to generated location ${generatedLocation}`;
} else if (item.urlProvider === 'network' && !sourceMappedOriginalLocation) {
element = this.renderTextURL(item.url);
this._dom.find('.lh-link', element).textContent += `:${line}:${column}`;
this._dom.find('.lh-link', element).textContent += `:${item.line + 1}:${item.column}`;
} else if (item.urlProvider === 'comment' && sourceMappedOriginalLocation) {
element = this._renderText(`${sourceMappedOriginalLocation} (from source map)`);
element.title = `${generatedLocation} (from sourceURL)`;
} else if (item.urlProvider === 'comment' && !sourceMappedOriginalLocation) {
element = this._renderText(`${generatedLocation} (from sourceURL)`);
} else {
element = this._renderText(`${item.url}:${line}:${column} (from sourceURL)`);
return null;
}

element.classList.add('lh-source-location');
element.setAttribute('data-source-url', item.url);
// DevTools expects zero-indexed lines.
element.setAttribute('data-source-line', String(item.line));
element.setAttribute('data-source-column', String(item.column));

return element;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ describe('LegacyJavaScript audit', () => {
"location": Object {
"column": 0,
"line": 0,
"original": undefined,
"type": "source-location",
"url": "https://www.googletagmanager.com/a.js",
"urlProvider": "network",
Expand Down
31 changes: 31 additions & 0 deletions lighthouse-core/test/report/html/renderer/details-renderer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,37 @@ describe('DetailsRenderer', () => {
assert.equal(sourceLocationEl.getAttribute('data-source-column'), `${sourceLocation.column}`);
});

it('renders source-location values using source map data', () => {
const sourceLocation = {
type: 'source-location',
url: 'https://www.example.com/script.js',
urlProvider: 'network',
line: 10,
column: 5,
original: {
file: 'main.js',
line: 100,
column: 10,
},
};
const details = {
type: 'table',
headings: [{key: 'content', itemType: 'source-location', text: 'Heading'}],
items: [{content: sourceLocation}],
};

const el = renderer.render(details);
const sourceLocationEl = el.querySelector('.lh-source-location.lh-link');
assert.strictEqual(sourceLocationEl.localName, 'a');
assert.equal(sourceLocationEl.href, 'https://www.example.com/script.js');
assert.equal(sourceLocationEl.textContent, 'main.js:101:10');
assert.equal(sourceLocationEl.title, 'maps to generated location https://www.example.com/script.js:11:5');
// DevTools should still use the generated location.
assert.equal(sourceLocationEl.getAttribute('data-source-url'), sourceLocation.url);
assert.equal(sourceLocationEl.getAttribute('data-source-line'), `${sourceLocation.line}`);
assert.equal(sourceLocationEl.getAttribute('data-source-column'), `${sourceLocation.column}`);
});

it('renders source-location with lh-link class for relative url', () => {
const sourceLocation = {
type: 'source-location',
Expand Down
15 changes: 13 additions & 2 deletions types/audit-details.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,23 @@ declare global {
*/
export interface SourceLocationValue {
type: 'source-location';
/** urls from the network are always valid urls. otherwise, urls come from either a comment or header, and may not be well-formed. */
/** A "url" representing the source file. May not be a valid URL, see `urlProvider`. */
url: string;
/** 'network' when the url is the actual, observed resource url. 'comment' when the url comes from a sourceMapURL comment or X-SourceMap header */
/**
* - `network` when the url is the actual, observed resource url. This is always a valid URL.
* - `comment` when the url comes from a sourceURL comment. This could be anything, really.
*/
urlProvider: 'network' | 'comment';
/** Zero-indexed. */
line: number;
column: number;
/** The original file location from the source map. */
original?: {
/** The relevant file from the map's `sources` array. */
file: string;
line: number;
column: number;
};
}

/**
Expand Down

0 comments on commit e298b3c

Please sign in to comment.