DEV Community

Nikhil Verma
Nikhil Verma

Posted on

XPaths in the modern age

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"]`);
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

And you render it in Vue 3 like this.

<span>
    <slot />
<span>
Enter fullscreen mode Exit fullscreen mode

What Vue 3 will do is output the following DOM structure

<span>
    ""
    "Hello World!"
    ""
</span>
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)