npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@causeeffect/foldkit-jsx

v0.0.2

Published

Classic JSX adapter for Foldkit's typed view builders

Readme

@causeeffect/foldkit-jsx

@causeeffect/foldkit-jsx adapts a Foldkit view's typed HtmlBuilder to a classic JSX factory. Foldkit remains responsible for HTML, event handlers, dispatch boundaries, and Submodels. The content mapper only translates JSX syntax into configurable factory calls.

Setup

In your application's directory, install the adapter and Foldkit's runtime dependencies:

npm install @causeeffect/foldkit-jsx [email protected] [email protected] @effect/[email protected]
npm install --save-dev @causeeffect/jsx-content-mapper vite@^8.1.4
npm install --save-dev --save-exact [email protected]

This setup requires Node.js ^22.18.0 || >=24.11.0. TypeScript 7.1 content mappers are experimental; stable TypeScript cannot check .gtsx files yet. The pinned nightly above is the version used by the example and integration tests. @effect/platform-browser supplies the browser services used by Foldkit's application runtime.

Configure .gtsx

Create tsconfig.json in your application:

{
  "compilerOptions": {
    "target": "es2022",
    "lib": ["es2022", "dom", "esnext.disposable"],
    "module": "esnext",
    "moduleResolution": "bundler",
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "skipLibCheck": true,
    "noEmit": true,
    "types": ["vite/client"]
  },
  "contentMappers": [
    {
      "package": "@causeeffect/jsx-content-mapper",
      "extensions": [".gtsx"],
      "options": {
        "jsxRuntime": "classic",
        "jsxFactory": "jsx.createElement",
        "jsxFragmentFactory": "jsx.Fragment"
      }
    }
  ],
  "include": ["src"]
}

Create vite.config.mts with the same JSX options:

import { gtsx } from '@causeeffect/jsx-content-mapper/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    gtsx({
      jsxRuntime: 'classic',
      jsxFactory: 'jsx.createElement',
      jsxFragmentFactory: 'jsx.Fragment',
    }),
  ],
});

Classic mode uses the factory expression in your file's scope. It does not inject a runtime import. Both TypeScript and Vite must use these classic options; the mapper's default generator runtime produces a different kind of element. The mapper guide documents the transformation and available options.

Add scripts to your application's package.json:

{
  "type": "module",
  "scripts": {
    "dev": "vite",
    "typecheck": "tsc --noEmit --runExternalCode",
    "build": "npm run typecheck && vite build",
    "preview": "vite preview"
  }
}

--runExternalCode allows TypeScript to start the mapper's Node process. Vite transforms .gtsx for the browser, while the typecheck script checks the original files. No jsx compiler option is required.

Views

Create jsx inside each view using the h argument supplied by Foldkit:

import { createJsx } from '@causeeffect/foldkit-jsx';
import type { Document, HtmlBuilder } from 'foldkit/html';

type Message = { readonly _tag: 'ClickedIncrement' };
type Model = { readonly count: number };

export function view(model: Model, h: HtmlBuilder<Message>): Document {
  const jsx = createJsx(h);
  return {
    title: `Counter: ${model.count}`,
    body: (
      <main Class="counter">
        <p>{model.count}</p>
        <button OnClick={{ _tag: 'ClickedIncrement' }}>Increment</button>
      </main>
    ),
  };
}

Intrinsic props use Foldkit's constructor names and parameter types directly. Class="counter" calls h.Class('counter'), OnClick={message} calls h.OnClick(message), and OnInput={(value) => message(value)} calls h.OnInput with a callback whose value is contextually typed as string. Event Messages must belong to the current view. Names keep Foldkit's exact casing, such as Id, Role, AriaLabel, Value, Checked, and Style; lowercase class and onClick are rejected.

The adapter derives prop types from HtmlBuilder<Message> and discovers the corresponding constructors on the supplied builder. It contains no copied HTML attribute table or event implementation. The generic content mapper preserves the prop names in its classic factory calls and has no Foldkit dependency.

undefined props are omitted; boolean attribute values including false are passed through. AllowDrop is a boolean switch for the zero-argument h.AllowDrop() helper. The attributes prop remains an escape hatch for childAttributes, required multi-argument helpers such as h.DataAttribute, h.Attribute, and h.OnCutText, and optional controls such as h.OnClick options. Its attributes are applied before direct props, using Foldkit's normal attribute combination behavior.

key delegates to h.keyed(tag), while Key uses h.Key directly. Numbers and bigints become text; nested arrays and fragments flatten; null, undefined, and booleans render nothing. A fragment is a list of children, so a root Foldkit view should return an element or null. Void elements accept no children. A textarea uses Value={text} and rejects both children and InnerHTML.

Ordinary function components receive their props and a children array when JSX supplies children. Required props, declared child types, and the component's return type remain checked. Declare an optional children prop for components that render nested markup.

Submodels

Define a child with Foldkit's normal Submodel.defineView. Its .gtsx view creates its own adapter from its child builder:

import { Submodel } from 'foldkit';
import { createJsx } from '@causeeffect/foldkit-jsx';

export const childView = Submodel.defineView<ChildModel, ChildMessage>(
  (model, h) => {
    const jsx = createJsx(h);
    return <button OnClick={childMessage}>{model.count}</button>;
  },
);

Embed that child through the parent's bound component:

const jsx = createJsx(h);
return (
  <section>
    <jsx.Submodel
      slotId="counter"
      model={model.counter}
      view={childView}
      toParentMessage={(message) => GotCounterMessage({ message })}
    />
  </section>
);

jsx.Submodel delegates directly to h.submodel. Foldkit infers the child's Model and Message from its branded view. Views that declare per-render inputs require viewInputs of that exact type; views without inputs reject that prop. Each embed site needs a distinct slotId. Update routing still uses Foldkit's Update.foldChild, as demonstrated by the example.

The adapter never stores a process-wide builder. Create it in each root or child view and pass builders to extracted helpers as Foldkit recommends.

Run your application

Keep Foldkit's normal Model, init, and update functions, and export your JSX View from src/app.gtsx. Start the application from src/main.ts:

import { Runtime } from 'foldkit';
import { Model, init, update, view } from './app.gtsx';

const container = document.getElementById('root');
if (!container) throw new Error('The application container is missing.');

Runtime.run(Runtime.makeApplication({ Model, init, update, view, container }));

Add the container and module entry point to your root index.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Foldkit with GTSX</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

Check the app and start Vite from your application directory:

npm run typecheck
npm run dev

Open the URL Vite prints. Use npm run build to check and bundle the app, then npm run preview to serve that build locally.

For complete Model, Message, update, and Submodel implementations, see the runnable Foldkit example, including its application View and counter Submodel. The VS Code extension guide explains how to enable hover types, completion, diagnostics, and navigation for your .gtsx files.