Million.js: an optimizing compiler that rewrites React reconciliation
Optimizing compiler for React
At a glance
- What is it?
- Million.js compiles React components into direct DOM updates instead of diffing a virtual tree. It targets list-heavy or frequently re-rendering components, not whole applications.
- Who is it for?
- Adopt Million.js when a specific React component re-renders often and its cost sits in reconciliation, and you are willing to accept a compile step plus the block model's constraints. Do not adopt it as a blanket replacement for React's renderer, and do not expect it to help components whose cost is in data fetching, layout or third-party widgets.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 132 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Million.js actually replaces
React updates an interface in two stages. It renders a component to get a snapshot of the tree, then reconciles that snapshot against the previous one, walking element by element and patching only what changed. The README walks through a small counter example and counts six diff checks for a div, a paragraph and a button. The claim is not that six checks are expensive. It is that the number of checks grows with the number of JSX elements, so a component with hundreds of nodes pays a cost proportional to its size on every state change.
Million.js targets that second stage. The README describes the approach as skipping the diffing step and directly updating the DOM node, turning reconciliation from O(n) into O(1) for the components it compiles. The intended audience is React developers who already have a working application and a component that re-renders too often. It is not a framework you start a project in, and the README frames it as working with React rather than replacing it.
How the compiler turns JSX into direct DOM writes
The README shows a conceptual version of the generated output. Where React would compare old and new trees, the compiled component guards a DOM write behind a comparison against the previous value:
if (count !== prevCount) {
<p>.innerHTML = `Count: ${count}`;
}The same sketch assigns the event handler directly to the node. Nothing in that snippet is a diff. The compiler has already decided, at build time, which DOM node corresponds to which piece of state, so at runtime it only needs to know whether the value changed.
The repository layout supports this reading. The package exposes separate entry points for the main runtime, a JSX runtime, a compiler and an experimental surface, and the top level contains compiler.d.ts, jsx-runtime.d.ts, react.d.ts and react-server.d.ts alongside a packages/ directory and a pnpm workspace file. That split matters in practice: the compiler runs in your build, the runtime ships to the browser, and the type declaration files exist so the JSX runtime can be swapped without breaking TypeScript. The README also links a blog post titled "Virtual DOM: Back in Block" for the internals, which is where the block model is described rather than in the README itself.
Installing Million.js and compiling a first component
The README gives a single install command. It says the CLI installs the package and configures the project for you.
npx million@latestAfter that, the README says to run your project and that information should show up in your command line. If the CLI fails, the README points to an installation guide rather than describing manual configuration, so the bundler-specific steps are not documented in the README itself.
The runtime is published as the million package, and the entry points listed in package.json include the main export, ./jsx-runtime, ./compiler and ./experimental. A component is then annotated so the compiler picks it up. The README's own example is a plain function component using useState:
function App() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}What you should see after the CLI runs is the package wired into your build and the compiler reporting on components it processed when the dev server starts. The README does not document the exact console output, so treat the command-line message as a signal to check rather than a specified contract.
Where the block model costs you flexibility
The trade-off is structural. A compiler that decides at build time which DOM node a value maps to needs the component's shape to be knowable at build time. The README's own explanation leans on a comparison against a previous value, which means the generated code carries state about the last render. That is cheap, and it is also the source of the constraints.
Dynamic structures are the obvious pressure point. A list whose length changes, a component that swaps between fundamentally different subtrees, or markup produced by a runtime expression the compiler cannot see all push against a model built around stable node identity. The README does not enumerate these limits, and it does not document a rollback path if a compiled component misbehaves. That silence is worth weighing: you are adding a build-time transformation in front of your renderer, and the README's troubleshooting section is a link to an install guide, not a list of known failure modes.
It is also the wrong tool for a component that is slow for reasons other than reconciliation. If the cost sits in a network request, a large chart library, or layout thrash from an animation, skipping the diff changes nothing about where the time goes.
How it differs from memoization and from Preact
The closest alternative is not another framework. It is React.memo and the useMemo and useCallback hooks, which the repository lists among its topics. Those tools stop a component from re-rendering when its props have not changed, but when a render does happen, React still reconciles the resulting tree with the same diffing walk. Million.js attacks the walk itself. That is why the two are not mutually exclusive: memoization reduces how often reconciliation runs, and Million.js reduces what reconciliation costs when it does run.
The other comparison is Preact, which the topic list also names. Preact is a smaller implementation of the same virtual DOM idea. It keeps the diff and makes the diffing code lighter, so it helps across an entire application and requires you to run on a different React-compatible runtime. Million.js keeps React and changes the update strategy for the components you opt in, which means the benefit is concentrated rather than global. If your problem is bundle size everywhere, that points one way. If your problem is one expensive component, it points the other.
Maintenance, versioning and the licence
The repository is not archived, and its last push was on 2026-05-20. The most recent release listed is v3.1.0 from 2024-05-21, while package.json reports version 3.1.10, so patch releases have shipped outside the release list shown here. That gap between the last tagged release and the package version is something to confirm against the npm page before you pin a version in a lockfile.
The licence is MIT, declared in package.json and present as a LICENSE file at the top level. MIT permits commercial use and modification with the copyright notice retained; it also means there is no warranty. That is a statement about the licence text, not legal advice for your situation.
Upgrade cost is the real ongoing expense. Because the compiler is a build-time transformation, a major version can change what the compiler accepts and what the JSX runtime emits. The presence of a separate ./experimental export suggests some surfaces move faster than the stable ones, and depending on those is a different risk profile from depending on the main export.
Editorial conclusion
Adopt Million.js when a specific React component re-renders often and its cost sits in reconciliation, and you are willing to accept a compile step plus the block model's constraints. Do not adopt it as a blanket replacement for React's renderer, and do not expect it to help components whose cost is in data fetching, layout or third-party widgets. Before wiring it into a build, verify two things against the repository itself: that the current major version's docs still describe the block API you plan to use, and that your bundler plugin path is the one the install guide names for your framework. The last push to the repository was on 2026-05-20, so check open issues for the specific failure mode you hit before assuming a fix is coming.
Frequently asked questions
What is Million.js and what problem does it solve?
It is an optimizing compiler for React that makes reconciliation faster by skipping the diffing step and updating DOM nodes directly. The README frames the problem as React's reconciliation growing slower as a component's JSX element count grows.
How do I install Million.js in a React project?
The README gives one command, npx million@latest, and says the CLI installs the package and configures the project automatically. It then says to run your project, and points to an installation guide if you hit problems.
Does Million.js replace React?
No. The README describes it as working with React and making reconciliation faster, not as a separate framework you build an application on.
What is the licence for Million.js?
It is MIT, declared in package.json and included as a LICENSE file at the repository root.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/aidenybai-million)