-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
core(gather): new computed artifact, js-bundles #10078
Merged
Merged
Changes from all commits
Commits
Show all changes
60 commits
Select commit
Hold shift + click to select a range
6d27caa
initial
connorjclark f9408f3
count lines
connorjclark fa07a80
mapping test
connorjclark 7ad9389
do not pollute global array
connorjclark 2f80d5a
remove hacky globalThis.cdt
connorjclark 122f2a2
ts
connorjclark 808736d
Common.console
connorjclark dff8358
errors
connorjclark 4a9127d
delete some stuff not needed
connorjclark 0d73a8b
reduce code
connorjclark 43da869
tmp
connorjclark ed4bb21
remove ParsedURL
connorjclark 7d50227
nul
connorjclark 9f47880
remove unused js
connorjclark 47e8a41
comment
connorjclark 73eca7b
move SDK up b/c global pollution is gone
connorjclark 3333c4f
add sections to source map type
connorjclark 0b655b6
better cdt lib gen
connorjclark 7129ef9
trim unneeded code
connorjclark 5d17268
rm some ts ignores
connorjclark 6480198
update frontend
connorjclark e6fde71
semver
connorjclark caa269b
comments
connorjclark 943e19c
deletionBraceCount
connorjclark 52796db
no unshift
connorjclark ecb6260
no index
connorjclark e3b8eee
fix log.error, count newlines
connorjclark 370c9bc
remove load fn
connorjclark b9f8a90
test sourceLineMapping
connorjclark 2ba1d27
ts
connorjclark 2cd5a72
no lazy. LH.Artifacts.Bundle
connorjclark 141099b
Merge remote-tracking branch 'origin/master' into computed-artifact-b…
connorjclark 2d1bc1b
indices comment
connorjclark 5b02f21
use tsc
connorjclark 7e44451
names
connorjclark f470521
comment
connorjclark dc5d601
sanity checks
connorjclark 83adf25
JSBundles
connorjclark 1457eae
fix ts
connorjclark 70b0388
latest cdt
connorjclark 24a9538
test
connorjclark 795393f
ts
connorjclark 8c12500
rawcode
connorjclark 354f364
ts undefined
connorjclark 36d4b48
comment
connorjclark 8a5f792
rm todo
connorjclark c5a6faa
ts
connorjclark c947635
comment
connorjclark d3be59a
Merge remote-tracking branch 'origin/master' into computed-artifact-b…
connorjclark 68441bb
-networkRecord
connorjclark e25d82a
Update lighthouse-core/computed/js-bundles.js
connorjclark e73b8c8
test dirty build
connorjclark d9aa735
update
connorjclark 418ed9b
fix
connorjclark d8be656
debug
connorjclark ce15205
blah
connorjclark d75df5e
ok
connorjclark f6ffd75
check just 1
connorjclark bae54d4
2020
connorjclark f1bc372
Merge remote-tracking branch 'origin/master' into computed-artifact-b…
connorjclark File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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,137 @@ | ||
/** | ||
* @license Copyright 2020 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'; | ||
|
||
/* eslint-disable no-console */ | ||
|
||
const fs = require('fs'); | ||
const ts = require('typescript'); | ||
|
||
const outDir = `${__dirname}/../lighthouse-core/lib/cdt/generated`; | ||
const files = { | ||
'node_modules/chrome-devtools-frontend/front_end/sdk/SourceMap.js': 'SourceMap.js', | ||
}; | ||
|
||
console.log('making modifications ...'); | ||
|
||
for (const [inFilename, outFilename] of Object.entries(files)) { | ||
const code = fs.readFileSync(inFilename, 'utf-8'); | ||
const codeWithoutLegacyExports = code.substr(0, code.indexOf('/* Legacy exported object')); | ||
const codeTranspiledToCommonJS = ts.transpileModule(codeWithoutLegacyExports, { | ||
compilerOptions: {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2019}, | ||
}).outputText; | ||
|
||
const sourceFile = ts.createSourceFile('', codeTranspiledToCommonJS, | ||
ts.ScriptTarget.ES2019, true, ts.ScriptKind.JS); | ||
const simplePrinter = ts.createPrinter({newLine: ts.NewLineKind.LineFeed}); | ||
|
||
const classesToRemove = [ | ||
// Not needed. | ||
'EditResult', | ||
// Not needed. | ||
'WasmSourceMap', | ||
]; | ||
const methodsToRemove = [ | ||
// Not needed. | ||
'load', | ||
// Not needed. | ||
'sourceContentProvider', | ||
]; | ||
const expressionsToRemove = [ | ||
/* Original: | ||
|
||
let url = Common.ParsedURL.completeURL(this._baseURL, href) || href; | ||
const source = sourceMap.sourcesContent && sourceMap.sourcesContent[i]; | ||
if (url === this._compiledURL && source) { | ||
url += Common.UIString('? [sm]') | ||
} | ||
this._sourceInfos.set(url, new TextSourceMap.SourceInfo(source, null)); | ||
sourcesList.push(url); | ||
---- | ||
If a source file is the same as the compiled url and there is a sourcesContent, | ||
then `entry.sourceURL` (what is returned from .mappings) will have `? [sm]` appended. | ||
This is useful in DevTools - to show that a sources panel tab not a real network resource - | ||
but for us it is not wanted. The sizing function uses `entry.sourceURL` to index the byte | ||
counts, and is further used in the details to specify a file within a source map. | ||
*/ | ||
`url += Common.UIString('? [sm]')`, | ||
// Delete all the export statements. | ||
'exports.', | ||
]; | ||
// Complicated expressions are hard detect with the TS lib, so instead work with the raw code. | ||
const rawCodeToReplace = { | ||
// Similar to the reason for removing `url += Common.UIString('? [sm]')`. | ||
// The entries in `.mappings` should not have their url property modified. | ||
'Common.ParsedURL.completeURL(this._baseURL, href)': `''`, | ||
}; | ||
|
||
// Verify that all the above code is present. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
const codeFragments = [ | ||
...classesToRemove, | ||
...methodsToRemove, | ||
...expressionsToRemove, | ||
...Object.values(rawCodeToReplace), | ||
]; | ||
for (const codeFragment of codeFragments) { | ||
if (!codeTranspiledToCommonJS.includes(codeFragment)) { | ||
throw new Error(`did not find expected code fragment: ${codeFragment}`); | ||
} | ||
} | ||
|
||
const printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed}, { | ||
substituteNode(hint, node) { | ||
let removeNode = false; | ||
|
||
if (ts.isMethodDeclaration(node) && | ||
// Make typescript happy. | ||
!ts.isComputedPropertyName(node.name) && | ||
methodsToRemove.includes(node.name.text)) { | ||
removeNode = true; | ||
} | ||
|
||
if (ts.isClassDeclaration(node) && node.name && classesToRemove.includes(node.name.text)) { | ||
removeNode = true; | ||
} | ||
|
||
if (ts.isExpressionStatement(node)) { | ||
const asString = simplePrinter.printNode(ts.EmitHint.Unspecified, node, sourceFile); | ||
if (classesToRemove.some(className => asString.includes(className))) { | ||
removeNode = true; | ||
} | ||
if (expressionsToRemove.some(className => asString.includes(className))) { | ||
removeNode = true; | ||
} | ||
} | ||
|
||
if (removeNode) { | ||
return ts.createNode(ts.SyntaxKind.Unknown); | ||
} | ||
|
||
return node; | ||
}, | ||
}); | ||
|
||
let sourceFilePrinted = ''; | ||
sourceFile.forEachChild(node => { | ||
sourceFilePrinted += printer.printNode(ts.EmitHint.Unspecified, node, sourceFile) + '\n'; | ||
}); | ||
|
||
for (const [code, replacement] of Object.entries(rawCodeToReplace)) { | ||
connorjclark marked this conversation as resolved.
Show resolved
Hide resolved
|
||
sourceFilePrinted = sourceFilePrinted.replace(code, replacement); | ||
if (sourceFilePrinted.includes(code)) { | ||
throw new Error(`expected only one occurrence for: ${code}`); | ||
} | ||
} | ||
|
||
const modifiedFile = [ | ||
'// @ts-nocheck\n', | ||
'// generated by yarn build-cdt-lib\n', | ||
'const Common = require(\'../Common.js\')\n', | ||
sourceFilePrinted, | ||
'module.exports = TextSourceMap', | ||
].join(''); | ||
fs.writeFileSync(`${outDir}/${outFilename}`, modifiedFile); | ||
} |
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,122 @@ | ||
/** | ||
* @license Copyright 2020 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 log = require('lighthouse-logger'); | ||
const makeComputedArtifact = require('./computed-artifact.js'); | ||
const SDK = require('../lib/cdt/SDK.js'); | ||
|
||
/** | ||
* Calculate the number of bytes contributed by each source file | ||
* @param {LH.Artifacts.Bundle['map']} map | ||
* @param {string} content | ||
* @return {LH.Artifacts.Bundle['sizes']} | ||
*/ | ||
function computeGeneratedFileSizes(map, content) { | ||
const lines = content.split('\n'); | ||
/** @type {Record<string, number>} */ | ||
const files = {}; | ||
const totalBytes = content.length; | ||
let unmappedBytes = totalBytes; | ||
|
||
// If the map + contents don't line up, return a result that | ||
// denotes nothing is mapped. | ||
const failureResult = {files: {}, unmappedBytes, totalBytes}; | ||
|
||
// @ts-ignore: This function is added in SDK.js. This will eventually be added to CDT. | ||
map.computeLastGeneratedColumns(); | ||
|
||
for (const mapping of map.mappings()) { | ||
const source = mapping.sourceURL; | ||
const lineNum = mapping.lineNumber; | ||
const colNum = mapping.columnNumber; | ||
// @ts-ignore: `lastColumnNumber` is not on types yet. This will eventually be added to CDT. | ||
const lastColNum = /** @type {number=} */ (mapping.lastColumnNumber); | ||
|
||
// Webpack sometimes emits null mappings. | ||
// https://github.com/mozilla/source-map/pull/303 | ||
if (!source) continue; | ||
|
||
// Lines and columns are zero-based indices. Visually, lines are shown as a 1-based index. | ||
|
||
const line = lines[lineNum]; | ||
if (line === null) { | ||
log.error('JSBundles', `${map.url()} mapping for line out of bounds: ${lineNum + 1}`); | ||
return failureResult; | ||
} | ||
|
||
if (colNum > line.length) { | ||
// eslint-disable-next-line max-len | ||
log.error('JSBundles', `${map.url()} mapping for column out of bounds: ${lineNum + 1}:${colNum}`); | ||
return failureResult; | ||
} | ||
|
||
let mappingLength = 0; | ||
if (lastColNum !== undefined) { | ||
if (lastColNum > line.length) { | ||
// eslint-disable-next-line max-len | ||
log.error('JSBundles', `${map.url()} mapping for last column out of bounds: ${lineNum + 1}:${lastColNum}`); | ||
return failureResult; | ||
} | ||
mappingLength = lastColNum - colNum; | ||
} else { | ||
// Add +1 to account for the newline. | ||
mappingLength = line.length - colNum + 1; | ||
} | ||
files[source] = (files[source] || 0) + mappingLength; | ||
unmappedBytes -= mappingLength; | ||
} | ||
|
||
return { | ||
files, | ||
unmappedBytes, | ||
totalBytes, | ||
}; | ||
} | ||
|
||
class JSBundles { | ||
/** | ||
* @param {Pick<LH.Artifacts, 'SourceMaps'|'ScriptElements'>} artifacts | ||
*/ | ||
static async compute_(artifacts) { | ||
const {SourceMaps, ScriptElements} = artifacts; | ||
|
||
/** @type {LH.Artifacts.Bundle[]} */ | ||
const bundles = []; | ||
|
||
// Collate map and script, compute file sizes. | ||
for (const SourceMap of SourceMaps) { | ||
if (!SourceMap.map) continue; | ||
const {scriptUrl, map: rawMap} = SourceMap; | ||
|
||
if (!rawMap.mappings) continue; | ||
|
||
const scriptElement = ScriptElements.find(s => s.src === scriptUrl); | ||
if (!scriptElement) continue; | ||
|
||
const compiledUrl = SourceMap.scriptUrl || 'compiled.js'; | ||
const mapUrl = SourceMap.sourceMapUrl || 'compiled.js.map'; | ||
// @ts-ignore: CDT expects undefined properties to be explicit. | ||
const rawMapForCdt = /** @type {any} */ (rawMap); | ||
const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMapForCdt); | ||
|
||
const content = scriptElement && scriptElement.content ? scriptElement.content : ''; | ||
const sizes = computeGeneratedFileSizes(map, content); | ||
|
||
const bundle = { | ||
rawMap, | ||
script: scriptElement, | ||
map, | ||
sizes, | ||
}; | ||
bundles.push(bundle); | ||
} | ||
|
||
return bundles; | ||
} | ||
} | ||
|
||
module.exports = makeComputedArtifact(JSBundles); |
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
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,14 @@ | ||
/** | ||
* @license Copyright 2020 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'; | ||
|
||
/** @fileoverview Needed for the generated/SourceMap.js file */ | ||
|
||
const Common = { | ||
connorjclark marked this conversation as resolved.
Show resolved
Hide resolved
|
||
console, | ||
}; | ||
|
||
module.exports = Common; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'll be a little weird to have devtools' POV slightly different here, but looking at the spec and a few examples... I don't see a great reason why the sources' URLs should be relative to the sourcemap file's URL.
so maybe we end up also removing this in devtools.