@knno/jsx-optimizer
v2.2.1
Published
Compile-time optimizer for @knno/jsx — template cloning and precise DOM bindings
Readme
@knno/jsx-optimizer
Compile-time optimizer for @knno/jsx — replaces runtime JSX dispatch with template cloning and precise DOM bindings.
Adds ~0 runtime overhead: no virtual DOM, no runtime createElement loops, no prop-type dispatching. The compiler analyzes your JSX at build time and generates optimized JavaScript that directly manipulates the DOM.
Installation
npm install -D @knno/jsx-optimizer
# or
pnpm add -D @knno/jsx-optimizerRequires
@knno/jsxas a runtime dependency.
Usage
Vite
// vite.config.ts
import { optimizer } from '@knno/jsx-optimizer/vite';
export default {
plugins: [optimizer()],
};Rollup / Rolldown
// rollup.config.js
import { optimizer } from '@knno/jsx-optimizer/rollup';
export default {
plugins: [optimizer()],
};esbuild / tsup
// esbuild config
import { optimizer } from '@knno/jsx-optimizer/esbuild';
await esbuild.build({
plugins: [optimizer()],
});// tsup.config.ts
import { optimizer } from '@knno/jsx-optimizer/esbuild';
export default {
esbuildPlugins: [optimizer()],
};Programmatic
import { transform } from '@knno/jsx-optimizer';
const result = transform(sourceCode, 'app.tsx');
// → { code: optimizedJavaScript, map?: sourceMap }How It Works
The optimizer replaces JSX with a two-phase execution model:
Phase 1 — Build Time (the optimizer)
// Your source code
<tr class={() => selected({ key: row.id }) === row.id ? "danger" : ""}>
<td class="col-md-1">{row.id}</td>
<td class="col-md-4"><a onClick={handler}>{value("label", () => row.label)}</a></td>
</tr>↓ Compiler analyzes and generates:
// Template — created once, cloned for each row
const _tpl0 = (() => {
const t = document.createElement("template");
t.innerHTML = `<tr><td class="col-md-1"></td><td class="col-md-4"><a></a></td></tr>`;
return () => t.content.firstElementChild.cloneNode(true);
})();
// Binding code
(() => {
const root = _tpl0(); // 1 cloneNode instead of 8 createElement calls
const td1 = root.firstElementChild;
const a1 = td1.nextElementSibling.firstElementChild;
track(root, ctx => { // Dynamic className — skips setAttr
ctx.skipUpdateDOM = true;
root.className = expr(); // Direct property assignment
});
appendAll(td1, row.id); // Dynamic text
a1.addEventListener("click", handler); // Event
appendAll(a1, value("label", ...)); // Reactive child
return root;
})();Phase 2 — Runtime (what the browser executes)
| | Without optimizer | With optimizer | |---|---|---| | createElement per row | 8× | 0 (1× cloneNode) | | jsxGen prop dispatch | 6× | 0 (direct assignment) | | appendAll type checks | 6× | 2× (only dynamic children) | | DOM update path | generic updateDOM | precise (node.data / el.className) |
Safety
The optimizer follows a "degrade gracefully, never crash" principle:
- Optimizable elements (all-static props + optimizable children) → template + bindings
- Unoptimizable elements (components, spread attrs, dynamic children containing JSX) → falls back to runtime
jx()— functionally identical to no optimizer. - Nested JSX in expressions → inner elements are individually optimized and referenced in the outer expression.
No matter the JSX complexity, the output is always semantically equivalent to the runtime-only version.
When Not to Use
The optimizer adds a build step and slightly increases bundle size (~4%). Skip it if:
- You're prototyping or debugging (runtime mode gives clearer stack traces)
- Your app has very few JSX elements (the overhead isn't worth it)
- You're using a build tool without plugin support (use the
transform()API directly)
Requirements
- Node.js: >= 20.9.0
- Build tool: Vite, Rollup, Rolldown, esbuild, tsup, or any tool with a transform hook
License
MIT © Thor Qin
