Library Template
Component libraries can ship JSX/TSX source from index.js. Consumer apps compile that source with @opentf/esdev-plugin-afw; the library does not need to bundle a second copy of the framework.
esdev create my-lib --template=library --package-manager=pnpm
The generated project compiles components for tests and includes a rendering test; esdev test --dom runs it.
Package entry
export { default as Counter } from "./src/Counter.jsx";
Include index.js and src/ in the package's files list, and make @opentf/afw a peer dependency. Install the runtime, plugin and test helpers for development:
pnpm add -D @opentf/afw @opentf/esdev-plugin-afw @opentf/afw-test
Sample component
export default function Counter(props) { let count = $state(props.initial ?? 0); return <button type="button" onclick={() => count++}>Count: {count}</button>; }
Rendering tests
The library's esdev.json compiles components for client rendering and turns route discovery off, since a library has no app/ folder. The Testing Setup module registers cleanup:
{ "plugins": [{ "module": "@opentf/esdev-plugin-afw", "options": { "target": "csr", "routes": false } }], "test": { "setup": ["@opentf/afw-test/setup"] } }
import { expect, test } from "runtime:test"; import { render } from "@opentf/afw-test"; import { Counter } from "../index.js"; test("updates after a click", async () => { const { getByRole } = render(Counter, { initial: 2 }); const button = getByRole("button", { name: "Count: 2" }); button.click(); expect(button.textContent).toBe("Count: 3"); });
esdev test --dom
Use --browser for full user-event interaction sequences. Publishing source and publishing a compiled library are different workflows; see esdev's official library build guide if consumers need precompiled output.