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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@knighted/jsx

v1.2.1

Published

Runtime JSX tagged template that renders DOM or React trees anywhere without a build step.

Readme

@knighted/jsx

CI codecov NPM version

A runtime JSX template tag backed by the oxc-parser 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. One syntax works everywhere—browser scripts, SSR utilities, and bundler pipelines—no separate transpilation step required.

Key features

  • Parse true JSX with no build step – template literals go through oxc-parser, so fragments, spreads, and SVG namespaces all work as expected.
  • DOM + React runtimes – choose jsx for DOM nodes or reactJsx for React elements, and mix them freely (even on the server).
  • Loader + SSR support – ship tagged templates through Webpack/Rspack, Next.js, or plain Node by using the loader and the @knighted/jsx/node entry.

Quick links

Installation

npm install @knighted/jsx

[!IMPORTANT] This package is ESM-only and targets browsers or ESM-aware bundlers. require() is not supported; use native import/<script type="module"> and a DOM-like environment.

[!NOTE] Planning to use the React runtime (@knighted/jsx/react)? Install react@>=18 and react-dom@>=18 alongside this package so the helper can create elements and render them through ReactDOM.

The 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. Because npm enforces the cpu: ["wasm32"] flag you must opt into the install explicitly:

npm_config_ignore_platform=true npm install @oxc-parser/binding-wasm32-wasi

[!TIP] 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 <script type="module"> blocks without any extra setup.

Usage

import { jsx } from '@knighted/jsx'

let count = 3
const handleClick = () => {
  count += 1
  console.log(`Count is now ${count}`)
}

const button = jsx`
  <button className={${`counter-${count}`}} onClick={${handleClick}}>
    Count is ${count}
  </button>
`

document.body.append(button)

React runtime (reactJsx)

Need 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):

import { useState } from 'react'
import { reactJsx } from '@knighted/jsx/react'
import { createRoot } from 'react-dom/client'

const App = () => {
  const [count, setCount] = useState(0)

  return reactJsx`
    <section className="react-demo">
      <h2>Hello from React</h2>
      <p>Count is ${count}</p>
      <button onClick={${() => setCount(value => value + 1)}}>
        Increment
      </button>
    </section>
  `
}

createRoot(document.getElementById('root')!).render(reactJsx`<${App} />`)

The 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 <${MyComponent} /> and use hooks/state as usual. The helper lives in a separate subpath so DOM-only consumers never pay the React dependency cost.

Loader integration

Use 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.

// rspack.config.js / webpack.config.js
export default {
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        include: path.resolve(__dirname, 'src'),
        use: [
          {
            loader: '@knighted/jsx/loader',
            options: {
              // Optional: restrict or rename the tagged templates.
              // tags: ['jsx', 'reactJsx'],
            },
          },
        ],
      },
    ],
  },
}

Pair 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. The demo fixture under test/fixtures/rspack-app shows a full setup that mixes Lit and React, and there is also a standalone walkthrough at morganney/jsx-loader-demo:

npm run build
npm run setup:wasm
npm run build:fixture

Then point a static server at the fixture root (which serves index.html plus the bundled dist/hybrid.js and dist/reactMode.js) to see it in a browser:

# Serve the rspack fixture from the repo root
npx http-server test/fixtures/rspack-app -p 4173

Visit http://localhost:4173 (or whichever port you pick) to interact with both the Lit + React hybrid demo and the React-mode bundle.

Need a deeper dive into loader behavior and options? Check out src/loader/README.md for a full walkthrough.

Node / SSR usage

Import 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:

import { jsx } from '@knighted/jsx/node'
import { reactJsx } from '@knighted/jsx/node/react'
import { renderToString } from 'react-dom/server'

const Badge = ({ label }: { label: string }) =>
  reactJsx`
    <button type="button">React says: ${label}</button>
  `

const reactMarkup = renderToString(
  reactJsx`
    <${Badge} label="Server-only" />
  `,
)

