DEV Community

Cover image for Million.js 1.0.0 Release!
Aiden Bai
Aiden Bai

Posted on • Updated on

Million.js 1.0.0 Release!

<1kb compiler-augmented virtual DOM. It's fast!

I initially started Million.js on a whim. I wanted to tinker around and figure out how to build a simple virtual DOM, and maybe share it with a couple friends. Six months later, Million.js 1.0.0 is completed!

It's been a hot minute, but I've genuinely enjoyed every moment of the process. I'm insanely ecstatic to finally present a stable version of something I'm proud of.

YT Video

What is Million.js?

It's a Virtual DOM, or the architecture React is built off of. Its goal is to be a compile target for Transitional UI Libraries by providing ways for compilers to optimize diffing.

Essentially, Million.js leverages the declaratively and flexibility of the Virtual DOM and while deferring to the compiler when optimizations can be made.

It also is composable, but sensible by default, allowing you to build scalable, increasingly complex logic, but also enjoy best practices with simple API if need be.

// Composable
const diff = node([children(), props()]);
const data = diff(el, newVNode, oldVNode, []);
flush(data.workStack, schedule);

// Equivalent sensible default API
patch(el, newVNode);
Enter fullscreen mode Exit fullscreen mode

And more complex usage of the default API:

import { m, createElement, patch } from 'million';

// Initialize app
const app = createElement(m('div', { id: 'app' }, ['Hello World']));
document.body.appendChild(app);
// Patch content
patch(app, m('div', { id: 'app' }, ['Goodbye World']));
Enter fullscreen mode Exit fullscreen mode

Why?

  • 🦁 Built for libraries that compile
  • 📦 Lightweight bundle size (<1kb brotli+min)
  • ⚡ Fast runtime operations
  • 🛠️ Composable using drivers, sensible by default

Next Steps

I want to bootstrap a compiler using babel JSX AST plugins and explore the possibilities around automatically applying flags, deltas, and keys at build time to optimize away unnecessary diffing.

Additionally, I want to see what new paradigms and APIs can be built for new UI libraries, and possibility greater adoption of Million.js or tangential ideology.

All in all, I'm excited for the future of Transitional UI Libraries, and I hope Million.js is a step towards that future!

Learn more:

Discussion (22)

Collapse
mindplay profile image
Rasmus Schultz

"Compiler-augmented"?

Looks like a virtual DOM diffing library?

I've read the articles and documentation, I've glanced at the code, and I still don't understand what this is or why?

I understand you have plans to use this in a compiler? But if I understand correctly, this does not do precise updates like e.g. Svelte or Solid? If not, what's the point?

What's the difference from e.g. Snabbdom? The API has a different shape, but is there any functional difference? It's virtual DOM and recursive diffing, right?

The only difference I can understand, is it does a million ops per second - that sounds fine, but then virtual DOM and diffing is still going to be the bottleneck, right? It won't be fast (as in reactivity and precise updates) just maybe marginally faster than some other virtual DOM libraries... or?

What am I missing?

Genuinely trying to understand. 🙂

Collapse
aidenybai profile image
Aiden Bai Author • Edited on

Yeah, it is a virtual DOM diffing library, hence the description containing "Virtual DOM"

It's a virtual DOM that is intended to be a compile target for compilers. This way, library developers don't need to solely rely on runtime optimizations (excluding prerendering, etc)

Yes, there are plans for a compiler. A compiler doesn't need to be a fine-grained reactivity library like Svelte or Solid, take a look at Vue SFC compiler or what Next.js has built, although these cases aren't nearly to the degree of the aformentioned fine-grained libraries. The point is to help optimize libraries that use Virtual DOM by leveraging a compiler.

Million.js's goal is to be a target for compiled virtual DOM libraries, while Snabbdom seems to just allow you to build virtual DOM libraries in the general case. Fundamentally, they are both virtual DOM, so yes, they are inherently the same thing, but that doesn't apply to the larger case because of differing use case and implementation.

Million.js doesn't have a million ops per second -- check benchmarks. Virtual DOM architecture's bottleneck is the diffing, hence why Million attempts to solve this with "compiler-augmented" API. I don't have specific metrics comparing fine-grained vs. Million.js and other Virtual DOM libraries, and have no intention to. That responsibility is delegated to the great developers at JavaScript Framework Benchmark. Million.js's goal is just to attempt to optimize compiled virtual DOM libraries.

Hope this clarifies your concerns

Collapse
mindplay profile image
Rasmus Schultz

Virtual DOM architecture's bottleneck is the diffing, hence why Million attempts to solve this with "compiler-augmented" API.

This is where I don't follow.

From what I can figure, anything that uses virtual DOM and performs diffing has to do, in principle, the same amount of essential work. I don't see how having an API with a different shape, internally, is going to make any difference in terms of performance?

Many of the existing libraries are almost definitely already near their theoretical performance limits - many of the "fast" virtual DOM libraries are competing for very narrow marginals.

If you have motivations besides performance for building this library, maybe it's time to drum up some of those? 🙂

Either way, if we're going to need a compiler, my (annoying, sorry) question is always this: if we're going to compile anyway, why not compile to something with better performance limits? We're already close to those limits with virtual DOM diffing - and there are plenty of libraries that can do this without a compile step. There's even a few that can do precise updates without a compiler. What is the complexity and overhead of a compiler going to buy us?

Thread Thread
aidenybai profile image
Aiden Bai Author • Edited on

