There has been a lot of heated discussion lately in the web community about component based development. One side argued that Web Components are a standard we should follow whereas others complained that they still lag behind in what frameworks offer. Fact is, that the web was originally meant to be a document based environment with no encapsulation. CSS, for example, shines when it can cascade down lots of levels in the document and get applied to tons of documents in a project. Both Web Components and Framework solutions are not easy to get your head around when you get started. Today we have a guest post by Frank Thürigen who has been working on a different approach for some decades and now cleaned that one up and documented it.
This is a call to action for you to take a look at his approach and give feedback. Without further ado, over to you Frank.
Micro Components
The DOM and Javascript are a reliable workhorse. They work, but they are not exactly a race horse. Nor can you easily work with them. You can have dynamic content on a page, even single page applications (SPA), but the ways to get there are clumsy at best.
This doesn't even include the workings of existing frameworks. Those offer a published interface to otherwise concealed internal functionality. Using them confines you into an ecosystem and tools chain you will hardly ever escape from. Most of all, existing frameworks hardly interact with each other, and often you cannot use parts of their functionality without digesting all of it.
It is very hard to modularize Javascript functionality in an easy and understandable way. Micro Components are an attempt to do that.
This is a longer read, find a comfy seat and some spare time. Forget about your framework for a moment and take a step back to see the broader perspective. You are in for a ride.
If you want to skim over this, just read the bold parts. 1-2 minutes will do, and you should get the gist.
Why Micro Components?
- Javascript programming once was easy. Nowadays - especially when using frameworks - it is a pain to even get accustomed to it and coding IMHO feels like jail work.
I want to make Javascript programming intuitive and fun again, and most of all cooperative.
- As for companies, I want to ensure that code is SOLID:
- Interoperable between teams and projects in any framework (DIP)
- Safe up to the level of paranoia if desired.
- Exchangeable without side effects, as long as the interface contract is met. (ISP, DIP)
- Manageable via repositories
- Remote and Offshoring enabled without even sharing your own codebase
- Scalable by design
- Modular so I can split data-driven business logic from UI coding. (SRP, OCP)
SOLID principles in brackets. LSP doesn’t apply, since MCs are composed not inherited.
How do Micro Components work then?
Micro Components attach Javascript functionality to the DOM in an organised way. Also from your own Micro Component you will be able to traverse the DOM for other MCs and call functionality on them. Micro Components are an independent functionality and framework agnostic.
Micro Components consist of a single Javascript class MC() with 3 static methods and 7 private methods. The code weighs in at 2.5 kB. Your own code specific micro component extends from this class. The static methods MC.add() and MC.remove() handle attaching and detaching your micro components to a DOM element.
- MC.add() adds a ._mc property to the DOM node and puts your MC in it. The property is an instance of the MC class where you can add your own Micro Components or those from foreign sources. Also MC.add() adds a “_mc” attribute to the same DOM node, indicating there is at least one Micro Component residing inside the DOM node.
- MC.remove() deletes a MC from the ._mc property. When the last Micro Component is removed from the ._mc property, the _mc attribute vanishes to indicate there is no Micro Component attached to this DOM element.
Imagine you have a button on your page:
<button></button>
You then execute this Javascript code:
const button = document.querySelector('button');
const body = document.body;
// your Micro Component class
class MyMC extends MC {
constructor( target ) {
super(target);
}
doSomething() {
this.target.style.backgroundColor = ‘#00ff00’;
}
}
MC.add( button, ‘myButtonMC’, new MyMC(button) );
MC.add( body, ‘myBodyMC’, new MyMC(body) );
As a result your button now looks like this:
<button _mc></button>
Furthermore you can access your instance and call methods on it.
button
._mc
.myButtonMC
.doSomething();
Also you can access other MCs from inside your MC:
this
.parents(‘myBodyMC’)[0]
.doSomething();
… and likewise from outside:
button
._mc
.parents(‘myBodyMC’)[0]
.doSomething();
Inside your Micro Component code you have access to methods you inherited from the superclass MC:
- this.add() will let you add other private Micro Components to your own one.
- this.remove() will remove a private MC.
- this.parent(search?) will scan the tree towards <body> for the closest DOM node with a _mc attribute, and returns an array of all MCs there.
- this.parents(search?) does likewise, but scans for all DOM nodes containing MC up to <body>.
- this.siblings(search?) gives you an array containing your private MC collection.
- this.children(search?) works like .parent(), but into the depth of the DOM tree.
- this.descendants(search?) works like .parents(), but into the depth of the DOM tree.
If the optional search parameter is specified, only the requested element will be in the array. Or the array will be empty to indicate that no match was found.
And that is about it! Except that you can use the MC.debug static boolean to indicate you want to inspect your own Micro Component at runtime, that is in the browser debugger.
Maybe you wondered about the word private in the text above. Yes, MCs can contain any private objects, literally anything. That also implies other MCs recursively.You use them like a modules collection on each DOM node.
Micro Components are a DIY approach, supporting DRY, KISS, and SOLID.
Here is an analogy: In the time it takes to fire up a big V8 truck engine you hop into your go kart and drive 10 circles around that truck. 10 of your go karts lift the same load as your truck, in a reliable and reusable manner. If one of your go karts fails, replace it. If you need only 3, use only 3. Produce your own specialised truck out of 3 go karts. The go cart can even pull the big iron to make it faster, or carry a part of its load. And it drives on any terrain: road, race track, dirt track, you name it.
This looks almost too simple…
That is what small solutions do. They are just like a Swiss army knife, minimalistic and simple. But they can solve big problems.
You could have coded it yourself, given the specification for this class. You didn’t, because you didn’t go through those 20 years of problem drilling, that led to the specification of this. Noting all the reasoning down would exceed the extent of this already long article by far.
How did we get here (skip this if you're not into history)
You may wonder how something as simple as this took 20 years. Well, I spent a couple of days on rewriting this code, but close to 20 years refining approaches that didn’t work out in the end or were outdated at some time. The original framework twoBirds started 2004 with Web Components, Observables and On-Demand requirement loading. It underwent 9 complete rewrites over time. The first 2 years saw me stuck with mostly broken code, trial-and-error cross-browser coding and no debugger in sight. In 2006 it matured to v2 and had its first commercial SPA implementation. In 2007 it was made public on Ajaxian.com. That was far ahead of the crowd then and mostly disregarded.
This framework will now be dissolved into its functional entities, to serve as independent and standalone reference MC implementations that you can import everywhere.
How do Micro Components differ to the current state of Web Components or Virtual DOM solutions?
- Web Components
Native Web Components are inherited from the HTMLElement class. Whatever functionality you want in a native WC, you need to code it there. Multiple inheritance is forbidden since Web Components must inherit from HTMLElement. So native Web Components are just tailor made DOM nodes and they are monolithic.
Framework Web Components are usually just the framework's idea of how to modularise code. They don’t divide their sub-functionalities for further modularisation. In some cases you can't even turn them into native components and distribute them separately. Also in some cases they make the DOM unreadable, like 20 levels of DIV elements.
If a modern native web component uses inner Micro Components you can extend your functionality to your liking by adding more private MCs. The outer Web Component class only handles WC specific functionality, like onConnect, onDisconnect, onAttach, onAttributeChange, so readability and standard web compliance is maximised. These are wired to the inner Micro Components as you like. This way you can compose complex functionality from smaller encapsulated and reusable modules.
MCs also work with normal DOM nodes (div, span).
- As to Virtual DOM: there is no need for that. There already is a DOM, it is as easy as that.
How do micro components work with frameworks?
Micro Components work in any framework by design. In fact they can help by leveraging their inherent shortcomings.
Once you developed a micro component, it works standalone, and you can pack it into a Javascript file that you use in any framework. They blend in seamlessly.
Can AI generate micro components for me?
Micro Components are so small and precise, you can literally ask an AI to generate code based on your description and it will reliably generate working code and will tell you how to use it in your framework. That is because the AI does not need to consider your surrounding code.
I discussed micro components vs. frameworks with ChatGPT, and it got very good at it. Here is a snippet. This is about how to solve the prop drilling problem in React with a micro component.
Yes I know React has a solution for that. Frameworks need to offer solutions for shortcomings that they themselves created, because solving the problem from the ground up would be a breaking change. You may already have seen insanely fast version change cycles in frameworks, so the problem is inherent by nature. Micro Components don't have that problem. The sum of MCs in your application constitutes your framework, and these may come from your preferred sources, and they all interact.
What about State Libraries, JSX and Lifecycle hooks?
- Basically you don’t need external state libraries - at least for small functionality.
Once you understand Javascript’s native getters/setters and the Proxy() class you have all it takes to automate the DOM in a data-driven way. Javascript has all it takes.
Complex state handling on the other hand will be the first of the upcoming reference implementations derived from the outdated twobirds framework based on its convenience observables. These can be hardened to paranoia levels for enterprise use, targeting data-driven business processes.
- JSX makes easy components look easy.
The more it becomes complex, the deeper down the rabbit hole you go. Also, the more abstraction you pack into your DOM templates, like if, loops and the like, the more you deal with 2 coding schemes to do the same thing. Why? Javascript has all it takes.
- Lifecycle hooks
… exist because they represent internal requirements in frameworks. They are a generic coding scheme specific to every framework, not portable. By design you need a way to attach functionality components at the precise right time, also you need to know their inner workings. IMHO that is too much overhead.
Micro Components are independent and asynchronous by design, because you can add them dynamically at runtime, even on-demand load them.
Also the DOM has its own lifecycle methods, so why not use these? Think modern native web components. Think again Javascript has all it takes.
What about performance or memory issues?
The polyfill weighs in at 2,5 kB. Since it doesn’t extend the HTMLElement itself, only those DOM nodes where you actually attach Micro Components, its memory impact is neglect-able. I have attached 23k public and private MCs to the DOM on a 5 year old computer:
It took 27 ms. Try it on your computer and use the debugger to see the results. BTW it contains the source code of the MC() class and the test code in the debugger as readable code. Feel free to copy-paste it in your site in the debugger console and see what happens. It then will simply merge in with your existing code without interfering. Inspect the DOM <div> elements. Functionality inside those MCs weights in at the code size, but only once since it is classes. Data weighs in just like in any other Javascript application.
What about security issues?
Don’t stop reading when you arrive at this sentence: In general Micro Components are the “cowboy” approach: yes I can, so I will do it. “YippieYaYay”, the gate opens, and off you go. Every micro component starts as a prototype.
That may feel like taming a horse in a rodeo arena at first. But once you get the hang of it, you will love it. It is plain old Javascript in the end, no abstractions. Also it is very hard to stun a website with a dysfunctional MC. Try while (true) if you are badly in need of some coffee.
Everything else is also possible, to the extent that Javascript allows for it. But these are secondary thoughts: Functionality first, QA later. Of course in a company or bigger corporation you would implement a CMM process for code quality issues, so you can rate individual MC code quality based on your QA requirements and have a solid interface contract for outsourced functionality.
Fun fact for aspiring cowboys: I explained the concept to a friend of mine around 2007, back when it still was twoBirds monolithic web components. His question was: “Can you help me learn Javascript?”. I did, and out of the blue he phoned back half a year later, to tell me he had coded a social community website (SPA) with it, and “... it felt like shovelling sand in a bag”. Lo and behold, this site had all the bells and whistles, geolocated friend search, cockpit design, mobile enabled, you name it. 2007! He just wanted to call back to say “thanks”.
You’ve created the proposal and it is based on your own learnings in an own framework, can you tell us a bit about the successes you had with this approach?
From a functional viewpoint I am absolutely self sufficient and need no framework or abstractions. This merges seamlessly with everything, no matter how complex the projected functionality is. And debugging is a charm. It just reliably works.
Functionality I implemented with it:
- Data Driven Workflow with micro components using tB specific observables.
- DOM handling in general, lots of UI functionality enriching the standard DOM
- 2 way Data Binding between forms and observable objects representing the form data
- 1 way Data Binding from data observables to the DOM
- Loose Coupling via my own user event objects. That is true loose, no DOM events.
- Tight Coupling is a core feature anyway.
- Rule Engines linear in the very first SPA, now via data driven chains
- Business Process Engines think node-red
- Complex Runtime Templating without JSX or virtual DOM
- Single Page Applications from the very start in 2004
- Micro Frontends are just another description of what this does
- Electron Apps
- Progressive Web Apps
- Complex Native Web Components, that comprised of a set of modular UI and business logic micro components
- Progressive enhancement out of the box
Fun stuff I did:
- A vending machine frontend for a lottery company.
- Captain’s Bridge UI for an ocean going container vessel. It was a wave stress simulator ships use to optimise course and speed to minimize wear on the hull.
- A voice menu editor for a SIP provider to edit enterprise customers voice menus. This was really complicated logic.
- A bulk edit software for the Swiss construction site services catalog.
This bulk tree editing application edits a tree 13 levels deep, and manages 50 MB raw data. I can give a screen share of that if anybody is interested.
Fun fact: in this project I also had 2 apprentices, both in their teens and new to programming. Both of them started working on code almost immediately and had a lot of fun with it. They even divided roles between them based on their likings, one focussing on CSS and the other on UI logic. I could do that, because with micro components you cannot break project code, so I just let them do their stuff and they did. They were coding web components, and they worked remote due to COVID. It worked like charm.
Things I want to do or at least try in the future:
- Back port this to ES3. That should take a day, since it is the same class, so there is no need for further docs.
- Then I want to enhance the hbbtv reference implementation to …
- … show my system and server dashboard in real-time on my private TV.
- … run several communication suites on the TV, e.g. mail client, messengers…
- … try all the other stuff that wasn’t on TV before.
What about support in browsers? Many other ideas didn’t come to fruition as browser support took longer than we wanted.
The polyfill already works cross-browser and cross-framework. The specification is final. No v2 for Micro Components. Unless W3C decides to break the internet - unlikely - it will work as long as browsers exist.
Adding the MC class to Javascript engines will improve speed in really heavy applications of course, and make your application code 2.5 kB smaller, but again: it's already there.
What do you need from our community now? What can we do to make Micro Components evolve?
- Try it in your framework.
- On the gitlab page and also here you have my contact info at the end. Feel free to contact me with any questions, or request a how-to. I have to write those anyway. I am willing to help.
- Share your experiences with me, even if they should be bad. Any feedback will result in improvement.
In case something breaks I can try to help or at least document it to avoid grievances for other users.
As for positive feedback, I can also document it (anonymous) to show others what has been archived with it. - Share this article with anybody who may be concerned. The more feedback I get, the better. Criticism is highly appreciated also. I want that even if it floods my email account.
- Also, if this already helps you in any way: I am in need of funding, since I have a lot of work ahead of me: splitting my framework's former functionality into standalone reference MC implementations, like state, form handling, UI, you name it. It is all there, and all of it is 100% web standard compliant.
In the end this was a 5 year full time effort, stretched over 20 years real time. I am sick of spending all my private savings on it until I am so broke I need to get a job. I really did that.
Useful Links
… or any other chat tool or messenger, send me an invitation with “Micro Components” in it.
Final Thoughts
I love the internet. Since it was BBS.
Why? I am also a musician. A bad one, for that. But I still do music in places where the fallout can be contained. Family and the like. :-) If the internet had been around when I started, I would probably be a good musician now, because through the internet I would have had all the teaching material I needed right at my hand. When I was young, I could not afford these and had to help myself along the way by guessing.
Also I am an autist, and I cannot stand injustice. twoBirds always was an approach to make the internet more just. Everybody in this world, wherever they might be, and no matter how limited their resources are, should be enabled to contribute to the internet for the better of us all. A small genius micro component might make all the difference. Likewise everybody should be able to access free tools to better his surroundings with IT.
My goal is to give everybody the same chance to contribute, for money or for free, and the same access to free software. Ultimately I want to change the javascript development ecosystem towards being truly cooperative. That is the essence of open source. I hope Micro Components can help along this way.
Frank