{"id":34891021,"url":"https://github.com/knightedcodemonkey/jsx","last_synced_at":"2026-01-30T02:36:57.912Z","repository":{"id":326953499,"uuid":"1107232475","full_name":"knightedcodemonkey/jsx","owner":"knightedcodemonkey","description":"Runtime JSX tagged template that renders DOM or React trees anywhere without a build step.","archived":false,"fork":false,"pushed_at":"2026-01-05T05:59:40.000Z","size":1465,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-09T07:55:49.828Z","etag":null,"topics":["browser","dom","jsx","loader","nodejs","react","ssr","wasm"],"latest_commit_sha":null,"homepage":"https://knightedcodemonkey.github.io/jsx/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/knightedcodemonkey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-30T20:32:34.000Z","updated_at":"2026-01-05T05:59:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/knightedcodemonkey/jsx","commit_stats":null,"previous_names":["knightedcodemonkey/jsx"],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/knightedcodemonkey/jsx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knightedcodemonkey%2Fjsx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knightedcodemonkey%2Fjsx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knightedcodemonkey%2Fjsx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knightedcodemonkey%2Fjsx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/knightedcodemonkey","download_url":"https://codeload.github.com/knightedcodemonkey/jsx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knightedcodemonkey%2Fjsx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28384004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T10:34:27.190Z","status":"ssl_error","status_checked_at":"2026-01-13T10:34:26.289Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser","dom","jsx","loader","nodejs","react","ssr","wasm"],"created_at":"2025-12-26T05:29:27.183Z","updated_at":"2026-01-23T17:03:22.501Z","avatar_url":"https://github.com/knightedcodemonkey.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [`@knighted/jsx`](https://www.npmjs.com/package/@knighted/jsx)\n\n![CI](https://github.com/knightedcodemonkey/jsx/actions/workflows/ci.yml/badge.svg)\n[![codecov](https://codecov.io/gh/knightedcodemonkey/jsx/graph/badge.svg?token=tjxuFwcwkr)](https://codecov.io/gh/knightedcodemonkey/jsx)\n[![NPM version](https://img.shields.io/npm/v/@knighted/jsx.svg)](https://www.npmjs.com/package/@knighted/jsx)\n\nA runtime JSX template tag backed by the [`oxc-parser`](https://github.com/oxc-project/oxc) WebAssembly build. Use real JSX syntax directly inside template literals and turn the result into live DOM nodes (or values returned from your own components) without running a bundler. Prefer a build step? The loader can precompile templates so your runtime ships without loading the WASM parser. One syntax works everywhere—browser scripts, SSR utilities, and bundler pipelines—no separate transpilation step required.\n\n## Key features\n\n- **Parse true JSX with no build step** – template literals go through `oxc-parser`, so fragments, spreads, and SVG namespaces all work as expected.\n- **DOM + React runtimes** – choose `jsx` for DOM nodes or `reactJsx` for React elements, and mix them freely (even on the server).\n- **Loader + SSR support** – ship tagged templates through Webpack/Rspack, Next.js, or plain Node by using the loader and the `@knighted/jsx/node` entry (loader builds remove the WASM requirement at runtime).\n\n## Quick links\n\n- [Usage](#usage)\n- [React runtime](#react-runtime-reactjsx)\n- [Loader integration](#loader-integration)\n- [Node / SSR usage](#node--ssr-usage)\n- [Browser usage](#browser-usage)\n- [Development diagnostics](docs/development-diagnostics.md)\n- [TypeScript plugin](docs/ts-plugin.md)\n- [TypeScript guide](docs/typescript.md)\n- [Component testing](docs/testing.md)\n- [CLI setup](docs/cli.md)\n\n## Installation\n\n```sh\nnpm install @knighted/jsx\n```\n\n\u003e [!IMPORTANT]\n\u003e `@knighted/jsx` ships as ESM-only. The dual-mode `.cjs` artifacts we build internally are not published.\n\n\u003e [!NOTE]\n\u003e Planning to use the React runtime (`@knighted/jsx/react`)? Install `react@\u003e=18` and `react-dom@\u003e=18` alongside this package so the helper can create elements and render them through ReactDOM.\n\nThe parser automatically uses native bindings when it runs in Node.js. To enable the WASM binding for browser builds you also need the `@oxc-parser/binding-wasm32-wasi` package. The quickest path is:\n\n```sh\nnpx @knighted/jsx init\n```\n\nSee [docs/cli.md](docs/cli.md) for flags, dry runs, and package-manager overrides. If you prefer manual install, run `npm_config_ignore_platform=true npm install @oxc-parser/binding-wasm32-wasi`.\n\n\u003e [!TIP]\n\u003e Public CDNs such as `esm.sh` or `jsdelivr` already publish bundles that include the WASM binding, so you can import this package directly from those endpoints in `\u003cscript type=\"module\"\u003e` blocks without any extra setup.\n\n## Usage\n\n```ts\nimport { jsx } from '@knighted/jsx'\n\nlet count = 3\nconst handleClick = () =\u003e {\n  count += 1\n  console.log(`Count is now ${count}`)\n}\n\nconst button = jsx`\n  \u003cbutton className={${`counter-${count}`}} onClick={${handleClick}}\u003e\n    Count is ${count}\n  \u003c/button\u003e\n`\n\ndocument.body.append(button)\n```\n\n### React runtime (`reactJsx`)\n\nNeed to compose React elements instead of DOM nodes? Import the dedicated helper from the `@knighted/jsx/react` subpath (React 18+ and `react-dom` are still required to mount the tree):\n\n```ts\nimport { useState } from 'react'\nimport { reactJsx } from '@knighted/jsx/react'\nimport { createRoot } from 'react-dom/client'\n\nconst App = () =\u003e {\n  const [count, setCount] = useState(0)\n\n  return reactJsx`\n    \u003csection className=\"react-demo\"\u003e\n      \u003ch2\u003eHello from React\u003c/h2\u003e\n      \u003cp\u003eCount is ${count}\u003c/p\u003e\n      \u003cbutton onClick={${() =\u003e setCount(value =\u003e value + 1)}}\u003e\n        Increment\n      \u003c/button\u003e\n    \u003c/section\u003e\n  `\n}\n\ncreateRoot(document.getElementById('root')!).render(reactJsx`\u003c${App} /\u003e`)\n```\n\nThe React runtime shares the same template semantics as `jsx`, except it returns React elements (via `React.createElement`) so you can embed other React components with `\u003c${MyComponent} /\u003e` and use hooks/state as usual. The helper lives in a separate subpath so DOM-only consumers never pay the React dependency cost.\n\nIntrinsic props, events, and refs follow React’s JSX intrinsic element typings (React 18/19), and helper types like `ReactJsxIntrinsicElements`, `ReactJsxRef`, and `ReactJsxDomAttributes` are exported from `@knighted/jsx/react` when you need annotations.\n\n### DOM-specific props\n\n- `style` accepts either a string or an object. Object values handle CSS custom properties (`--token`) automatically.\n- `class` and `className` both work and can be strings or arrays.\n- Event handlers use the `on\u003cEvent\u003e` naming convention (e.g. `onClick`), support capture-phase variants via `on\u003cEvent\u003eCapture`, and allow custom events with the `on:custom-event` syntax (descriptor objects with `{ handler, once, capture }` are also accepted).\n- `ref` supports callback refs as well as mutable `{ current }` objects.\n- `dangerouslySetInnerHTML` expects an object with an `__html` field, mirroring React.\n\n### Fragments \u0026 SVG\n\nUse JSX fragments (`\u003c\u003e...\u003c/\u003e`) for multi-root templates. SVG trees automatically switch to the `http://www.w3.org/2000/svg` namespace once they enter an `\u003csvg\u003e` tag, and fall back inside `\u003cforeignObject\u003e`.\n\n### Interpolations and components\n\n- `${...}` works exactly like JSX braces: drop expressions anywhere (text, attributes, spreads, conditionals) and the runtime keeps the original syntax. Text nodes do not need extra wrapping—`Count is ${value}` already works.\n- Interpolated values can be primitives, DOM nodes, arrays/iterables, other `jsx` trees, or component functions. Resolve Promises before passing them in.\n- Inline components are just functions/classes you interpolate as the tag name; they receive props plus optional `children` and can return anything `jsx` accepts.\n\n```ts\nconst Button = ({ variant = 'primary' }) =\u003e {\n  let count = 3\n\n  return jsx`\n    \u003cbutton\n      data-variant=${variant}\n      onClick=${() =\u003e {\n        count += 1\n        console.log(`Count is now ${count}`)\n      }}\n    \u003e\n      Count is ${count}\n    \u003c/button\u003e\n  `\n}\n\nconst view = jsx`\n  \u003csection\u003e\n    \u003cp\u003eInline components can manage their own state.\u003c/p\u003e\n    \u003c${Button} variant=\"ghost\" /\u003e\n  \u003c/section\u003e\n`\n\ndocument.body.append(view)\n```\n\n## Loader integration\n\nUse the published loader entry (`@knighted/jsx/loader`) when you want your bundler to rewrite tagged template literals at build time. The loader finds every ` jsx`` ` (and, by default, ` reactJsx`` ` ) invocation, rebuilds the template with real JSX semantics, and hands back transformed source that can run in any environment without loading the WASM parser at runtime.\n\n```js\n// rspack.config.js / webpack.config.js\nexport default {\n  module: {\n    rules: [\n      {\n        test: /\\.[jt]sx?$/,\n        include: path.resolve(__dirname, 'src'),\n        use: [\n          {\n            loader: '@knighted/jsx/loader',\n            options: {\n              // Optional: restrict or rename the tagged templates.\n              // tags: ['jsx', 'reactJsx'],\n            },\n          },\n        ],\n      },\n    ],\n  },\n}\n```\n\nPair the loader with your existing TypeScript/JSX transpiler (SWC, Babel, Rspack’s builtin loader, etc.) so regular React components and the tagged templates can live side by side.\n\nNeed a deeper dive into loader behavior and options? Check out [`src/loader/README.md`](src/loader/README.md). There is also a standalone walkthrough at [morganney/jsx-loader-demo](https://github.com/morganney/jsx-loader-demo).\n\n## Node / SSR usage\n\nImport the dedicated Node entry (`@knighted/jsx/node`) when you want to run the template tag inside bare Node.js. It automatically bootstraps a DOM shim by loading either `linkedom` or `jsdom` (install one of them to opt in) and then re-exports the usual helpers so you can keep authoring JSX in the same way:\n\n```ts\nimport { jsx } from '@knighted/jsx/node'\nimport { reactJsx } from '@knighted/jsx/node/react'\nimport { renderToString } from 'react-dom/server'\n\nconst Badge = ({ label }: { label: string }) =\u003e\n  reactJsx`\n    \u003cbutton type=\"button\"\u003eReact says: ${label}\u003c/button\u003e\n  `\n\nconst reactMarkup = renderToString(\n  reactJsx`\n    \u003c${Badge} label=\"Server-only\" /\u003e\n  `,\n)\n\nconst shell = jsx`\n  \u003cmain\u003e\n    \u003csection dangerouslySetInnerHTML={${{ __html: reactMarkup }}}\u003e\u003c/section\u003e\n  \u003c/main\u003e\n`\n\nconsole.log(shell.outerHTML)\n```\n\n\u003e [!NOTE]\n\u003e The Node entry tries `linkedom` first and falls back to `jsdom`. Install whichever shim you prefer (both are optional peer dependencies) and, if needed, set `KNIGHTED_JSX_NODE_SHIM=jsdom` or `linkedom` to force a specific one.\n\nThis repository ships a ready-to-run fixture under `test/fixtures/node-ssr` that uses the Node entry to render a Lit shell plus a React subtree through `ReactDOMServer.renderToString`. Run `npm run build` once to emit `dist/`, then execute `npm run demo:node-ssr` to log the generated markup.\n\nSee how to [integrate with Next.js](./docs/nextjs-integration.md).\n\n## TypeScript integration\n\nThe [`@knighted/jsx-ts-plugin`](docs/ts-plugin.md) keeps DOM (`jsx`) and React (`reactJsx`) templates type-safe with a single config block. The plugin maps each helper to the right mode by default, so you can mix DOM nodes and React components in the same file without juggling multiple plugin entries.\n\n```jsonc\n// tsconfig.json\n{\n  \"compilerOptions\": {\n    \"plugins\": [\n      {\n        \"name\": \"@knighted/jsx-ts-plugin\",\n        \"tagModes\": {\n          \"jsx\": \"dom\",\n          \"reactJsx\": \"react\",\n        },\n      },\n    ],\n  },\n}\n```\n\n- Choose **TypeScript: Select TypeScript Version → Use Workspace Version** in VS Code so the plugin loads from `node_modules`.\n- Run `tsc --noEmit` (or your build step) to surface the same diagnostics your editor shows.\n- Set `jsxImportSource` to `@knighted/jsx` when compiling `.tsx` helpers. The package publishes the `@knighted/jsx/jsx-runtime` module TypeScript expects. The runtime export exists solely for diagnostics and will throw if you call it at execution time—switch back to tagged templates before shipping code.\n- Drop `/* @jsx-dom */` or `/* @jsx-react */` immediately before a tagged template when you need a one-off override.\n- Import the `JsxRenderable` helper type from `@knighted/jsx` whenever you annotate DOM-facing utilities without the plugin:\n\n  ```ts\n  import type { JsxRenderable } from '@knighted/jsx'\n\n  const coerceToDom = (value: unknown): JsxRenderable =\u003e value ?? ''\n  const view = jsx`\u003csection\u003e${coerceToDom(data)}\u003c/section\u003e`\n  ```\n\n\u003e [!TIP]\n\u003e Full `tsconfig` examples (single config or split React + DOM helper projects) live in [docs/typescript.md](docs/typescript.md).\n\nHead over to [docs/ts-plugin.md](docs/ts-plugin.md) for deeper guidance, advanced options, and troubleshooting tips.\n\n## Browser usage\n\nWhen you are not using a bundler, load the module directly from a CDN that understands npm packages:\n\n```html\n\u003cscript type=\"module\"\u003e\n  import { jsx } from 'https://esm.sh/@knighted/jsx'\n  import { reactJsx } from 'https://esm.sh/@knighted/jsx/react'\n  import { useState } from 'https://esm.sh/react@19'\n  import { createRoot } from 'https://esm.sh/react-dom@19/client'\n\n  const reactMount = jsx`\u003cdiv data-kind=\"react-mount\" /\u003e`\n\n  const CounterButton = () =\u003e {\n    const [count, setCount] = useState(0)\n    return reactJsx`\n      \u003cbutton type=\"button\" onClick={${() =\u003e setCount(value =\u003e value + 1)}}\u003e\n        Count is ${count}\n      \u003c/button\u003e\n    `\n  }\n\n  document.body.append(reactMount)\n  createRoot(reactMount).render(reactJsx`\u003c${CounterButton} /\u003e`)\n\u003c/script\u003e\n```\n\n### Lite bundle entry\n\nIf you already run this package through your own bundler you can trim a few extra kilobytes by importing the minified entries:\n\n```ts\nimport { jsx } from '@knighted/jsx/lite'\nimport { reactJsx } from '@knighted/jsx/react/lite'\nimport { jsx as nodeJsx } from '@knighted/jsx/node/lite'\nimport { reactJsx as nodeReactJsx } from '@knighted/jsx/node/react/lite'\n```\n\nEach lite subpath ships the same API as its standard counterpart but is pre-minified and scoped to just that runtime (DOM, React, Node DOM, or Node React). Swap them in when you want the smallest possible bundles; otherwise the default exports keep working as-is.\n\n## Common gotchas\n\n### DocumentFragment reuse (DOM helper)\n\n`jsx` returns actual DOM nodes, so fragments compile down to real `DocumentFragment` instances. The browser treats those fragments as one-time transport containers: append them to a parent, and the fragment empties itself as it moves its children. Unlike VDOM libraries (React, Preact, Solid), we do not clone fragments on your behalf, so storing a fragment and reusing it later will not work the way a React developer might expect.\n\n```ts\nconst header = jsx`\n  \u003c\u003e\n    \u003ch1\u003eTitle\u003c/h1\u003e\n    \u003cp\u003eReusable? Only if you clone.\u003c/p\u003e\n  \u003c/\u003e\n`\n\ndocument.querySelector('header')!.append(header)\ndocument.querySelector('footer')!.append(header) // footer stays empty\n```\n\nWhen you need multiple copies, call the template again, wrap it in a helper (``const makeHeader = () =\u003e jsx`\u003c...\u003e`; makeHeader()``), or clone the fragment before reusing it (`footer.append(header.cloneNode(true))`). Components that return fragments are unaffected because every invocation produces a fresh fragment.\n\n## Limitations\n\n- Requires a DOM-like environment (it throws when `document` is missing).\n- JSX identifiers are resolved at runtime through template interpolations; you cannot reference closures directly inside the template without using `${...}`.\n- Promises/async components are not supported.\n\n## License\n\nMIT © Knighted Code Monkey\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknightedcodemonkey%2Fjsx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fknightedcodemonkey%2Fjsx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknightedcodemonkey%2Fjsx/lists"}