-
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: better type checking of evaluate() code #12795
Conversation
build/build-bundle.js
Outdated
// Class and function names are needed for things like loading gatherers by | ||
// name and accessing functions by name in Runtime.evaluate. Instead, just | ||
// rely on whitespace elimination for the large savings. | ||
compress: false, | ||
mangle: false, |
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.
there are a few permutations that work here, but I'm not sure if it matters (these just affect identifiers, so more bytes but parsers blaze through them so not a big deal if you aren't downloading our bundle).
The biggest savings would be something like:
// Class and function names are needed for things like loading gatherers by | |
// name and accessing functions by name in Runtime.evaluate. Instead, just | |
// rely on whitespace elimination for the large savings. | |
compress: false, | |
mangle: false, | |
// The config relies on class names for gatherers. | |
keep_classnames: true, | |
// Runtime.evaluate errors if function names are elided. | |
keep_fnames: true, | |
mangle: { | |
reserved: Object.keys(pageFunctions), | |
}, |
which is essentially no change in size over the current bundle but would be more fragile (either the function would need to be local to the file or in page-functions
). No problem today but if you did import someone else's function it wouldn't necessarily be obvious why it was broken.
// HACK: `getNodeDetails` is a function used in many places with multiple deps | ||
// that would need to be listed in the `evaluate()` call every time it's used. | ||
// The deps could be inlined, but they need to be available externally for | ||
// testing. So, override `getNodeDetails.toString()` to include all the deps. |
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.
what this says. All of the functions used in here are only used inside getNodeDetails
except getBoundingClientRect
(which would be easy to replace in full-page-screenshot
if need be) so they could all be defined internally to a getNodeDetails
wrapping function but...it is very convenient to test them all separately. Redefining toString
seems ok to me here, but we could rework if it's too ugly for anyone.
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.
this is hilarious
Oh yeah, and the relevance to #12771: page functions won't be treated as globals by bundlers, so no need to do any patching of the bundled output. |
const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js'); | ||
const dom = require('../driver/dom.js'); | ||
const pageFunctions = require('../../lib/page-functions.js'); | ||
const {getElementsInDocument, getNodeDetails} = require('../../lib/page-functions.js'); |
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.
(you probably already know this, just wanted to make it explicit)
FWIW this is what breaks terser. this is basically
const getElementsInDocument = require('../../lib/page-functions.js').getElementsInDocument;
which allows terser to do w/e it wants with the name here since it is just local.
went with the |
hmm, somehow this makes the devtools bundle 41KiB smaller even though I would have thought reserving the names would make it bigger (since there's a few names we won't have minified that would have been minified before). Maybe there's a default set of reserved names this is replacing? I ran the bundle tests locally but I guess lets see how the DT tests go... |
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.
left some nits in comments
but there are more options I'll mention below if we'd rather take more fragile approaches
Besides injecting pageFunctions
consisting of w/e passed to deps
, what options are there?
if we're able to keep the bundle size to ~the same with the custom mangle settings then I'm all for the approach in the PR. otherwise, I'll begrudgingly still accept it and its 20% addition to bundle size :)
double check with #12771
I'll check this.
Maybe there's a default set of reserved names this is replacing? I ran the bundle tests locally but I guess lets see how the DT tests go
seeing this in GHA bundled smoke test for tap targets:
✘ difference at TapTargets artifact[0]
expected: {"node":{"snippet":"/large-link-at-bottom-of-page/"}}
found: undefined
found result:
{
"__failedInBrowser": true,
"name": "ReferenceError",
"message": "a is not defined"
}
can you update the PR with master to pick up the recent CDT test fixes?
|
||
/* c8 ignore start */ | ||
function collectMetaElements() { | ||
const functions = /** @type {typeof pageFunctions} */({ |
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.
so long sweet summer child
@@ -13,10 +13,11 @@ | |||
* the c8 code coverage tool. See c8.sh | |||
* | |||
* Important: this module should only be imported like this: | |||
* const pageFunctions = require('...'); |
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.
missing the "do it this way" example now.
could keep the same comment and just swap the examples?
// that would need to be listed in the `evaluate()` call every time it's used. | ||
// The deps could be inlined, but they need to be available externally for | ||
// testing. So, override `getNodeDetails.toString()` to include all the deps. | ||
/** @type {string} */ |
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.
/** @type {string} */ |
seems obvious what the type of toString() is :)
${getBoundingClientRect.toString()} | ||
${getOuterHTMLSnippet.toString()} | ||
${getNodeLabel.toString()} | ||
${originalgetNodeDetailsToString}`; |
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.
originalGetNodeDetailsToString
^
@@ -294,7 +293,7 @@ class TraceElements extends FRGatherer { | |||
objectId, |
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 thought we had a version of this on driver/executeContext that was typed like execute
and also handled the boilerplate here... but I guess we don't. it's the only instance of Runtime.callFunctionOn
so it makes sense.
@@ -94,7 +94,7 @@ class ExecutionContext { | |||
return new Promise(function (resolve) { | |||
return Promise.resolve() | |||
.then(_ => ${expression}) | |||
.catch(${pageFunctions.wrapRuntimeEvalErrorInBrowserString}) | |||
.catch(${pageFunctions.wrapRuntimeEvalErrorInBrowser.toString()}) |
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.
why call toString explicitly when all objects in a template string are coerced via .toString()
automatically?
pushed to removing the postprocess stuff and just using mangle didn't work ... rollup has already renamed those functions before terser ever gets a chance to do anything. With terser disabled, image-elements looks like this: // source
const {
getElementsInDocument,
getBoundingClientRect,
getNodeDetails,
} = require('../../lib/page-functions.js'); // generated
const {
getElementsInDocument: getElementsInDocument$5,
getBoundingClientRect,
getNodeDetails: getNodeDetails$6,
} = pageFunctions;
// ....
function collectImageElementInfo() {
const allElements = getElementsInDocument$5('*'); // does not exist in runtime.evaluate
return getHTMLImages(allElements).concat(getCSSImages(allElements));
} And pretty much every audit errored with a similar reason. can repro in that branch with |
weird, can you tell why rollup still thinks it's a global?
yeah, hmm... |
oh right, because |
see #12771 (comment)
explicitly require individual functions that are going to be
deps
for anevaluate()
call. This continues the blurred line between Node andevaluate()
code that runs in the browser, but a) they're already intermixed and b) this allows the results to be type checked instead of alwaysany
via// @ts-expect-error
.*String
properties onpageFunctions
, just use the function directlytoString
approach forgetNodeDetails()
so we can keep using it as it has been. See below for discussion