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 { gracileJsxTs } from '@gracile-labs/vite-plugin-jsx-forge/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    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 name = 'World';
const el = <span class="greeting">{name}</span>;

Compiles to:

import { html } from 'lit';
const name = 'World';
const el = html`<span class="greeting">${name}</span>`;

Custom element rendering

📄 /src/features/my-element.el.tsx

'use html-signal';

import { customElement } from 'lit/decorators/custom-element.js';
import { LitElement } from 'lit';

@customElement('my-element')
export class MyElement extends LitElement {
  override render() {
    return (
      <>
        <h1>Hello</h1>
        <button on:click={() => console.log('clicked!')}>Click me</button>
      </>
    );
  }
}

Server document

📄 /src/document.tsx

'use html-server';

export const document = (props: { url: URL; title?: string }) => (
  <html lang="en">
    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>{props.title ?? 'My App'}</title>
    </head>
    <body>
      <route-template-outlet></route-template-outlet>
    </body>
  </html>
);

Template flavor directives

Place a "use html-*" directive at the top of a file to control which html tag function is imported:

DirectiveImport sourceUse case
(default)litStandard client templates
'use html-server'@lit-labs/ssrServer-side rendering
'use html-signal'@lit-labs/signalsSignal-aware templates

See the full JSX Forge documentation for the complete syntax reference (bindings, components, control helpers, etc.).