Yeah, I completely agree on the runtime performance peaking -- but the limit can be broken with precompilation. For instance, runtime operations can be optimized via Million deltas, which are essentially ways to tell million to run imperative runtime operations instead of diffing, or compiler flags, which give million clues on a vnode and vnode's children so it can significantly reduce diffing, not to mention tangential optimizations like better static hoisting and smart key assignment that can be created in the compiler.

Million.js has a really composable API, which is also another selling point. If there is one thing that other Virtual DOM libraries should allow the developer to compose logic together, including nested compositions.

Idealistically, compiling to imperative operations and having some sort of fine grained architecture over it may be a better, more performant implementation. I avoid to claim whichever architecture is better in a certain aspect because I personally don't know which is better. However, the React + Virtual DOM friends are trending towards this new ecosystem with build steps (React w/ Next.js), and the fact that these Virtual DOM-based libraries aren't leveraging this built step to further optimize (further than prerendering and trivial optimizations) the runtime Virtual DOM is a gap for further optimizations like Million.js + compiler.

Thread Thread
mindplay profile image
Rasmus Schultz

I see, so you have these flags that lets a compiler essentially disable tree traversals or skip other parts of the usual virtual DOM diffing process.

That's interesting. If I had to describe this, it would be something along the lines of "virtual DOM with opt-outs". This could definitely improve things over the brute force unconditional tree updates performed by traditional virtual DOM diffing.

I don't expect this will put you quite in the same category as libraries that do precise updates, but maybe close? You still have to diff the props - I wonder if there could be some way to distinguish "permanent" from "dynamic" props? Generally, dynamic props have a function associated with them anyhow, whereas permanent props can be just an expression.

I don't know if it's really still virtual DOM? In some sense it is, but as someone else pointed out, it's perhaps a closer relative to something like Glimmer.

Just an observation, but something like Sinuous or Solid also does diffing - but it's opt-in, whereas your library makes unnecessary work opt-out. Generally, opt-outs require more if-statements, whereas unconditionally requesting the operation you want is generally simpler at that level. But I am curious to see how this pans out - you're controlling some of the complexity at a different layer of abstraction, so maybe that pays off in terms of simplicity in a compiler's output. This will be interesting to see for sure 🙂

Thread Thread
aidenybai profile image
Aiden Bai Author

I agree with your description.

Million.js doesn't intend to be for precise update libraries, it just attempts to optimize parts of the virtual DOM that can be optimized. Props diffing is up to the library developer, Virtual DOM by default is "just pass the props down and we'll figure out the UI" (like React), vs precise update libraries which is "we'll diff the props then construct the UI."

It still fundamentally is a virtual DOM, you can use it like a Virtual DOM, in fact, you can use it without a compiler. However, it can be "augmented" by a compiler to achieve better performance. I'm not sure about glimmer but I'll do a bit of research into that.

I think Sinuous and Solid are more on the precise update libraries (I don't know specifically, just glazing over the readmes). Virtual DOM and precise update libraries are two different architectures, and diffing is done at a different level. The opt-outs can be conditionally calculated at compile-time, meaning that conditional statements would be unnecessary (just inserting flags into the runtime function).

Thanks for the encouragement, I'll do more research and see how I can improve Million further

Collapse
jvdl profile image
John van der Loo

I'm looking forward to seeing the next steps for this. Having a real-world use case and solution would be a great selling point for this, as it will allow you to highlight the benefits in a more tangible way.

Collapse
aidenybai profile image
Aiden Bai Author

Yup! Working hard to achieve that future and thanks for the encouragement

Collapse
minecodes profile image
Minecodes

why brotli? brotli is not really good. why not gzip?

Collapse
dan1ve profile image
Daniel Veihelmann

The reverse is true!

Collapse
aidenybai profile image
Aiden Bai Author

I guess it depends, but yeah, generally speaking brotli is better. You download the source code and serve it anyway, and you can choose what compression format you want to use.

Collapse
sjanjan profile image
lijian

great

Collapse
mangirima profile image
Kevin omanga

Quit interesting

Collapse
herberthobregon profile image
Herberth Obregón

Why continue to guide new developments when web components exist? You can take advantage of those features like lit.dev

Collapse
aidenybai profile image
Aiden Bai Author

This isn't really a new development -- React, Next.js, and Virtual DOM library friends are deeply embedded in the web development. If anything, it's an attempt to improve upon what is already established (standing on the shoulders of giants). Web components are the new kid on the block. It's a great option, but Million.js is just working in a different area of webdev.

Collapse
climentea profile image
Alin Climente

Looks like mithriljs
mithril.js.org/

Collapse
aidenybai profile image
Aiden Bai Author

Mithril.js also uses Virtual DOM, which is why it looks similar. In fact, the Million "sensible" API is basically a mix of mithril.js/snabbdom. It's a result of the same architecture.

Collapse
fridaycandour_46 profile image
FridayCandour

Looks like uiedbook.js build and buildTo

Collapse
aidenybai profile image
Aiden Bai Author • Edited on

Not sure where you're getting this, but that library's goal, API, and function are nothing like Million.js

Collapse
bsides profile image
Rafael Pereira

Just pointing out a typo:

From:
"It's goal is to be a compile target"
To:
"Its goal is to be a compile target"

Thanks for sharing!

Collapse
aidenybai profile image
Aiden Bai Author

Thanks! Fixed