Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark committed Dec 7, 2019
1 parent d0f1773 commit 6d27caa
Show file tree
Hide file tree
Showing 14 changed files with 1,447 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

**/node_modules/**
**/third_party/**
**/generated/**
**/source-maps/**

/dist/**

Expand Down
49 changes: 49 additions & 0 deletions build/build-cdt-lib.js
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(''));
}
161 changes: 161 additions & 0 deletions lighthouse-core/computed/bundle-analysis.js
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);
78 changes: 78 additions & 0 deletions lighthouse-core/lib/cdt/SDK.js
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;
Loading

0 comments on commit 6d27caa

Please sign in to comment.