const shell = jsx`
  <main>
    <section dangerouslySetInnerHTML={${{ __html: reactMarkup }}}></section>
  </main>
`

console.log(shell.outerHTML)

[!NOTE] 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.

This 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.

Next.js integration

[!IMPORTANT] Next already compiles .tsx/.jsx files, so you do not need this helper to author regular components. The loader only adds value when you want to reuse the tagged template runtime during SSR—mixing DOM nodes built by jsx with React markup, rendering shared utilities on the server, or processing tagged templates outside the usual component pipeline.

Next (and Remix/other Webpack-based SSR stacks) can run the loader by adding a post-loader to the framework config so the template tags are rewritten after SWC/Babel transpilation. The fixture under test/fixtures/next-app ships a complete example that mixes DOM and React helpers during SSR so you can pre-render DOM snippets (for emails, HTML streams, CMS content, etc.) while still returning React components from your pages. The important bits live in next.config.mjs:

import path from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const repoRoot = path.resolve(__dirname, '../../..')
const distDir = path.join(repoRoot, 'dist')

export default {
  output: 'export',
  webpack(config) {
    config.resolve.alias = {
      ...(config.resolve.alias ?? {}),
      '@knighted/jsx': path.join(distDir, 'index.js'),
      '@knighted/jsx/react': path.join(distDir, 'react/index.js'),
    }

    config.module.rules.push({
      test: /\.[jt]sx?$/,
      include: path.join(__dirname, 'pages'),
      enforce: 'post',
      use: [{ loader: path.join(distDir, 'loader/jsx.js') }],
    })

    return config
  },
}

Inside pages/index.tsx you can freely mix the helpers. The snippet below uses jsx on the server to prebuild a DOM fragment and then injects that HTML alongside a normal React component on the client:

import type { GetServerSideProps } from 'next'
import { jsx } from '@knighted/jsx'
import { reactJsx } from '@knighted/jsx/react'

const buildDomShell = () =>
  jsx`
    <section data-kind="dom-runtime">
      <h2>DOM runtime</h2>
      <p>Rendered as static HTML on the server</p>
    </section>
  `

export const getServerSideProps: GetServerSideProps = async () => {
  return {
    props: {
      domShell: buildDomShell().outerHTML,
    },
  }
}

const ReactBadge = () =>
  reactJsx`
    <button type="button">React badge</button>
  `

type PageProps = { domShell: string }

export default function Page({ domShell }: PageProps) {
  return reactJsx`
    <main>
      <${ReactBadge} />
      <div dangerouslySetInnerHTML={${{ __html: domShell }}}></div>
    </main>
  `
}

Build the fixture locally with npx next build test/fixtures/next-app (or run npx vitest run test/next-fixture.test.ts) to verify the integration end to end. You can adapt the same pattern in app/ routes, API handlers, or server actions whenever you need DOM output generated by the tagged template runtime.

Interpolations

  • All dynamic values are provided through standard template literal expressions (${...}) and map to JSX exactly where they appear. Interpolations used as text children no longer need an extra {...} wrapper—the runtime automatically recognizes placeholders inside text segments (so Count is ${value} just works). Use the usual JSX braces when the syntax requires them (className={${value}}, {...props}, conditionals, etc.).
  • Every expression can be any JavaScript value: primitives, arrays/iterables, DOM nodes, functions, other jsx results, or custom component references.
  • Async values (Promises) are not supported. Resolve them before passing into the template.

Components

You can inline components by interpolating the function used for the tag name. The component receives a props object plus the optional children prop and can return anything that jsx can render (DOM nodes, strings, fragments, other arrays, ...).

const Button = ({ children, variant = 'primary' }) => {
  const el = document.createElement('button')
  el.dataset.variant = variant
  el.append(children ?? '')
  return el
}

const label = 'Tap me'

const view = jsx`
  <section>
    <${Button} variant="ghost">
      ${label}
    </${Button}>
  </section>
`

document.body.append(view)

Fragments & SVG

