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.

Shell
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

JavaScriptindex.js
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:

Shell
pnpm add -D @opentf/afw @opentf/esdev-plugin-afw @opentf/afw-test

Sample component

JSXsrc/Counter.jsx
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:

JSONesdev.json
{
  "plugins": [{
    "module": "@opentf/esdev-plugin-afw",
    "options": { "target": "csr", "routes": false }
  }],
  "test": { "setup": ["@opentf/afw-test/setup"] }
}
JavaScripttests/counter.test.js
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");
});
Shell
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.

Last updated on
Edit this page