-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d0f1773
commit 6d27caa
Showing
14 changed files
with
1,447 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ | |
|
||
**/node_modules/** | ||
**/third_party/** | ||
**/generated/** | ||
**/source-maps/** | ||
|
||
/dist/** | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
const fs = require('fs'); | ||
const {execFileSync} = require('child_process'); | ||
const glob = require('glob'); | ||
|
||
const files = [ | ||
'node_modules/chrome-devtools-frontend/front_end/sdk/SourceMap.js', | ||
'node_modules/chrome-devtools-frontend/front_end/common/ParsedURL.js', | ||
]; | ||
const outDir = 'lighthouse-core/lib/cdt/generated'; | ||
|
||
execFileSync('node_modules/.bin/tsc', [ | ||
'--allowJs', | ||
'--outDir', | ||
outDir, | ||
...files, | ||
]); | ||
|
||
console.log('making modifications ...'); | ||
|
||
/** @type {[RegExp, string][]} */ | ||
const patterns = [ | ||
[/self./g, ''], | ||
[/(SDK|Common)/g, 'globalThis.cdt.$1'], | ||
]; | ||
for (const file of glob.sync(`${outDir}/**/*.js`)) { | ||
const code = fs.readFileSync(file, 'utf-8'); | ||
const lines = code.match(/^.*(\r?\n|$)/mg) || []; | ||
const modifiedLines = lines.map((line, i) => { | ||
// Don't modify jsdoc comments. | ||
if (/^\s*[/*]/.test(line)) { | ||
return line; | ||
} | ||
|
||
let newLine = line; | ||
let changed = false; | ||
for (const pattern of patterns) { | ||
if (!pattern[0].test(newLine)) continue; | ||
changed = true; | ||
newLine = newLine.replace(pattern[0], pattern[1]); | ||
} | ||
if (changed) { | ||
console.log(`${file}:${i}: ${line.trim()}`); | ||
} | ||
return newLine; | ||
}); | ||
modifiedLines.unshift('// generated by build-cdt-lib.js\n'); | ||
modifiedLines.unshift('// @ts-nocheck\n'); | ||
fs.writeFileSync(file, modifiedLines.join('')); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/** | ||
* @license Copyright 2019 Google Inc. All Rights Reserved. | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. | ||
*/ | ||
'use strict'; | ||
|
||
const Audit = require('../audits/audit.js'); | ||
const NetworkRecords = require('./network-records.js'); | ||
const makeComputedArtifact = require('./computed-artifact.js'); | ||
|
||
/** | ||
* @typedef {typeof import('../lib/cdt/SDK.js')} SDK | ||
*/ | ||
|
||
/** | ||
* @typedef Sizes | ||
* @property {Record<string, number>} files | ||
* @property {number} unmappedBytes | ||
* @property {number} totalBytes | ||
*/ | ||
|
||
/** | ||
* @typedef Bundle | ||
* @property {LH.Artifacts.RawSourceMap} rawMap | ||
* @property {LH.Artifacts.ScriptElement} script | ||
* @property {LH.Artifacts.NetworkRequest=} networkRecord | ||
* @property {SDK['TextSourceMap']} map | ||
* @property {Sizes} sizes | ||
*/ | ||
|
||
// Lifted from source-map-explorer. | ||
/** Calculate the number of bytes contributed by each source file */ | ||
// @ts-ignore | ||
function computeGeneratedFileSizesForCDT(sourceMapData) { | ||
const {map, content} = sourceMapData; | ||
const lines = content.split('\n'); | ||
/** @type {Record<string, number>} */ | ||
const files = {}; | ||
let mappedBytes = 0; | ||
|
||
map.computeLastGeneratedColumns(); | ||
|
||
// @ts-ignore | ||
for (const mapping of map.mappings()) { | ||
const source = mapping.sourceURL; | ||
const generatedLine = mapping.lineNumber + 1; | ||
const generatedColumn = mapping.columnNumber; | ||
const lastGeneratedColumn = mapping.lastColumnNumber; | ||
|
||
// Webpack seems to sometimes emit null mappings. | ||
// https://github.com/mozilla/source-map/pull/303 | ||
if (!source) continue; | ||
|
||
// Lines are 1-based | ||
const line = lines[generatedLine - 1]; | ||
if (line === null) { | ||
// throw new AppError({ | ||
// code: 'InvalidMappingLine', | ||
// generatedLine: generatedLine, | ||
// maxLine: lines.length, | ||
// }); | ||
} | ||
|
||
// Columns are 0-based | ||
if (generatedColumn >= line.length) { | ||
// throw new AppError({ | ||
// code: 'InvalidMappingColumn', | ||
// generatedLine: generatedLine, | ||
// generatedColumn: generatedColumn, | ||
// maxColumn: line.length, | ||
// }); | ||
continue; | ||
} | ||
|
||
let mappingLength = 0; | ||
if (lastGeneratedColumn !== undefined) { | ||
if (lastGeneratedColumn >= line.length) { | ||
// throw new AppError({ | ||
// code: 'InvalidMappingColumn', | ||
// generatedLine: generatedLine, | ||
// generatedColumn: lastGeneratedColumn, | ||
// maxColumn: line.length, | ||
// }); | ||
continue; | ||
} | ||
mappingLength = lastGeneratedColumn - generatedColumn + 0; | ||
} else { | ||
// TODO Buffer.byteLength? | ||
mappingLength = line.length - generatedColumn; | ||
} | ||
files[source] = (files[source] || 0) + mappingLength; | ||
mappedBytes += mappingLength; | ||
} | ||
|
||
// TODO: remove? | ||
// Don't count newlines as original version didn't count newlines | ||
const totalBytes = content.length - lines.length + 1; | ||
|
||
return { | ||
files, | ||
unmappedBytes: totalBytes - mappedBytes, | ||
totalBytes, | ||
}; | ||
} | ||
|
||
class BundleAnalysis { | ||
/** | ||
* @param {Pick<LH.Artifacts, 'SourceMaps'|'ScriptElements'|'devtoolsLogs'>} artifacts | ||
* @param {LH.Audit.Context} context | ||
*/ | ||
static async compute_(artifacts, context) { | ||
const {SourceMaps, ScriptElements} = artifacts; | ||
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; | ||
const networkRecords = await NetworkRecords.request(devtoolsLog, context); | ||
|
||
/** @type {Bundle[]} */ | ||
const bundles = []; | ||
|
||
// Collate map, script, and network record. | ||
for (let mapIndex = 0; mapIndex < SourceMaps.length; mapIndex++) { | ||
const {scriptUrl, map: rawMap} = SourceMaps[mapIndex]; | ||
if (!rawMap) continue; | ||
|
||
const scriptElement = ScriptElements.find(s => s.src === scriptUrl); | ||
const networkRecord = networkRecords.find(r => r.url === scriptUrl); | ||
if (!scriptElement) continue; | ||
|
||
// Lazily generate expensive things. | ||
/** @type {SDK['TextSourceMap']=} */ | ||
let map; | ||
/** @type {Sizes=} */ | ||
let sizes; | ||
|
||
const bundle = { | ||
rawMap, | ||
script: scriptElement, | ||
networkRecord, | ||
get map() { | ||
// Defer global pollution. | ||
const SDK = require('../lib/cdt/SDK.js'); | ||
if (map) return map; | ||
// @ts-ignore: TODO: `sections` needs to be in rawMap types | ||
return map = new SDK.TextSourceMap(`compiled.js`, `compiled.js.map`, rawMap); | ||
}, | ||
get sizes() { | ||
if (sizes) return sizes; | ||
return sizes = computeGeneratedFileSizesForCDT({ | ||
map: bundle.map, | ||
content: scriptElement && scriptElement.content, | ||
}); | ||
}, | ||
}; | ||
bundles.push(bundle); | ||
} | ||
|
||
return bundles; | ||
} | ||
} | ||
|
||
module.exports = makeComputedArtifact(BundleAnalysis); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// @ts-nocheck | ||
// TODO: How to ignore everything here in tsc? | ||
|
||
global.cdt = {}; | ||
|
||
require('./generated/common/ParsedURL.js'); | ||
|
||
const SDK = { | ||
...require('./generated/sdk/SourceMap.js'), | ||
}; | ||
|
||
Object.defineProperty(Array.prototype, 'upperBound', { | ||
/** | ||
* Return index of the leftmost element that is greater | ||
* than the specimen object. If there's no such element (i.e. all | ||
* elements are smaller or equal to the specimen) returns right bound. | ||
* The function works for sorted array. | ||
* When specified, |left| (inclusive) and |right| (exclusive) indices | ||
* define the search window. | ||
* | ||
* @param {!T} object | ||
* @param {function(!T,!S):number=} comparator | ||
* @param {number=} left | ||
* @param {number=} right | ||
* @return {number} | ||
* @this {Array.<!S>} | ||
* @template T,S | ||
*/ | ||
value: function(object, comparator, left, right) { | ||
function defaultComparator(a, b) { | ||
return a < b ? -1 : (a > b ? 1 : 0); | ||
} | ||
comparator = comparator || defaultComparator; | ||
let l = left || 0; | ||
let r = right !== undefined ? right : this.length; | ||
while (l < r) { | ||
const m = (l + r) >> 1; | ||
if (comparator(object, this[m]) >= 0) { | ||
l = m + 1; | ||
} else { | ||
r = m; | ||
} | ||
} | ||
return r; | ||
}, | ||
}); | ||
|
||
globalThis.cdt.SDK.TextSourceMap.prototype.findExactEntry = function(line, column) { | ||
// findEntry takes compiled locations and returns original locations. | ||
const entry = this.findEntry(line, column); | ||
// without an exact hit, we return no match | ||
const hitMyBattleShip = entry && entry.lineNumber === line; | ||
if (!entry || !hitMyBattleShip) { | ||
return { | ||
sourceColumnNumber: null, | ||
sourceLineNumber: null, | ||
name: null, | ||
sourceURL: null, | ||
}; | ||
} | ||
return entry; | ||
}; | ||
|
||
// Add `lastColumnNumber` to mappings. | ||
globalThis.cdt.SDK.TextSourceMap.prototype.computeLastGeneratedColumns = function() { | ||
const mappings = this.mappings(); | ||
if (mappings.length && typeof mappings[0].lastColumnNumber !== 'undefined') return; | ||
|
||
for (let i = 0; i < mappings.length - 1; i++) { | ||
const mapping = mappings[i]; | ||
const nextMapping = mappings[i + 1]; | ||
if (mapping.lineNumber === nextMapping.lineNumber) { | ||
mapping.lastColumnNumber = nextMapping.columnNumber; | ||
} | ||
} | ||
}; | ||
|
||
module.exports = SDK; |
Oops, something went wrong.