We tend to think of components as things that belong to a framework. After all, React has components, Vue has components, Angular has components…it's just how we've always used them.
Because of that, people tend to refer to Lit and FAST Element as frameworks, but they’re not. They’re libraries, and that’s an important distinction.
If you want a React component to work, you have to use it with React. If you want a Vue component to work, you have to use it with Vue. If you want an Angular component to work…well, you get the point.
With web components, the platform is the framework.
Naturally, a follow up question is "why do you need a library then?" The truth is that we don’t. We can create web components without a library. Here's a counter component written in pure JavaScript.
class MyCounter extends HTMLElement {
static get observedAttributes() {
return ['count'];
}
constructor() {
super();
this.state = {
count: 0
};
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<button type="button">
Count:
<span class="count">${this.state.count}</span>
</button>
`;
this.handleClick = this.handleClick.bind(this);
}
connectedCallback() {
this.shadowRoot.querySelector('button').addEventListener('click', this.handleClick);
}
disconnectedCallback() {
this.shadowRoot.querySelector('button').removeEventListener('click', this.handleClick);
}
get count() {
return this.state.count;
}
set count(newCount) {
this.state.count = newCount;
this.update();
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'count') {
this.state.count = Number(newValue);
this.update();
}
}
handleClick() {
this.count = this.count + 1;
}
update() {
this.shadowRoot.querySelector('.count').textContent = this.state.count;
}
}
customElements.define('my-counter', MyCounter);
We choose to use libraries to improve the the component authoring experience and abstract messy boilerplate into efficient, reusable modules. Here's a functionally equivalent counter built with Lit.
import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('my-counter')
class MyCounter extends LitElement {
@property({ type: Number }) count = 0;
handleClick() {
this.count++;
}
render() {
return html`
<button type="button" @click=${this.handleClick}>
Count: ${this.count}
</button>
`;
}
}
Sure, we can bake features such as declarative rendering and reactivity into each and every component, but that’s not DRY. It would convolute the code and make our components larger and more difficult to maintain. That’s not what I want and it's probably not what my users want.
Alternatively, we could build those features ourselves and split them off into reusable modules — but that's just reinventing the wheel, isn't it?
When you think of it that way, using a library to build web components makes a lot of sense.
Aside: It’s been said that developer experience is the only benefit to using a library. While it’s true that benefits to the end user are marginalized with one-off components, it's worth noting that APIs such as those offered by Lit and FAST Element lead to less bugs due to reduced complexity and less code in the component itself. Consider the counter examples above. Which one is easier to maintain?
Top comments (27)
For the fun of it, HTML + DOM Level 0!
"What could possibly go wrong?"
Or a single source of truth:
Code golf is fun.
I feel hacky. But hey, the counter state isn't a global anymore.
I suspect someone has used something like this with their site somewhere and felt like being super clever.
I am currently working on a svelte application and more than once gotten to a point where I feel like I have to fight the framework to represent logic in a readable way (i.e. without having dozens of variables for everything)
I'd much rather write vanilla JS
You are battling with svelte? Have you seen angular?
Luckily I only know svelte and vanilla JS; although I mostly rely on a few custom helpers to reduce boilerplate when generating HTML from JS because
document.createElement
is just way too cumbersome. These days I use plain JS to prototype things even if I will later have to translate it into svelte because that's what we use at work.I have done a fair bit with Svelte. Its the fastest REPL around and I like the "Component" paradigm in the .svelte files.
Until my code got too complex.
Svelte (or any Framework) wants you to stick to the paradigm.. its called a Framework for a reason. If you have too much HTML/CSS/JS knowledge you will come to a point where you have to make a decision... stick to the paradigm... or ditch the tool.
I ditched Svelte (apart from the occaisonal quick prototype) because I did not want to be framed.
I ran into problems with Svelte when I wanted to "hotwire" CSS styling and got into a fight with "Svelte"
svelte.dev/repl/382ed83cd7954f6088...
It was a choice... stick to native technology that will work for the next 25 JS years or learn to really use a Framework that will most likely not be around in 25 years time ( oh, I have seen so many technologies come and go since I started in 1994)
Sounds similar to my experience; Svelte wants you to stick to the paradigm, but the paradigm falls apart for more complex applications.
EDIT: I just realised that message wasn't meant in response to me; I answered right from my notifications and had just assumed this was on another comment thread.
In other words, continue reading with the fact in mind that I'm a dumbass; but the points are still valid if applied to that other conversation (and, to some extent, probably to this one too)
I think you're still completely missing my point. I cannot give you a simple example of when complexity becomes too big for svelte to handle nicely. That's the whole point; svelte looks good in almost all simple examples, because there isn't much state to keep around, nor things to do with it. The problem is that the approach svelte takes just doesn't scale well because it is too fine-tuned to hide the complexities of a hello-world example.
And once again, you are totally misunderstanding the
querySelector
point. It's not about callingquerySelector
; it's about having a shared API. You can call any method of a normal DOM element on your custom elements, because they are just normal DOM elements. The same doesn't work svelte components, which are a separate object with a horrible API that manages the real DOM nodes.You also cannot define methods on svelte components, which severely limits the ways you can interact with them. So from the outside, svelte components are ridiculously unwieldy compared to custom elements.
And from the inside, their paradigm for state management just doesn't scale nearly as well as plain javascript. The syntax is awkward and just similar enough to plain JS/HTML that you might get confused (just today a coworker tried doing
${}
in the HTML section of a svelte component right after making a similar change in the actual JS part).But the worst part, by far, is that there is zero re-usability without creating a new component. It's all or nothing. This might work for generic things, say, a component to turn an array into an ordered list; but sometimes these sub-components are simply too tightly linked to their surrounding component to be usable anywhere else, and at that point you're stuck with the boilerplate and performance overhead of a new full component.
And it's not even less boilerplate that you're writing; I've started switching to vanilla JS for prototyping because, aside from having the flexibility to write uglier code as I go, I also just have to write less of it, and on top of that it doesn't even need to be compiled.
I hope that was enough of an explanation. Looking forward to more nagging about how
querySelector
is bad.You know you could use the
bind
directive in svelte to get the element itself right?Yes, and you can even do stuff like
bind:self={array[index]}
to at least save on variables a bit but that doesn't really get you all that far either. In the end it still feels like writing lots of globals, except they're at least scoped to one component.At this point I don't know whether you are talking about the DOM or the JS itself. Svelte gets as close to JS and I doubt that you are trying to build a frontend or custom elements to be used in another framework.
My whole point is that the problems start to show in more complex examples, and no, I can not send you huge chunks of an internal project from my workplace.
In regards to
querySelector
, you seem to be missing the point; this isn't about any specific function, it's about custom elements aka. web component sharing most of their API with builtin HTML elements, as well as having methods in the first place.Me searching for where LUKESHIRU said "please send me huge chunks of an internal project"
I have added a 3rd term to my "Web Component" Lingo: BaseClass
import { LitElement, html } from 'lit';
html is a library function, LitElement is the BaseClass
Bitten by a Big Vendor 1.0 to 2.0 "upgrade" once; I have written my own BaseClass; I don't ever want to experience that again.
When making a comparison between Web Component Libraries and Native code; then at least make it a fair comparison:
I do like the code-golf examples in this thread; I am sticking with readable Web Component code for the comparison:
this.onclick
allows a Component user to overload the click event.bind
stuffconnectedCallback
, if you only work with shadowDOM (or already parsed global DOM)disconnectedCallback
, any handlers attached to elements inside our Custom Element will be garbage collectedthis.shadowRoot.querySelector("b").textContent
repetition.<count>
is an UnknownElement, perfectly valid to use for better semantic code"state" is just a location in memory
Like everyone who has ever done Assembly programming knows
Using the DOM (just a location in memory) as "state" not only shortens the code,
but also make
count
updates by changing the DOM directly possible.Something that never works when using Frameworks or Libraries.
You can do a simple experiment with your 5 year old daughter. All you need is a pen and paper.
That's not entirely true. You only need to declare properties that you want state updates on. If you want to listen on everything (because screw performance, I guess) you can write a 1-liner helper function with a
MutationObserver
and use it anywhere in your application.Hi guys!
You may want to check also this abstraction that i have created based on LitHtml!
github.com/r-html/rhtml/tree/maste...
Functional composition using Partial application combined with typescript decorators :)
You can take a look at this starter github.com/rxdi/starter-client-sid...
Regards!
Those are great examples if you don't care about lock-in or interoperability. I prefer to use my components in more than one framework — or even without one!
React is a library, not a framework - just cause it seems you're implying the contrary. Frameworks make use of the library (Next, Redwood, etc.) ❤️
They may call it a library, but it serves as a framework for components just like Angular and Vue.
So the word you're looking for is "paradigm"?
To literally answer your question: You probably can
But no, I actually did answer your question. Most applications that do anything interesting quickly start to look worse when written in svelte than if they were just built with web components.
To illustrate, here's just one example of an advantage:
HTMLElement
(or, more likely, any boilerplate-reducing wrapper you would use) is is a class, and on top of it one that shares the API of any other HTML element. This means you can use methods likequerySelector
& co. but you can also implement methods around the specific purpose of the component. Svelte, by contrast, only lets you trigger behaviour by changing state, or awkwardly passing closures around. Neither leads to readable code.StencilJS is a nice alternative
I wrote some thoughts regarding my experience with Stencil and why I prefer Lit now. abeautifulsite.net/posts/moving-fr...
mostly anything that's more complex than a hello world
The a11y issues with pseudo elements might be outdated information, but hard to know for sure since it still is way too often random individuals doing the research.