From the fleet census: 620 function bodies indexed across 29 plugin trees, 16 identical in two or more. These are the four cheapest — none closes over anything beyond its own parameters, so each is a file move plus an import rather than a new dependency to thread. hostElement resolves the custom element a component is mounted inside, one hop out through the shadow root. It was identical in three plugins, and it is the piece worth sharing for a reason other than line count: every plugin ships through defineCustomElement, a composite nests two shadow roots, and code that forgets the hop operates on the wrong node while looking correct. A document-level query for a node inside a shadow root returns null on a working build, which reads as a broken feature rather than a wrong query. Three copies were three chances to write that. findScrollportAncestor walks up to the nearest ancestor that actually scrolls, crossing shadow boundaries on the way — parentElement stops at a root, so a plugin in a custom element would otherwise find no scrollport and conclude the page does not scroll. findNodeInTree and buildFlatLegendMatchExpr join useLegendMapBridge, which already holds four functions from the same seam. They sit at module level rather than inside the factory because they close over nothing: a pure function hidden in a closure can only be reached by constructing the whole bridge. Call sites keep their old names through a one-line local binding, so no call changed and the diff is the declaration plus an import. Rebuild-and-compare, all five affected bundles, before -> after: regions-dashboard 1,098,469 -> 1,098,479 +10 country-posters 1,484,053 -> 1,484,028 -25 doxa-research-map 1,354,051 -> 1,354,061 +10 dashboard-plus-grid 915,660 -> 915,644 -16 big-picture 366,907 -> 366,719 -188 net -209, and country-posters gains 25 bytes against the 1.5 MB ceiling. Every behaviour-bearing token counted, not set-compared, and unchanged in all three map bundles: regions-fill, peoplePraying, engagementStatus, adoptionStatus, overflowY, scrollHeight, getRootNode, _flat_filter, religion. Owners cleared their own files first: regions for regions-list.vue, upg100 for countries-map.vue. check:selection-kinds, check:legend-matches-source, check:legend-data-contract, check:packet-variants, check:no-dead-library and check:no-cross-plugin-imports all pass.
28 lines
1.6 KiB
JavaScript
28 lines
1.6 KiB
JavaScript
/**
|
|
* hostElement — the custom element a component is mounted inside.
|
|
*
|
|
* ★ WHY THIS IS A LIBRARY FUNCTION AND NOT FIVE LINES IN EACH PLUGIN. Every plugin ships through
|
|
* Vue's `defineCustomElement`, which mounts into a SHADOW ROOT, and a composite nests two of them
|
|
* (`<research-map-plus-grid>` contains `<doxa-research-map>`). So the element a component can see
|
|
* with `$el` is inside the shadow tree, and the thing a host page can style, measure or address is
|
|
* the ROOT NODE'S HOST — one hop out. Code that forgets the hop silently operates on the wrong node.
|
|
*
|
|
* That mistake is the one this codebase makes most often, and it is invisible when you make it: a
|
|
* `document.querySelector` for a node inside a shadow root returns null on a perfectly working
|
|
* build, which reads as "the feature is broken" rather than "the query was wrong". Three identical
|
|
* copies of this logic were three chances to write the wrong version of it.
|
|
*
|
|
* ⚠ `getRootNode()` is guarded because it does not exist on every node type this can be handed
|
|
* (a detached vnode el, a text node), and because a CLOSED shadow root returns no host — in both
|
|
* cases the element itself is the honest answer, not a throw.
|
|
*
|
|
* @param {object} inst a Vue internal instance (`getCurrentInstance()`)
|
|
* @returns {Element|null|undefined} the custom element host, or the component's own root element
|
|
*/
|
|
export function hostElement(inst) {
|
|
const root = inst?.vnode?.el || inst?.proxy?.$el
|
|
try { const rn = root && root.getRootNode && root.getRootNode(); if (rn && rn.host) return rn.host } catch (_) {}
|
|
return root
|
|
}
|
|
|
|
export default hostElement
|