XPaths in the modern age
Nikhil Verma
Posted on June 1, 2023
XPaths are selectors you can use to query the elements in your document (HTML, XML etc). They are similar but complex and more featureful versions of CSS query selectors many are used to.
This is an example XPath which can find the Google Search button by starting from the search input. It's a roundabout way of doing this but it shows the powers of XPath over normal query selectors which can't do this.
$x(`//textarea/ancestor::form//input[@value="Google Search"]`);
XPath with Web components
Since XPath is a pretty old specification (created in 1999 and last updated in 2016). It doesn't specify how to operate with Web components and shadow DOM.
So if you want to write an XPath which selects an element inside the shadow DOM of a component you simply can't. The only solution is to find the component with the XPath. Then use the shadowRoot
node to then further drill down into the component. For nested components it quickly becomes impractical.
To be fair this problem with shadow DOM is also present in querySelectors which don't work with it either. Lit has it's own section dedicated to using custom @query
decorators to find elements.
XPath with Vue 3
There is a very specific and peculiar problem that you face when using XPaths with Vue 3, which took me a long time to debug and find out.
Vue 3 when rendering child slots appends what's called "anchor" nodes to help it optimise it's updates. For example if you have a component like this:
<Text>
Hello World!
</Text>
And you render it in Vue 3 like this.
<span>
<slot />
<span>
What Vue 3 will do is output the following DOM structure
<span>
""
"Hello World!"
""
</span>
Here ""
is an empty text node. This trips up the contains(text())
API of XPath. So if you were relying on your elements containing a specific text you won't be able to do that anymore.
Here is a Github issue with examples. vue/core/issues/8444
A workaround
To solve both of the problems above, I had to ponyfill XPath in our application.
There was an excellent xpath
polyfill library which I forked and modified to add support for both Shadow DOM and Vue 3 text nodes.
This makes it "non spec" but it solves our needs and doesn't impact our development velocity.
It's published here xpath-next
import { parse } from 'xpath-next';
const expression = "//span";
const contextNode = document.body;
const nodes = parse(expression).select({ node: contextNode, isHtml: true });
My key takeaway from this experience is reliable technologies stop being so because the ecosystem moves on and makes them incompatible.
One could argue that Vue 3 behaviour should be fixed. But the fact that Web components don't work transparently with XPaths makes it hard to justify using it in it's original specification.
Posted on June 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.