-
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
misc: return specific html element type for dom.find #11526
Conversation
FWIW there's been a few people already doing this with the 4.1 beta, e.g. |
holy cow. gonna dig into that magic, thanks for sharing do we want to do that instead? we wouldn't be able to assert at runtime (not without duplicating the query parsing in JS...), but that's nbd. just like today, things should still error if incorrectly using the wrong html element. If we do, we gotta updates the TS. and wait for 4.1 stable. I saw a few tricky looking errors last time I checked out the latest tsc. |
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.
a few more cases of this
reportuifeatures
L141
L323
L327
cat renderer L339 - svg if you wanna tackle it...
personally, i wouldn't do either of these. doing it solely in TS seems cool, but i can't rationalize that time investment on a problem this size. IMO the current casts fixed in the current PR aren't a huge deal, so removing them doesnt feel too valuable. But.. I'm not gonna block it if others think otherwise. |
Fixed L141, not sure what you are referring to w/ the others.
doesn't use dom.find so i don't wanna change anything here. |
dunno about both but one of them was: l325
obv a querySelector use, not find(), but.. there are others.. no idea why they're not using find/findAll but... they have the same issue l231
dom.js L110
|
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.
@brendankenny I much prefer the api enabled by that magic TS you shared. When #11690 lands should we do that instead? @paulirish you mention maintainer burden but I don't think there is any with a copy/paste, and if a future TS makes it untenable then we can just do what this PR does now. I'd prefer the solution that requires no actual API change. |
It's under consideration for core typescript types (microsoft/TypeScript#29037) though there is skepticism (microsoft/TypeScript#41538, scroll down to "Better Types for querySelector"). There's also at least one library (https://www.npmjs.com/package/typed-query-selector) which would make it easy to include, though I haven't looked at the quality of implementation or anything :) |
If it were possible, what do you think about using // This is a demo only, it's not enough
interface Dom {
find: ParentNode['QuerySelector'];
} This would have over 9000 advantages:
|
Yeah, we'll definitely do this when available. @fregante I just tried using Strange, I suppose you don't see these errors? |
looks like those are real errors from the library, hidden when directly testing the library because of |
(I suspect everything's ok, the bounds just might need to be relaxed to |
types/externs.d.ts
Outdated
? HTMLElementTagNameMap[Tag] | ||
: Tag extends keyof SVGElementTagNameMap | ||
? SVGElementTagNameMap[Tag] | ||
: HTMLElement; // typed-query-selector use Element here, which we don't want. |
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 we should maybe do the right thing and have it be Element
here. Maybe we stuck to a default HTMLElement
because too many things needed to be fixed simultaneously?
Playing with it for a few minutes to see how bad the transition would be and I already found a bug in our renderer :)
lighthouse/lighthouse-core/report/html/renderer/category-renderer.js
Lines 146 to 153 in b2aa480
/** | |
* @return {HTMLElement} | |
*/ | |
_createChevron() { | |
const chevronTmpl = this.dom.cloneTemplate('#tmpl-lh-chevron', this.templateContext); | |
const chevronEl = this.dom.find('.lh-chevron', chevronTmpl); | |
return chevronEl; | |
} |
is allowed to return HTMLElement
because dom.find()
is defaulting to returning HTMLElement
, but .lh-chevron
is an SVGElement
:
lighthouse/lighthouse-core/report/html/templates.html
Lines 35 to 37 in b2aa480
<!-- Toggle arrow chevron --> | |
<template id="tmpl-lh-chevron"> | |
<svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> |
As a result, when we try to add a title
to the chevron for a "Show audits"
tooltip:
lighthouse/lighthouse-core/report/html/renderer/category-renderer.js
Lines 291 to 292 in b2aa480
const chevronEl = summaryInnerEl.appendChild(this._createChevron()); | |
chevronEl.title = Util.i18n.strings.auditGroupExpandTooltip; |
the property is set but it doesn't do anything...because SVGElement
doesn't support title
for tooltips (I guess you need <title>
for that or just set it on a parent HTMLElement
, like we do for the chevron on perf audits)
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.
good catch, changed to use Element as fallback.
required adding a lot of div
prefixes
Where is the |
I believe it's from https://github.com/g-plane/typed-query-selector/blob/2efb75b8cf6b671332bc4c9e62a101f276902814/parser.d.ts#L106-L108 |
cc @g-plane |
I can reproduce with setting |
I've released v2.4.0 which contains changes:
|
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.
Nice! Personally I love it. Safer and really no development overhead at all (it might reduce DX overhead, really).
@paulirish, seem better to you?
chevronEl.title = Util.i18n.strings.auditGroupExpandTooltip; | ||
const summaryInnerEl = this.dom.find('div.lh-audit-group__summary', clumpElement); | ||
summaryInnerEl.appendChild(this._createChevron()); | ||
summaryInnerEl.title = Util.i18n.strings.auditGroupExpandTooltip; |
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.
is this weird having the tooltip on the full width of the <summary>
for the group? Technically clicking on any of it does expand the group.
lighthouse/lighthouse-core/report/html/templates.html
Lines 57 to 58 in 73af3c8
<summary> | |
<div class="lh-audit-group__summary"> |
The alternative would be like the perf audits we have the tooltip on the parent .lh-chevron-container
that's only the size of the chevron.
cc @paulirish as the person who would probably care the most :)
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 looked at https://www.w3.org/TR/wai-aria-practices-1.1/#disclosure and a few other things and i'm convinced we're better completely without this title.
- none of the examples here or elsewhere have an extra tooltip written as a command, instead they all rely on descriptive text to describe whats behind the disclosure
- in our impl, it still says
show audits
even after you expand. we probably meant to switch tohide
but never cared enough.
let's delete!
also @connorjclark you can delete the title
in the HTML since we now know it's invalid https://github.com/GoogleChrome/lighthouse/blob/dom-find-types/lighthouse-core/report/html/templates.html#L37
@@ -70,15 +70,16 @@ class PerformanceCategoryRenderer extends CategoryRenderer { | |||
} | |||
|
|||
// Overwrite the displayValue with opportunity's wastedMs | |||
const displayEl = this.dom.find('.lh-audit__display-text', element); | |||
const displayEl = | |||
this.dom.find('span.lh-audit__display-text, div.lh-audit__display-text', element); |
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 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 one's kind of unfortunate :/
I wonder why we have
<span>
in one place and a<div>
in the other?
actually, they always end up display: flex
from .lh-audit__header
, so they could both be <span>
or both be <div>
and not have to have this double selector. The minor improvement is that the next person looking at this line doesn't have to wonder if it's significant that they're different types of elements, but up to you if that's worth anything :)
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 use the tag name at all?
this.dom.find('span.lh-audit__display-text, div.lh-audit__display-text', element); | |
this.dom.find('.lh-audit__display-text', element); |
or
this.dom.find('span.lh-audit__display-text, div.lh-audit__display-text', element); | |
this.dom.find<HTMLElement>('.lh-audit__display-text', element); |
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.
The minor improvement is that the next person looking at this line doesn't have to wonder if it's significant that they're different types of elements, but up to you if that's worth anything :)
div
changed the layout.
I guess you mistook .lh-audit__header
to be the parent of this element, but it's the grandparent.
Why use the tag name at all?
Because title
doesn't exist on Element
.
(also, this file is JS, not TS).
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.
they always end up
display: flex
from.lh-audit__header
, so they could both be<span>
or both be<div>
and not have to have this double selector.
div
changed the layout.I guess you mistook
.lh-audit__header
to be the parent of this element, but it's the grandparent.
Ah, whoops, sorry. Well, the <div>
can be a <span>
because that's the one of those two cases I actually tested to see if it would work :)
edit: though, again, the TODO
you added SGTM too :)
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 wasn't able to confidently test it (quickly..) so I just tossed a TODO there. Also leaving the "remove .title" work to a different PR, so figured keeping this all to just type changes / should-be-noop runtime changes is ideal.
*/ | ||
find(query, context) { | ||
/** @type {?HTMLElement} */ | ||
/** @type {?import('typed-query-selector/parser').ParseSelector<T>} */ |
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 are your thoughts about doing it this way vs using the querySelector
shim provided by default?
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 you're suggesting deleting this jsdoc line and relying on the types the lib injects. however, the T
type doesn't transfer. quick to test: just delete the line and see how the function always returns Element
then.
@fregante this approach is a good one, but for us this will be a devDep, not exposed to end users of Lighthouse. It's making the internal generation of the report easier/(type)safer, not a report feature or anything. |
Adds an optional third parameter toAllows us to remove some ugly type coercion.dom.find/findAll
which is plugged into typescript to return a specific HTML element type. Also does a runtime check.found all occurrences with\*/ \(.*dom\.find
. maybe more?An alternative API would be to keep the parameters the same, but use TypeScript magic to extract the tag name from a CSS selector. But I don't think typescript yet has the meta-programming capabilities for this (the template strings update is moving closer to this... microsoft/TypeScript#40336 ).ended up using TypeScript magic via
typed-query-selector
:)