Use JSX fragments (<>...</>) for multi-root templates. SVG trees automatically switch to the http://www.w3.org/2000/svg namespace once they enter an <svg> tag, and fall back inside <foreignObject>.

DOM-specific props

  • style accepts either a string or an object. Object values handle CSS custom properties (--token) automatically.
  • class and className both work and can be strings or arrays.
  • Event handlers use the on<Event> naming convention (e.g. onClick).
  • ref supports callback refs as well as mutable { current } objects.
  • dangerouslySetInnerHTML expects an object with an __html field, mirroring React.

Browser usage

When you are not using a bundler, load the module directly from a CDN that understands npm packages:

<script type="module">
  import { jsx } from 'https://esm.sh/@knighted/jsx'

  const message = jsx`<p>Hello from the browser</p>`
  document.body.append(message)
</script>

If you are building locally with Vite/Rollup/Webpack make sure the WASM binding is installable so the bundler can resolve @oxc-parser/binding-wasm32-wasi (details below).

Installing the WASM binding locally

@oxc-parser/binding-wasm32-wasi publishes with "cpu": ["wasm32"], so npm/yarn/pnpm skip it on macOS and Linux unless you override the platform guard. Run the helper script after cloning (or whenever you clean node_modules) to pull the binding into place for the Vite demo and any other local bundler builds:

npm run setup:wasm

The script downloads the published tarball via npm pack, extracts it into node_modules/@oxc-parser/binding-wasm32-wasi, and removes the temporary archive so your lockfile stays untouched. If you need to test a different binding build, set WASM_BINDING_PACKAGE before running the script (for example, WASM_BINDING_PACKAGE=@oxc-parser/[email protected] npm run setup:wasm).

Prefer the manual route? You can still run npm_config_ignore_platform=true npm install --no-save @oxc-parser/binding-wasm32-wasi@^0.99.0, but the script above replicates the vendored behavior with less ceremony.

Lite bundle entry

If you already run this package through your own bundler you can trim a few extra kilobytes by importing the minified entries:

import { jsx } from '@knighted/jsx/lite'
import { reactJsx } from '@knighted/jsx/react/lite'
import { jsx as nodeJsx } from '@knighted/jsx/node/lite'
import { reactJsx as nodeReactJsx } from '@knighted/jsx/node/react/lite'

Each 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.

Testing

Run the Vitest suite (powered by jsdom) to exercise the DOM runtime and component support:

npm run test

Tests live in test/jsx.test.ts and cover DOM props/events, custom components, fragments, and iterable children so you can see exactly how the template tag is meant to be used.

Need full end-to-end coverage? The Playwright suite boots the CDN demo (examples/esm-demo.html) and the loader-backed Rspack fixture to verify nested trees, sibling structures, and interop with Lit/React:

npm run test:e2e

[!NOTE] The e2e script builds the library, installs the WASM parser binding, bundles the loader fixture, and then runs playwright test. Make sure Playwright browsers are installed locally (npx playwright install --with-deps chromium).

Browser demo / Vite build

This repo ships with a ready-to-run Vite demo under examples/browser that bundles the library (make sure you have installed the WASM binding via the command above first). Use it for a full end-to-end verification in a real browser (the demo imports @knighted/jsx/lite so you can confirm the lighter entry behaves identically):

# Start a dev server at http://localhost:5173
npm run dev

# Produce a production Rollup build and preview it
npm run build:demo
npm run preview

For a zero-build verification of the lite bundle, open examples/esm-demo-lite.html locally (double-click or run open examples/esm-demo-lite.html) or visit the deployed GitHub Pages build produced by .github/workflows/deploy-demo.yml (it serves that same lite HTML demo).

Limitations

  • Requires a DOM-like environment (it throws when document is missing).
  • JSX identifiers are resolved at runtime through template interpolations; you cannot reference closures directly inside the template without using ${...}.
  • Promises/async components are not supported.

License

MIT © Knighted Code Monkey