Skip to content

Configure JSX

Read as Markdown

Pibbl publishes automatic-runtime entries at @pibbl/core/jsx-runtime and @pibbl/core/jsx-dev-runtime. Compilers generate those imports; application code normally imports Canvas/runtime values from @pibbl/core.

Install @pibbl/core, configure the automatic runtime, then describe a scene in an ordinary .tsx component and mount it with pibbl(canvas, root). Pibbl JSX creates branded Canvas elements; React and the DOM are not involved. The interactive workbench linked after this guide contains a complete first scene with stateful click feedback and disposal.

Pibbl does not install a global JSX namespace and has no React dependency. Its module-scoped JSX types accept synchronous Pibbl component functions, require uppercase imported values, validate component props and children, and reject lowercase intrinsic tags.

For a Pibbl-only package, configure the runtime once:

{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@pibbl/core",
"moduleResolution": "Bundler",
"strict": true
}
}

react-jsxdev is also supported for an explicit development build. It uses @pibbl/core/jsx-dev-runtime, whose jsxDEV records filename, line, and column for runtime diagnostics.

To opt in one file instead, put the pragma before imports:

/** @jsxImportSource @pibbl/core */
import { Rectangle } from "@pibbl/core";
export const swatch = <Rectangle style={{ width: 20, height: 20 }} />;

The pragma chooses the runtime; it does not import React or a Fragment name. The fragment shorthand <>...</> is compiled against Pibbl automatically.

Vite honors the TypeScript configuration above for .tsx and .jsx input. If configuring esbuild directly, use the automatic transform and Pibbl import source:

import { build } from "esbuild";
await build({
entryPoints: ["src/main.tsx"],
bundle: true,
format: "esm",
jsx: "automatic",
jsxImportSource: "@pibbl/core",
outfile: "dist/main.js",
});

Equivalent CLI flags are --jsx=automatic --jsx-import-source=@pibbl/core. Do not configure the classic transform; Pibbl documents the automatic runtime.

Use Babel’s JSX transform with runtime: "automatic" and importSource: "@pibbl/core":

{
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{ "runtime": "automatic", "importSource": "@pibbl/core" }
]
]
}

For development-source metadata, select the corresponding Babel development transform in development builds with the same runtime and importSource. Despite the plugin’s historical name, the emitted constructor imports come from Pibbl, and react is not required.

JavaScript authors use .jsx, the same automatic-runtime configuration, and normal ESM imports:

/** @jsxImportSource @pibbl/core */
import { Rectangle, Text, pibbl } from "@pibbl/core";
function Scene() {
return (
<>
<Rectangle style={{ width: 240, height: 120, fill: "navy" }} />
<Text style={{ left: 16, top: 16, fill: "white" }}>JavaScript</Text>
</>
);
}
const controller = pibbl(document.querySelector("canvas"), <Scene />);
window.addEventListener("pagehide", () => controller.dispose(), { once: true });

JavaScript receives the same runtime validation as TypeScript but not static prop checking. Keep styles explicit and use the diagnostics in JSX troubleshooting.

import {
Group,
Rectangle,
Text,
pibbl,
useSignal,
type PibblController,
type PibblNode,
} from "@pibbl/core";
interface PosterProps {
initialMessage: string;
}
function Poster({ initialMessage }: PosterProps): PibblNode {
const message = useSignal(initialMessage);
return (
<Group>
<Rectangle
style={{ width: 400, height: 240, fill: "#1f4bd8", cursor: "pointer" }}
onClick={() =>
message.update((old) => (old === "Hello" ? "Pibbl!" : "Hello"))
}
/>
<Text
pointerEvents="none"
style={{
left: 200,
top: 100,
fill: "#fff",
font: "800 48px sans-serif",
textAlign: "center",
}}
>
{message}
</Text>
</Group>
);
}
export function mountPoster(canvas: HTMLCanvasElement): PibblController {
return pibbl(canvas, <Poster initialMessage="Hello" />);
}
const controller = mountPoster(document.querySelector("canvas")!);
controller.dispose();
controller.dispose(); // Idempotent.

A compiler chooses one JSX runtime for the whole file. It cannot switch based on an individual expression. In a React/Preact/Pibbl codebase:

  • keep Pibbl JSX in .pibbl.tsx files using the Pibbl pragma or a Pibbl-specific included tsconfig;
  • keep host JSX in its own files using the host runtime;
  • pass Pibbl component functions across the boundary, not already-created foreign elements;
  • when both trees truly belong in one file, keep JSX for the dominant runtime and build the minority Pibbl tree with createElement as createPibblElement.

The strict Bundler and NodeNext fixture playground/package-consumer/jsx.tsx exercises both compiler subpaths without React installed. See React coexistence for the full host pattern.

defineThreeLayer() from @pibbl/three returns a normal Pibbl component, so it can share a Pibbl JSX file without changing the runtime:

import { Rectangle } from "@pibbl/core";
import { defineThreeLayer } from "@pibbl/three";
const ProductScene = defineThreeLayer({
create() {
// Return an ordinary Three scene and camera.
return createProductScene();
},
});
const scene = (
<>
<Rectangle pointerEvents="none" style={{ width: 320, height: 180 }} />
<ProductScene style={{ width: 320, height: 180 }} />
</>
);

Continue with Use Three.js inside Pibbl.

Open the interactive workbench

Read the Authoring, signals, and lifecycle companion for ownership, adaptation, failure modes, and verification. Agent start provides the version-selection workflow.

Documentation built with @pibbl/core 0.0.2, revision c321a83. ALPHA — NOT FOR PRODUCTION USE.