How we use web components
github.blog
github.blog
Two caveats with webcomponents however. The first is Safari will be the bane of your existence. You should dev in safari if you can. If it works there it will work in all other browsers. The second is stay away from form elements, for now. Or if you do, use an encapsulating component that inserts a slotted input into the light dom. The form registration api isn’t fully supported yet except by chrome, and even then it’s a bit rough around the edges.
```
class DeleteButton(Component): tag = 'delete-button'
class HTMLElement:
def connectedCallback(self):
this.addEventListener('click', this.delete.bind(this))
async def delete(self, event):
csrf = document.querySelector('[name="csrfmiddlewaretoken"]')
await fetch(this.attributes['delete-url'].value, {
method: 'delete',
headers: {'X-CSRFTOKEN': csrf.value},
redirect: 'manual',
}).then(lambda response: print(response))
```Speaking of which, Rails ViewComponent looks a lot like Python Ryzom, except the later optionnaly supports data-binding over websockets.
But writing JS in Python is extremely satisfying if you already like writing Python more than you like writing JS ;)
i assume any linter for this code will one day become sentient.
edit: Fixed a link and grammar.
I sort of wish Rails is more of Github's Rails rather than Basecamp's rails.
an example following is an event listener in a JS controller.
openAddJob(event) {
this.openDialogAddClasses();
this.modalPanelTarget.innerHTML = MODAL_SPINNER_HTML;
$.ajax({
type: "GET",
url: `/job_items/new_form`,
success: function (repsonse) {
this.modalPanelTarget.innerHTML = repsonse;
}.bind(this),
error: function (repsonse) {},
});
}
Currently there is a lot of scope for DRY a lot of things in the app but will be doing that once I have more feature tests coverage.It's so nice to be able to pick things I need from different frameworks, like Ionic, vaadin, Shoelace, etc... and they all just work together.
I tend to implement vanilla web components, I enjoy managing the life cycle and don't find it particularly burdensome. Of course. Lit-html is a big reason why that's easy to do. I use it for rendering. I generally avoid using the shadow dom unless I have a really good reason.
My components all self-register, and are pretty encapsulated. They all maintain internal state, but for more global state I use a library I wrote called ApplicationState [1].
It's hard to imagine going back to the framework churn after experiencing the freedom of Web Components.
It really feels like we've finally realized the promise of composability, reusability, encapsulation and performance with Web Components.
There are also some other goodies in there like aliasing that I enjoy.
Absolutely great for the developer that wants to ship pre-built components that cannot be affected by the CSS of the site they're used in; absolutely useless when you want to make re-usable components for your own app, where you very much want slot content to be styled using the CSS you already have.
I don't suppose you know if there's any performance impact there—e.g. even if it's not downloading it, is there overhead from processing the file once per component?
Another handy thing to know is that exportparts reexports parts from a nested web component, and CSS --custom-properties will cascade down into the shadow dom
Something goes wrong in CI and you'd like to dump the DOM to figure out why? Too bad, the entire page is sitting inside a Shadow DOM, and you get to see an empty body tag and a timeout waiting for an element to appear.
Want to click that button that's nested 5 Shadow DOMs deep? Have fun creating a monster JS query to manually trawl though the Shadow DOMs to click it, because Selenium has no idea how to access the button. And that's if you're lucky and the Shadow DOM is marked "open". If it's marked closed, you can go sit on your thumb.
It's like someone looked at iframes and thought "hey, that's a great idea!" and never stopped to think why they were considered bad practice already 20 years ago.
You can use web components without the shadow DOM (I've seen them being called "custom elements" then). That's what I do and I'm happy with the results.
Shameless plug, but we're building a no-code testing framework (https://reflect.run) that does all of this for you automatically. Works great with Shadow DOM (both open and closed) as well as more esoteric things - for example did you know Salesforce's Lightning framework overrides how Shadow DOM works? :D
document.querySelector("#foo").click()
You have to write something like this: document.querySelector("#nested-1").shadowRoot.querySelector("#nested-2").shadowRoot.querySelector("#foo").click()
It's not just the length of the query (which can get stupidly long), it's that you have to know and care about the overall structure of the document and where the button sits in it. I just want to click the damn button. It has a nice, easy-to-find ID attached to it, why does that have to be so hard?Also, it's not a Selenium bug that you can't dump the entire DOM when Shadow DOM gets involved. That's by design; Selenium just allows you to call `innerHTML` or similar, and if the <body> tag is using Shadow DOM because someone else thought it was a great idea to make the whole page a Web Component then you're SOL and you'll just get `<body></body>`. Have fun debugging that.
The worst part about all this bad design is that it wasn't even necessary. Are you worried about a bad CSS rule from some fancy component causing havoc with the rest of your page? There should exist something as simple as `<div style-link="foo.css">...</div>`. Have those rules scoped to that div element and you're done, problem solved. Worried about some component's JS causing havoc? Same thing, `<div script-link="foo.js">...</div>`. All JS is automatically scoped to that div element. That would have been far better than the brain-dead approach of Shadow DOM.
The big thing holding back webcompinents at this point is a data sharing strategy, and I think we could get away with actually using components to store and manage data:
https://www.vadosware.io/post/sade-pattern-services-as-dom-e...
The github repo (which also happens to contain how to write a web component in lit-element, slim.js, tonic, vue, svelte):
Most likely explanation: It simply didn't exist yet when they chose.
Also, lit-element requires elements use its own base class while Catalyst doesn't.
The other thing I was worried about was that it was planned to after writing this web component lib, to wrap these components in React. Does anyone have any experience or insights into a React wrapped web component lib?
class App extends React.Component {
render() {
return (
<my-component custom-attribute={value}></my-component>
)
}
}
In your webcomponent just make sure you listen to changes to that attribute like so: static get observedAttributes() {
return ['custom-attribute']
}
Then you can decide how the component changes whenever that attribute is updated by using the `attributeChangedCallback` function. Alternatively, use a base element that incorporates a render() function which will automatically update everything in the shadowdom.Main difference is it becomes much harder to pass complex data structures. Passing strings is easy, but passing an array of data isn't feasible with this model.
Unfortunately it seems facebook isn't prioritizing fixing issues like this.
I'd suggest using preact if you want a framework that's more compatible with web components but gives you the React experience.
So you can't take existing React libraries and 1:1 map them to web components. That approach only works for 'leaf' components that accept no children.
What am I missing, what's the elevator pitch for web-components and shadow DOM?
Scoped styles seems to be something that's pushed with shadow DOM a lot, but isn't <style> in the page body illegal HTML (despite yes, it works)?
Still having a tough time seeing the benefits of web components.
To me React's value is JSX and ReactDOM. I actually use JSX+ReactDOM without the rest of React, using basic classes. Works great.
I have personally not built anything for the web using components, so I don't know how much they satisfy that promise in reality. But that's the promise.
WC + Shadow DOM are great for using 'new' components in, say, a legacy web application that has leaky style rules (e.g., the main app has style selectors like `button`), which would bleed into your new component.
There's none except the oft-repeated "use the platform!" (as if other frameworks and libraries use something else, and not the platform).
Currently web components work, if:
- you have a large distributed team, and
- you need to have consistent elements and styling across many properties, and
- your components can be expressed as "leaf elements". That is, view-only elements. No forms, no state, only presenting some data.
Beyond that the elevator pitch is "for the past 10 years, and counting, we've been bravely trying to fix the ever-growing list of problems that web components introduced by simply being. And we're solving that by throwing more and more javascript at the problem".
A very non-exhaustive list: https://twitter.com/rich_harris/status/1198332398561353728?l...
And things like participation in forms? It's "solved" by adding Javascript: https://web.dev/more-capable-form-controls/
Which is ironic, given that the original pitch was "we're piling more and more into Javascript, and we shouldn't" https://fronteers.nl/congres/2011/sessions/web-components-an...:
--- start quote ---
I think we’re stuck today in a little bit of a rut of extensibility. We wind up leaning on JavaScript to get things, because it is the Turing complete language in our environment. It is the only thing that can give us an answer when CSS and HTML fail us. So we wind up piling ourselves into the JavaScript boat. We keep piling into the JavaScript boat.
Bruce yesterday brought up the great example of an empty body tag, and sort of this pathological case of piling yourself into the JavaScript boat, where you wind up then having to go recreate all of the stuff that the browser was going to do more or less for you if you’d sent markup down the wire in order to get back to the same value that was going to be provided to you if you’d done it in markup. But you did it for a good reason. Gmail has an empty body tag, not because it’s stupid. Gmail does that because that’s how you can actually deliver the functionality of Gmail in a way that’s both meaningful and reliable and maintainable. You wind up putting all of your bets, all of your eggs, into the JavaScript basket.
--- end quote ---
You can do some client-side state management with Stimulus though.
But why actually have pages - I actually think this is a good differentiator of if your site is naturally an SPA or not - if you really need pages, perhaps because people need to be able to bookmark a specific part of your site that is more important to them than other parts - your site as a whole might not actually need to be an SPA although it might have parts of it that function as SPAs.
I can offer some comparisons with Vue which I have more experience with. I'd describe myself primarily as a backend developer with some frontend experience.
In both cases I found it much more productive than building the traditional SPA. Building an SPA feels very much like building two applications: your backend API and the frontend app. This might make sense where you have a team of specialists, or multiple frontends (i.e. IOS+Android+web). If you're a sole developer it's important to acquire "superpowers" that give you a productivity boost to compensate for your limited time and expertise, and Hotwire and htmx feel very much like superpowers. Combined with a compatible JS library like Stimulus or Alpine I didn't feel like I hit too many walls compared to Vue. This made it easier to focus on the UI and backend logic and kept maintenance to a minimum.
That said, the more interactive parts did require some lateral thinking where Vue would have likely been easier in terms of the mental model, for example keeping an audio player open during page navigations and maintaining state without full page refreshes (i.e. to prevent the player restarting). Another thing I missed where the sheer ease of building and re-using components in Vue, and Django templates and tags feel quite clunky in comparison (some other frameworks like Laravel have perhaps a better template component model). Overall though it was so much easier to build a traditional web application compared to the complexity of an SPA.
Comparing Hotwire to htmx: Hotwire required more server work when doing atomic changes (HTML fragments returned from AJAX requests). There's an existing Rails gem which does most of this, I had to write my own Django package. There's an expectation that it should be used with websockets, but really that's optional. In most cases though Turbo Drive works pretty much like Turbolinks: once you install it, it will provide SPA-like page transitions for free. Htmx feels a bit less polished, and the learning curve is a bit steeper, but has much more in the way of features and extensions and doesn't require as much rework in the backend. There's an hx-boost feature that works similar to turbo drive but I found it's easier to use htmx' low-level options to manage different sections of the site as appropriate.
Took a minute to figure out how to get React/Vue components to render inside of a web component, but now we're all set.
I was interested in Lit, and am try to bundle that, but it's not working at all, and I really feel like I'm missing something.
Especially their code examples
What's great is that:
- I use preact's htm as a renderer [3], which is JSX but as template strings.
- The API is like (p)react but a bit more generalized. I like it.
- The web component concept is great. Especially for mixing server-side rendering and JavaScript-powered components.
That last one IMO is web components killer feature. I can now wrote a mini component and then I tugg it in with the other 99% of my page that is rendered server side.
It means, I'm able to serve my users quickly. I have SEO'd everything too. Cool!
-1: https://github.com/TimDaub/web3-sign-msg
(For comparison, in React you can pass the state down as props from a common ancestor, and in Clojure frameworks all app state is in a giant object referenced by components as needed.)
How do you pass the state between an input button and a label?
Whatever technique you choose to do that (including react!) you can use it with web components.
Some frameworks like react though also solve the componentization problem, so since web components are not necessary there (and weren't ready yet when react first came out), they tend to not get used to react and friends, even if they would technically work.
An example of this would be a radio-group and radio-element. When an element is clicked it fires a "clicked" event to the parent radio-group. The radio-group then toggles on "selected" for the clicked element, and removes the previously selected element's "selected" attribute.
I recently took the time to explore a new idea I haven’t seen explored before —- services as DOM elements:
https://www.vadosware.io/post/sade-pattern-services-as-dom-e...
Straight to the repo:
https://gitlab.com/mrman/services-as-dom-elements
The idea is simple, use references to get at other DOM elements that happen to act like whatever kind of store you need
I doubt the shadow dom is particularly crawler friendly.
Also, they aren't great at SSR though some frameworks try to fix that.
It would also depend a lot on how you were using/designing them.
If you were using slots and putting content inside of a custom tag, and just using that tag for display concerns, SEO would be just fine, though you may lose some semantic markup points.
However, one of the more exciting projects in the web components space (lit.dev) now also supports proper SSR as well which is a very new thing in the world of web components. They are trying to build it in such a way that any other library can take advantage of through a common interface.
In fact there are some kind of early stage talks happening over here https://github.com/webcomponents/community-protocols where a bunch of companies like Google, Adobe, ING and others are trying to develop some open protocols on a whole bunch of topics to improve interoperability between various libraries so that no one has to buy in 100% to any one setup.
From a quick skim of their docs, it looks like AMP still uses web components.
The nice part about web components is that the users of a library don't have to care about how it's implemented, so switching to preact is a transparent change.
But it is not fully integrated with react, you have to do some work for interaction: https://www.sitepen.com/blog/wrapping-web-components-with-re...
That link is a few years old though I think the principle is the same.
Ugh, every time I see such PR-ese language, I want to stop reading any further