-
Notifications
You must be signed in to change notification settings - Fork 9.5k
core: utility function for generating eval code #10781
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
Conversation
This looks really good and agreed this will make A few thoughts, though I've only read the code, not played around with it with the type checker, so maybe you've already worked through these:
|
We'll also probably always be living with some level of typescript issues no matter what and we'll just have to be ok with it.
another idea (drifting farther from the proposed PR) is that with multi-client you can now get another devtools to connect to the Lighthouse-controlled page to do some debugging. I just tried putting a |
It's possible, but my concern is I think we consider
Another benefit of importing as As far as enforcing, I think we're just left with code reviews. If we change all imports then I think further contributions won't be influenced to use the destructuring format.
Ah yeah, that's true...for this to work it'd have to be more like
IMO this is pretty nice. I think adopting a standard that all injected functions need to be on a namespaced
Agreed.
I think we'll be able to get 100% TS coverage here if we buy into the
I'll have to give that a whirl. |
Agreed! That's what I meant about
we'll have to agree to disagree :)
Oh, my point was separate from
It's surprisingly usable! We'd just need to turn off the protocol timeout. |
For a first pass, I can put up a PR that just extends |
May refer back to this for further enhancements. #10816 has done the most important bit. |
The current interface for executing JavaScript in a page demands this of the developer:
Idea 1 -
createEvalCode
step 3 is where most of the developer burden surfaces. it can be done in one of three ways, depending on necessity:
To address the serialization woes, I propose this interface:
A real-er example (from
link-elements.js
):trace-elements.js
must return afunction
declaration, so there should also be amode: 'iffe'|'function'
to control the code generation. See the diff to this draft PR for details.I think this would be useful for many of the more complex usages of
evaluateAsync
:lighthouse/lighthouse-core/gather/gatherers/seo/tap-targets.js
Lines 307 to 331 in 5f372ea
lighthouse/lighthouse-core/gather/gatherers/trace-elements.js
Lines 141 to 157 in 5f372ea
lighthouse/lighthouse-core/gather/fetcher.js
Lines 135 to 161 in 5f372ea
Simple ones like this should probably just remain as-is.
I think the criteria is: if it requires more than one function or parameter serialization, it'd benefit from
pageFunction.createEvalCode
.Idea 2 - types
We can "tag" the return value of
createEvalCode
, which returns a string, with the return type ofmainFn
.driver.evaluateAsync
can then attempt to pattern match for that type, and mark its return value as the same!The nice part about this is that it's "tagged" via an intersection type
string & {__taggedType: T}
, so this value can be used in any way a string can be used, which is mighty convenient since at runtime it's only ever gonna be a string.Instances where the type would be useful for code readability and correctness (including most of the examples already linked above):
(
tag
isany
)lighthouse/lighthouse-core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js
Lines 135 to 156 in 5f372ea
(
elements
is coerced)lighthouse/lighthouse-core/gather/gatherers/image-elements.js
Lines 191 to 202 in 5f372ea
(return value is coerced)
lighthouse/lighthouse-core/gather/gatherers/meta-elements.js
Lines 21 to 33 in 5f372ea
(value is coerced)
lighthouse/lighthouse-core/gather/gatherers/iframe-elements.js
Lines 42 to 51 in 5f372ea
(value is coerced)
lighthouse/lighthouse-core/gather/gatherers/anchor-elements.js
Lines 80 to 91 in 5f372ea
There's a few more like these, where
afterPass
directly returns the result ofevaluateAsync
and the type is coerced to w/e the@return
JSDoc says it should be.The criteria I mentioned earlier might then become: if it requires more than one function, parameter serialization, or returns a value, it'd benefit from
pageFunction.createEvalCode
.Idea 3 - scope the dependency functions
Example:
lighthouse/lighthouse-core/gather/gatherers/meta-elements.js
Lines 21 to 33 in 5f372ea
All the logic in this gatherer is in a string, so nothing is type checked. Even if moved to a function and stringified like most other usages of
evaluateAsync
,getElementsInDocument
would need to be ignored with eslint and tsignored because it doesn't actually exist until executed in the browser.page-functions.js
could exportgetElementsInDocument
, and if the function referenced it then typecheck/linting would work, but would error at runtime becausepageFunctions
doesn't exist in the browser.If instead we injected a
pageFunction = {...}
object with all the dependencies, we could import the function in Node, use it in our browser function, and maintain all the type checking. It'd look like this:As part of this, I think
page-functions.js
should always be imported like this:and stop exporting the
*String
variants of each function, just export the function.At this point, we might even consider just injecting all the page functions in
createEvalCode
, but that may be too wasteful (this has to cost something, right?)Idea 4 - debugging
Debugging page functions is tedious. Best way I've found so far is to just throw an error with the data I want to log. It'd be nice if any
console.log
s executed in the code would be collected and printed when the code returns.could do this by listening to
Console.messageAdded
during execution. could require something likeconsole.log('lhdebug', ...)
so that it only logs stuff we are debugging, not something else the page is doing.