@knighted/jsx 1.2.0-rc.2 → 1.2.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +18 -20
  2. package/package.json +8 -5
package/README.md CHANGED
@@ -56,7 +56,7 @@ const handleClick = () => {
56
56
 
57
57
  const button = jsx`
58
58
  <button className={${`counter-${count}`}} onClick={${handleClick}}>
59
- Count is {${count}}
59
+ Count is ${count}
60
60
  </button>
61
61
  `
62
62
 
@@ -78,7 +78,7 @@ const App = () => {
78
78
  return reactJsx`
79
79
  <section className="react-demo">
80
80
  <h2>Hello from React</h2>
81
- <p>Count is {${count}}</p>
81
+ <p>Count is ${count}</p>
82
82
  <button onClick={${() => setCount(value => value + 1)}}>
83
83
  Increment
84
84
  </button>
@@ -147,12 +147,12 @@ import { renderToString } from 'react-dom/server'
147
147
 
148
148
  const Badge = ({ label }: { label: string }) =>
149
149
  reactJsx`
150
- <button type="button">React says: {${label}}</button>
150
+ <button type="button">React says: ${label}</button>
151
151
  `
152
152
 
153
153
  const reactMarkup = renderToString(
154
154
  reactJsx`
155
- <${Badge} label={${'Server-only'}} />
155
+ <${Badge} label="Server-only" />
156
156
  `,
157
157
  )
158
158
 
@@ -250,7 +250,7 @@ Build the fixture locally with `npx next build test/fixtures/next-app` (or run `
250
250
 
251
251
  ### Interpolations
252
252
 
253
- - All dynamic values are provided through standard template literal expressions (`${...}`). Wrap them in JSX braces to keep the syntax valid: `className={${value}}`, `{${items}}`, etc.
253
+ - All dynamic values are provided through standard template literal expressions (`${...}`) and map to JSX exactly where they appear. Use JSX braces anywhere the syntax normally requires them (`className={${value}}`, spreads, etc.), but plain text children can interpolate directly, e.g. `Count is ${value}`.
254
254
  - Every expression can be any JavaScript value: primitives, arrays/iterables, DOM nodes, functions, other `jsx` results, or custom component references.
255
255
  - Async values (Promises) are not supported. Resolve them before passing into the template.
256
256
 
@@ -266,10 +266,12 @@ const Button = ({ children, variant = 'primary' }) => {
266
266
  return el
267
267
  }
268
268
 
269
+ const label = 'Tap me'
270
+
269
271
  const view = jsx`
270
272
  <section>
271
- <${Button} variant={${'ghost'}}>
272
- {${'Tap me'}}
273
+ <${Button} variant="ghost">
274
+ ${label}
273
275
  </${Button}>
274
276
  </section>
275
277
  `
@@ -336,6 +338,15 @@ npm run test
336
338
 
337
339
  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.
338
340
 
341
+ 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:
342
+
343
+ ```sh
344
+ npm run test:e2e
345
+ ```
346
+
347
+ > [!NOTE]
348
+ > 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`).
349
+
339
350
  ## Browser demo / Vite build
340
351
 
341
352
  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):
@@ -357,19 +368,6 @@ For a zero-build verification of the lite bundle, open `examples/esm-demo-lite.h
357
368
  - JSX identifiers are resolved at runtime through template interpolations; you cannot reference closures directly inside the template without using `${...}`.
358
369
  - Promises/async components are not supported.
359
370
 
360
- ## Performance notes vs `htm`
361
-
362
- [`htm`](https://github.com/developit/htm) popularized tagged template literals for view rendering by tokenizing the template strings on the fly and calling a user-provided hyperscript function. This library takes a different approach: every invocation runs the native `oxc-parser` (compiled to WebAssembly) to build a real JSX AST before constructing DOM nodes.
363
-
364
- Tradeoffs to keep in mind:
365
-
366
- - **Parser vs tokenizer** – `htm` performs lightweight string tokenization, while `@knighted/jsx` pays a higher one-time parse cost but gains the full JSX grammar (fragments, spread children, nested namespaces) without heuristics. For large or deeply nested templates the WASM-backed parser is typically faster and more accurate than string slicing.
367
- - **DOM-first rendering** – this runtime builds DOM nodes directly, so the cost after parsing is mostly attribute assignment and child insertion. `htm` usually feeds a virtual DOM/hyperscript factory (e.g., Preact’s `h`), which may add an extra abstraction layer before hitting the DOM.
368
- - **Bundle size** – including the parser and WASM binding is heavier than `htm`’s ~1 kB tokenizer. If you just need hyperscript sugar, `htm` stays leaner; if you value real JSX semantics without a build step, the extra kilobytes buy you correctness and speed on complex trees.
369
- - **Actual size** – as of `v1.2.0-rc.1` the default `dist/jsx.js` bundle is ~9.0 kB raw / ~2.3 kB min+gzip, while the `@knighted/jsx/lite` entry stays ~5.7 kB raw / ~2.5 kB min+gzip. `htm` weighs in at roughly 0.7 kB min+gzip, so the lite entry narrows the gap to ~1.8 kB for production payloads.
370
-
371
- In short, `@knighted/jsx` trades a slightly larger runtime for the ability to parse genuine JSX with native performance, whereas `htm` favors minimal footprint and hyperscript integration. Pick the tool that aligns with your rendering stack and performance envelope.
372
-
373
371
  ## License
374
372
 
375
373
  MIT © Knighted Code Monkey
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/jsx",
3
- "version": "1.2.0-rc.2",
3
+ "version": "1.2.0-rc.3",
4
4
  "description": "Runtime JSX tagged template that renders DOM or React trees anywhere without a build step.",
5
5
  "keywords": [
6
6
  "jsx runtime",
@@ -63,6 +63,7 @@
63
63
  "prettier": "prettier -w .",
64
64
  "test": "vitest run --coverage",
65
65
  "test:watch": "vitest",
66
+ "test:e2e": "npm run build && npm run setup:wasm && npm run build:fixture && playwright test",
66
67
  "build:fixture": "node scripts/build-rspack-fixture.mjs",
67
68
  "demo:node-ssr": "node test/fixtures/node-ssr/render.mjs",
68
69
  "dev": "vite dev --config vite.config.ts",
@@ -75,15 +76,17 @@
75
76
  "devDependencies": {
76
77
  "@eslint/js": "^9.39.1",
77
78
  "@knighted/duel": "^2.1.6",
78
- "@types/node": "^22.10.1",
79
+ "@oxc-project/types": "^0.99.0",
80
+ "@playwright/test": "^1.57.0",
79
81
  "@rspack/core": "^1.0.5",
80
82
  "@types/jsdom": "^27.0.0",
83
+ "@types/node": "^22.10.1",
81
84
  "@types/react": "^19.2.7",
82
85
  "@types/react-dom": "^19.2.3",
83
86
  "@vitest/coverage-v8": "^4.0.14",
84
87
  "eslint": "^9.39.1",
85
88
  "eslint-plugin-n": "^17.10.3",
86
- "@oxc-project/types": "^0.99.0",
89
+ "eslint-plugin-playwright": "^2.4.0",
87
90
  "http-server": "^14.1.1",
88
91
  "jsdom": "^27.2.0",
89
92
  "lit": "^3.2.1",
@@ -103,9 +106,9 @@
103
106
  "oxc-parser": "^0.99.0"
104
107
  },
105
108
  "peerDependencies": {
106
- "react": ">=18",
107
109
  "jsdom": "*",
108
- "linkedom": "*"
110
+ "linkedom": "*",
111
+ "react": ">=18"
109
112
  },
110
113
  "peerDependenciesMeta": {
111
114
  "react": {