You basically write all the normal HTML and CSS so it's loaded in right away. But then you also make use of the advantages that defining a custom element gives you.
I've started moving to this technique from my earlier technique of putting a data-module on wrapping div components, where I manually had to deal with a bunch of stuff which HTML Web Components just do for you for free. Super handy!
Querying elements also becomes more complex.
i'm sure if you webpack the crap out of it bundling hundreds of dependencies into the same parse, it'll perform poorly, but well, stop doing that.
You tackle it by doing two things:
1) use a bundler (rollup or webpack) if your code is big so it can be chunked and loaded quickly
2) have a loading spinner by default
Some developers have a dogmatic zeal against loading spinners, but users are very accustomed to them.
In fact, it's possible for websites to load _too_ fast, seeming unnatural to users (who at this point in time are very much expecting the flow to be request->spinner->load).
Specifically, Shoelace uses Shadow DOM and adoptedStyleSheets.
For Shadow DOM, there’s Declarative Shadow DOM, a way of serialising a Shadow DOM, which is extra work to support, but possible; it’s currently supported in Chromium and Safari, and last month Firefox landed an experimental implementation.
But the key feature and problem of Shadow DOM (in my opinion, more problem than feature) is how it isolates styles. You need to add the stylesheet to every shadow root, via a <style> or <link rel=stylesheet> element (and hope the browser is clever enough to deduplicate, which it should be in the latter case but I don’t think it will be in the former), or via adoptedStyleSheets, which is generally more efficient and allows shared mutation after construction (unlike the other approaches). Shoelace uses adoptedStyleSheets. Trouble is, that doesn’t work with Declarative Shadow DOM.