Gracile — JSX Forge (Vite plugin)
A JSX Forge convenience wrapper for Vite
projects.
Compiles JSX/TSX to html tagged template literals at build time, using
TypeScript & ts-patch.
Tip
You can use this plugin with any Vite+Lit setup!
It’s totally decoupled from the Gracile framework.
Installation
npm i @gracile-labs/vite-plugin-jsx-forge jsx-forge
Setup
Vite config
📄 /vite.config.ts
import { gracileJsxTsimport gracileJsxTs } from '@gracile-labs/vite-plugin-jsx-forge/vite';
import { defineConfigfunction defineConfig(config: UserConfig): UserConfig (+5 overloads)Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.
} from 'vite';
export default defineConfigfunction defineConfig(config: UserConfig): UserConfig (+5 overloads)Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.
({
pluginsUserConfig.plugins?: PluginOption[] | undefinedArray of vite plugins to use.
: [
gracileJsxTsimport gracileJsxTs (),
// ...
],
});
TypeScript config
ts-patch must be installed and prepared for the compiler plugin to work.
📄 /tsconfig.json
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "jsx-forge",
"plugins": [
{
"transform": "jsx-forge/transform",
},
],
// Should be aligned with Rollup output directory.
"outDir": "dist",
},
}
Ambient types
Add the JSX types reference in a .d.ts file (or use a
triple-slash directive
at the top of your entry files):
📄 /src/ambient.d.ts
/// <reference types="jsx-forge/jsx-runtime" />
Or import the ambient types from this package directly:
/// <reference types="@gracile-labs/vite-plugin-jsx-forge/ambient" />
Usage
JSX is compiled statically to Lit html tagged templates. This is not a JSX
runtime — think Solid-style compilation, but targeting Lit (or any
compatible tagged template library).
Basic example
📄 /src/my-template.tsx
const nameconst name: "World" = 'World';
const elconst el: React.JSX.Element = <spanReact.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> classclass: string ="greeting">{nameconst name: "World" }</spanReact.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> >;
Compiles to:
import { htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
} from 'lit';
const nameconst name: "World" = 'World';
const elconst el: TemplateResult<1> = htmlconst html: (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult<1>Interprets a template literal as an HTML template that can efficiently
render to and update a container.
const header = (title: string) => html`<h1>${title}</h1>`;
The html tag returns a description of the DOM to render as a value. It is
lazy, meaning no work is done until the template is rendered. When rendering,
if a template comes from the same expression as a previously rendered result,
it's efficiently updated instead of replaced.
`<span class="greeting">${nameconst name: "World" }</span>`;
Custom element rendering
📄 /src/features/my-element.el.tsx
'use html-signal';
import { customElementconst customElement: (tagName: string) => CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
} from 'lit/decorators/custom-element.js';
import { LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
} from 'lit';
@customElementfunction customElement(tagName: string): CustomElementDecoratorClass decorator factory that defines the decorated class as a custom element.
('my-element')
export class MyElementclass MyElement extends LitElementclass LitElementBase element class that manages element properties and attributes, and
renders a lit-html template.
To define a component, subclass LitElement and implement a
render method to provide the component's template. Define properties
using the
{@linkcode
LitElement.properties
properties
}
property or the
{@linkcode
property
}
decorator.
{
override renderMyElement.render(): React.JSX.ElementInvoked on each update to perform rendering tasks. This method may return
any value renderable by lit-html's ChildPart - typically a
TemplateResult. Setting properties inside this method will not trigger
the element to update.
() {
return (
<>
<h1React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement> >Hello</h1React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement> >
<buttonReact.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> onon:click: () => void :clickon:click: () => void ={() => consolevar console: Console .logConsole.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
('clicked!')}>Click me</buttonReact.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> >
</>
);
}
}
Server document
📄 /src/document.tsx
'use html-server';
export const documentconst document: (props: {
url: URL;
title?: string;
}) => React.JSX.Element
= (propsprops: {
url: URL;
title?: string;
}
: { urlurl: URL : URL; titletitle?: string | undefined ?: string }) => (
<htmlReact.JSX.IntrinsicElements.html: React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement> langReact.HTMLAttributes<HTMLHtmlElement>.lang?: string | undefined ="en">
<headReact.JSX.IntrinsicElements.head: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadElement>, HTMLHeadElement> >
<metaReact.JSX.IntrinsicElements.meta: React.DetailedHTMLProps<React.MetaHTMLAttributes<HTMLMetaElement>, HTMLMetaElement> charsetcharset: string ="utf-8" />
<metaReact.JSX.IntrinsicElements.meta: React.DetailedHTMLProps<React.MetaHTMLAttributes<HTMLMetaElement>, HTMLMetaElement> nameReact.MetaHTMLAttributes<HTMLMetaElement>.name?: string | undefined ="viewport" contentReact.MetaHTMLAttributes<HTMLMetaElement>.content?: string | undefined ="width=device-width, initial-scale=1.0" />
<titleReact.JSX.IntrinsicElements.title: React.DetailedHTMLProps<React.HTMLAttributes<HTMLTitleElement>, HTMLTitleElement> >{propsprops: {
url: URL;
title?: string;
}
.titletitle?: string | undefined ?? 'My App'}</titleReact.JSX.IntrinsicElements.title: React.DetailedHTMLProps<React.HTMLAttributes<HTMLTitleElement>, HTMLTitleElement> >
</headReact.JSX.IntrinsicElements.head: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadElement>, HTMLHeadElement> >
<bodyReact.JSX.IntrinsicElements.body: React.DetailedHTMLProps<React.HTMLAttributes<HTMLBodyElement>, HTMLBodyElement> >
<route-template-outlet></route-template-outlet>
</bodyReact.JSX.IntrinsicElements.body: React.DetailedHTMLProps<React.HTMLAttributes<HTMLBodyElement>, HTMLBodyElement> >
</htmlReact.JSX.IntrinsicElements.html: React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement> >
);
Template flavor directives
Place a "use html-*" directive at the top of a file to control which html
tag function is imported:
| Directive | Import source | Use case |
|---|---|---|
| (default) | lit | Standard client templates |
'use html-server' | @lit-labs/ssr | Server-side rendering |
'use html-signal' | @lit-labs/signals | Signal-aware templates |
See the full JSX Forge documentation for the complete syntax reference (bindings, components, control helpers, etc.).