Vite plugin for LiteForge that transforms JSX into optimized DOM operations.
npm install @liteforge/vite-plugin --save-devvite.config.ts:
import { defineConfig } from 'vite'
import liteforge from '@liteforge/vite-plugin'
export default defineConfig({
plugins: [liteforge()]
})tsconfig.json:
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "@liteforge/runtime"
}
}The LiteForge Vite plugin transforms JSX syntax into direct DOM operations with signal-safe getter wrapping. This eliminates the need for a virtual DOM while maintaining familiar JSX syntax.
The plugin transforms JSX at build time:
Input:
const Counter = () => {
const count = signal(0)
return (
<div class="counter">
<p>Count: {count()}</p>
<button onclick={() => count.update(n => n + 1)}>
Increment
</button>
</div>
)
}Output (conceptual):
const Counter = () => {
const count = signal(0)
const _div = document.createElement('div')
_div.className = 'counter'
const _p = document.createElement('p')
effect(() => {
_p.textContent = `Count: ${count()}`
})
const _button = document.createElement('button')
_button.textContent = 'Increment'
_button.addEventListener('click', () => count.update(n => n + 1))
_div.appendChild(_p)
_div.appendChild(_button)
return _div
}import liteforge from '@liteforge/vite-plugin'
export default defineConfig({
plugins: [
liteforge({
// File extensions to transform
extensions: ['.tsx', '.jsx'],
// Enable HMR (experimental)
hmr: true,
// Enable template extraction for static content
templates: true,
// Debug mode (logs transformed code)
debug: false
})
]
})The plugin automatically wraps reactive expressions in getters to ensure proper signal tracking:
// Input
<p class={isActive() ? 'active' : 'inactive'}>
{user().name}
</p>
// The plugin wraps these in getters so effects can track themThis means you write natural JSX with signal calls, and the plugin handles reactivity.
Static HTML is extracted and converted to templates for better performance:
// Static content is optimized
<div class="container">
<header>
<h1>My App</h1>
<nav>...</nav>
</header>
{/* Dynamic content still uses effects */}
<main>{content()}</main>
</div>Event handlers are detected and attached directly:
<button
onclick={() => handleClick()}
onmouseover={(e) => highlight(e)}
>
Click me
</button>All standard DOM events are supported with the on prefix.
Use <>...</> for multiple root elements:
const List = () => (
<>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</>
)Component detection is automatic based on naming convention (PascalCase):
// Components (PascalCase) - passed to h() as function reference
<UserCard user={user} />
<Modal isOpen={showModal()} />
// HTML elements (lowercase) - created as DOM elements
<div class="card">...</div>
<button onclick={...}>...</button>-
HMR — Hot Module Replacement is not fully working yet. Manual browser refresh is required after changes.
-
Text Spacing — Adjacent text and signals need explicit spacing:
// May need explicit spaces <p>Hello{' '}{name()}</p>
-
Value Binding — Form inputs require getter syntax:
// Correct <input value={() => field.value()} /> // Won't update reactively <input value={field.value()} />
For testing or custom tooling:
import { transform, transformCode } from '@liteforge/vite-plugin'
const result = transform(code, {
extensions: ['.tsx'],
hmr: false,
templates: true
}, isDev)
// result.code - transformed code
// result.map - source map
// result.hasJsx - whether JSX was foundimport type {
LiteForgePluginOptions,
ResolvedPluginOptions
} from '@liteforge/vite-plugin'MIT