Lit and Rimmel are some relatively similar libraries as they both make use of tagged templates.
There are some key differences which we're going to explore here
Architecture and Programming Paradigm
Lit is a simple, lightweight library for building web components using JavaScript or TypeScript. It uses a reactive update system where changes in data trigger updates to the DOM. It leverages JavaScript template literals for HTML templating, encapsulated in its html tagged templates.
Rimmel emphasizes functional/reactive programming approach, by leveraging observable streams. This library focuses on using RxJS's Observables, Subjects, and BehaviorSubjects to create and manage UI state and behaviors, which can lead to more declarative and predictable UI code.
Templating and Data Binding
Lit offers a straightforward templating syntax (html
and css
tagged templates) and employs directives for advanced functionality like repeating templates, guarding updates, and managing attributes. It has built-in reactive data binding.
Rimmel utilises the rml
tagged template for its HTML structure intertwined with dynamic data streams. Rimmel’s approach is highly declarative, using Observables directly within the template to manage data changes, which automatically handles subscriptions and unsubscriptions to avoid memory leaks.
Component Model
Each Lit component is a class that extends HTMLElement, allowing the use of lifecycle callbacks and state management features provided by the web component standards.
Rimmel Components are designed as plain functions, which might appeal to developers looking for a more functional style of programming. This approach can make components easier to test and maintain.
State Management
Lit manages state internally within components or through external libraries. The state handling is less declarative compared to Rimmel but fits well within the object-oriented paradigm.
Rimmel leverages RxJS for state management, making it inherently reactive. This method provides more robust control over state changes through streams, which can be more intuitive for developers familiar with reactive programming.
Community and Ecosystem
Lit is backed by Google and has a larger community and ecosystem. This popularity brings more third-party tools, libraries, and plugins that integrate well with it.
Rimmel, as a newer and more specialized library, might have a smaller community and fewer resources available. However, its tight integration with RxJS could attract developers looking for advanced functional-reactive programming solutions.
Performance
Lit is highly optimised for performance, with efficient update checks and minimal overhead for reactive updates.
Rimmel's performance depends almost entirely on the reactive streams being used, as it only binds them to DOM events and sinks. Proper use can lead to extremely efficient updates.
Use Case Suitability
Both Lit and Rimmel are suitable for a wide range of projects. The former fits more where traditionally-structured OOP approaches are preferred whilst the latter shows it true power when creating complex interactive user interfaces.
Action
Let's compare both with a code example: a little component with two buttons that prints "Hello" when both are clicked.
import { LitElement, html, css } from 'lit';
class HelloButtons extends LitElement {
static properties = {
firstClicked: { type: Boolean },
secondClicked: { type: Boolean },
showMessage: { type: Boolean }
};
constructor() {
super();
this.firstClicked = false;
this.secondClicked = false;
this.showMessage = false;
}
firstButtonClicked() {
this.firstClicked = true;
this.checkBothClicked();
}
secondButtonClicked() {
this.secondClicked = true;
this.checkBothClicked();
}
checkBothClicked() {
if (this.firstClicked && this.secondClicked) {
this.showMessage = true;
}
}
render() {
return html`
<button @click="${this.firstButtonClicked}">Button 1</button>
<button @click="${this.secondButtonClicked}">Button 2</button>
<div>${this.showMessage ? 'Hello' : ''}</div>
`;
}
}
customElements.define('hello-buttons', HelloButtons);
const element = document.createElement('hello-buttons');
document.body.appendChild(element)
Follwing is the Rimmel example, managing state exclusively as RxJS streams. You may notice how shorter did the code become as a result:
import { BehaviorSubject, zip, take } from 'rxjs';
import { rml } from 'rimmel';
function HelloButtons() {
const first = new Subject();
const second = new Subject();
const msg = zip([first, second]).pipe(
map(() => 'Hello'),
take(1)
);
return rml`
<button onclick="${first}">Button 1</button>
<button onclick="${second}">Button 2</button>
<div>${msg}</div>
`;
}
document.body.innerHTML = HelloButtons();
Which programming paradigm do you prefer? Imperative or Functional? Pushing or Pulling data?
Top comments (4)
Problem is many developers discount vanilla JS too soon,
grabbing for "tools" that make work "easier"
But a fool with a tool, is still a fool. Offloading intelligence to a Library or a Compiler
Ask such an "expert" to not use a tool, and they probably fail.
(JSFidlle Result tab loads slow)
Fools and tools you say? Let's see, let's examine the situation a bit more closely.
For a start, we may agree there's a bit of boilerplate in the vanilla code:
class
,extends
,HTMLElement
,constructor
,createElement
(3x),innerHTML
,super()
,attachShadow
,mode: "open"
,append
.So, two things come to my mind now:
Vanilla doesn't come with many architectural and design patterns, so you have to reimplement them every single time and if you do, you've just created another framework.
For a long time I didn't know how to create vanilla web components, so I was a fool. Then I have myself created a JS framework from scratch which makes it easy to create powerful monadic web components, so I'm not a fool, but as I now use this tool every day, I've (happily) forgotten how to create them the vanilla way, so I'm back as the same fool.
Houston, we have a little conundrum here :)
P.S.: an AI reviewed my comment and summarised it egregiously: "is mastery about knowing how to do everything manually, or knowing when to use the right tool?"
What do you say?
You don't drive a Fiat 500 on the F1 track
and you don't drive a F1 race car to the supermarket
No we don't agree.
By what reasoning are you rimmel and rxjs commands not boilerplate?
The point is, many developers grab for tools without mastering the basics.
Make your 2 button example code work with N buttons to really show what them tools do.
But you can perfectly drive a Tesla. You won't turn on ludicrous or plaid mode for the weekly supermarket trip, you can still do your shopping and fit all you buy in the boot, but you can still go in plaid mode whenever you want or feel like it.
If you buy a Fiat 500, instead, you have no plaid mode, you're stuck in Fiat 500 mode forever.
A notable difference here is that a Fiat 500 and an S Plaid have a substantially different price tag.
The good news is that choosing Vanilla JS vs a well-designed UI library, on the other hand, cost the same to start. Any real difference is negligible for practical purposes.
Vanilla JS is stuck in Fiat 500 mode. You can't grow it and scale it. Ultimately, the reason you build webapps is to grow them, not to be stuck with a hello world button...
Ah, ok.... well...
zip()
from Rx is not boilerplate for me, it's the core business logic operation that makes the application behave like that. It waits for both source streams to emit something, and then propagate a single action. It's not redundant, it's not repeated, but unique "core" functionality.class extends HTMLElement
is boring and redundant... I keep facing the option of extending HTMLElement, when I could extend P, DIV, whatever. Do I need to keep thinking about this detail in every web component? Am I not better off by only ever extending HTMLElement? Where did the DRY principle go? Is that dead?attachShadow()
? Why? What else should I do with a web component's shadow root, if not attaching it? So it's boilerplate, it's not my core business logic. I have to repeat it without adding real value.Calling
super()
every time? It's not even optional, I believe, so it's something you just have to do and what do you get in exchange? Nothing. How is that not boilerplate?Could go on forever... could write webassembly instead, so we could specify exactly which registers get loaded in which order, etc... but... seriously?
Agreeing is optional from here. I won't try to convince you and will respect your views, as the ones above are just mine :)