@knighted/jsx 1.2.0-rc.2 → 1.2.0-rc.4
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.
- package/README.md +23 -22
- package/dist/lite/node/index.js +4 -0
- package/dist/lite/node/react/index.js +3 -0
- package/dist/lite/react/index.js +3 -0
- package/package.json +23 -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
|
|
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
|
|
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:
|
|
150
|
+
<button type="button">React says: ${label}</button>
|
|
151
151
|
`
|
|
152
152
|
|
|
153
153
|
const reactMarkup = renderToString(
|
|
154
154
|
reactJsx`
|
|
155
|
-
<${Badge} label=
|
|
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 (`${...}`)
|
|
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=
|
|
272
|
-
|
|
273
|
+
<${Button} variant="ghost">
|
|
274
|
+
${label}
|
|
273
275
|
</${Button}>
|
|
274
276
|
</section>
|
|
275
277
|
`
|
|
@@ -318,13 +320,16 @@ Prefer the manual route? You can still run `npm_config_ignore_platform=true npm
|
|
|
318
320
|
|
|
319
321
|
### Lite bundle entry
|
|
320
322
|
|
|
321
|
-
If you already run this package through your own bundler you can trim a few extra kilobytes by importing the minified
|
|
323
|
+
If you already run this package through your own bundler you can trim a few extra kilobytes by importing the minified entries:
|
|
322
324
|
|
|
323
325
|
```ts
|
|
324
326
|
import { jsx } from '@knighted/jsx/lite'
|
|
327
|
+
import { reactJsx } from '@knighted/jsx/react/lite'
|
|
328
|
+
import { jsx as nodeJsx } from '@knighted/jsx/node/lite'
|
|
329
|
+
import { reactJsx as nodeReactJsx } from '@knighted/jsx/node/react/lite'
|
|
325
330
|
```
|
|
326
331
|
|
|
327
|
-
|
|
332
|
+
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.
|
|
328
333
|
|
|
329
334
|
## Testing
|
|
330
335
|
|
|
@@ -336,6 +341,15 @@ npm run test
|
|
|
336
341
|
|
|
337
342
|
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
343
|
|
|
344
|
+
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:
|
|
345
|
+
|
|
346
|
+
```sh
|
|
347
|
+
npm run test:e2e
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
> [!NOTE]
|
|
351
|
+
> 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`).
|
|
352
|
+
|
|
339
353
|
## Browser demo / Vite build
|
|
340
354
|
|
|
341
355
|
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 +371,6 @@ For a zero-build verification of the lite bundle, open `examples/esm-demo-lite.h
|
|
|
357
371
|
- JSX identifiers are resolved at runtime through template interpolations; you cannot reference closures directly inside the template without using `${...}`.
|
|
358
372
|
- Promises/async components are not supported.
|
|
359
373
|
|
|
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
374
|
## License
|
|
374
375
|
|
|
375
376
|
MIT © Knighted Code Monkey
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import {parseSync}from'oxc-parser';var C="<!doctype html><html><body></body></html>",I=["window","self","document","HTMLElement","Element","Node","DocumentFragment","customElements","Text","Comment","MutationObserver","navigator"],M=()=>typeof document<"u"&&typeof document.createElement=="function",L=e=>{let n=globalThis,t=e;I.forEach(o=>{n[o]===void 0&&t[o]!==void 0&&(n[o]=t[o]);});},x=async()=>{let{parseHTML:e}=await import('linkedom'),{window:n}=e(C);return n},h=async()=>{let{JSDOM:e}=await import('jsdom'),{window:n}=new e(C);return n},F=()=>{let e=typeof process<"u"&&process.env?.KNIGHTED_JSX_NODE_SHIM?process.env.KNIGHTED_JSX_NODE_SHIM.toLowerCase():void 0;return e==="linkedom"||e==="jsdom"?e:"auto"},$=()=>{let e=F();return e==="linkedom"?[x,h]:e==="jsdom"?[h,x]:[x,h]},B=async()=>{let e=[];for(let t of $())try{return await t()}catch(o){e.push(o);}let n='Unable to bootstrap a DOM-like environment. Install "linkedom" or "jsdom" (both optional peer dependencies) or set KNIGHTED_JSX_NODE_SHIM to pick one explicitly.';throw new AggregateError(e,n)},u=null,T=async()=>{if(!M())return u||(u=(async()=>{let e=await B();L(e);})().catch(e=>{throw u=null,e})),u};var W=/<\s*$/,H=/<\/\s*$/,G="__KX_EXPR__",z=0,N={lang:"jsx",sourceType:"module",range:true,preserveParens:true},X=e=>{let n=`[oxc-parser] ${e.message}`;if(e.labels?.length){let t=e.labels[0];t.message&&(n+=`
|
|
2
|
+
${t.message}`);}return e.codeframe&&(n+=`
|
|
3
|
+
${e.codeframe}`),n},k=e=>{for(let n of e.body)if(n.type==="ExpressionStatement"){let t=n.expression;if(t.type==="JSXElement"||t.type==="JSXFragment")return t}throw new Error("The jsx template must contain a single JSX element or fragment.")},f=e=>{switch(e.type){case "JSXIdentifier":return e.name;case "JSXNamespacedName":return `${e.namespace.name}:${e.name.name}`;case "JSXMemberExpression":return `${f(e.object)}.${e.property.name}`;default:return ""}},E=(e,n)=>{if(!e||typeof e!="object")return;let t=e;typeof t.type=="string"&&(n(t),Object.values(t).forEach(o=>{if(o){if(Array.isArray(o)){o.forEach(r=>E(r,n));return}typeof o=="object"&&E(o,n);}}));},A=e=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," ").trim();return t.length>0?t:""},K=(e,n)=>{let t=new Set;return E(e,o=>{o.type==="Identifier"&&n.placeholders.has(o.name)&&t.add(o.name);}),Array.from(t)},R=(e,n,t)=>{if(e.type==="JSXElement"||e.type==="JSXFragment")return t(e);if(!("range"in e)||!e.range)throw new Error("Unable to evaluate expression: missing source range information.");let[o,r]=e.range,s=n.source.slice(o,r),a=K(e,n);try{let i=new Function(...a,`"use strict"; return (${s});`),c=a.map(p=>n.placeholders.get(p));return i(...c)}catch(i){throw new Error(`Failed to evaluate expression ${s}: ${i.message}`)}},V=e=>{let n=e.replace(/[^a-zA-Z0-9_$]/g,"");return n?/[A-Za-z_$]/.test(n[0])?n:`Component${n}`:"Component"},U=(e,n,t)=>{let o=t.get(e);if(o)return o;let r=e.displayName||e.name||`Component${n.length}`,s=V(r??""),a=s,i=1;for(;n.some(p=>p.name===a);)a=`${s}${i++}`;let c={name:a,value:e};return n.push(c),t.set(e,c),c},j=(e,n)=>{let t=e.raw??e,o=new Map,r=[],s=new Map,a=t[0]??"",i=z++,c=0;for(let p=0;p<n.length;p++){let d=t[p]??"",y=t[p+1]??"",m=n[p],J=W.test(d)||H.test(d);if(J&&typeof m=="function"){let D=U(m,r,s);a+=D.name+y;continue}if(J&&typeof m=="string"){a+=m+y;continue}let b=`${G}${i}_${c++}__`;o.set(b,m),a+=b+y;}return {source:a,placeholders:o,bindings:r}};var q=()=>{if(typeof document>"u"||typeof document.createElement!="function")throw new Error("The jsx template tag requires a DOM-like environment (document missing).")},Y=e=>typeof Node>"u"?false:e instanceof Node||e instanceof DocumentFragment,Q=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",_=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",v=(e,n,t)=>{if(!(t===false||t===null||t===void 0)){if(n==="dangerouslySetInnerHTML"&&typeof t=="object"&&t&&"__html"in t){e.innerHTML=String(t.__html??"");return}if(n==="ref"){if(typeof t=="function"){t(e);return}if(t&&typeof t=="object"){t.current=e;return}}if(n==="style"&&typeof t=="object"&&t!==null){let o=t,r=e.style;if(!r)return;let s=r;Object.entries(o).forEach(([a,i])=>{if(i!=null){if(a.startsWith("--")){r.setProperty(a,String(i));return}s[a]=i;}});return}if(typeof t=="function"&&n.startsWith("on")){let o=n.slice(2).toLowerCase();e.addEventListener(o,t);return}if(n==="class"||n==="className"){let o=Array.isArray(t)?t.filter(Boolean).join(" "):String(t);e.setAttribute("class",o);return}if(n==="htmlFor"){e.setAttribute("for",String(t));return}if(n in e&&!n.includes("-")){e[n]=t;return}e.setAttribute(n,t===true?"":String(t));}},l=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(_(n))throw new Error("Async values are not supported inside jsx template results.");if(Array.isArray(n)){n.forEach(t=>l(e,t));return}if(Q(n)){for(let t of n)l(e,t);return}if(Y(n)){e.appendChild(n);return}e.appendChild(document.createTextNode(String(n)));}},g=(e,n,t)=>R(e,n,o=>w(o,n,t)),O=(e,n,t)=>{let o={};return e.forEach(r=>{if(r.type==="JSXSpreadAttribute"){let a=g(r.argument,n,t);a&&typeof a=="object"&&!Array.isArray(a)&&Object.assign(o,a);return}let s=f(r.name);if(!r.value){o[s]=true;return}if(r.value.type==="Literal"){o[s]=r.value.value;return}if(r.value.type==="JSXExpressionContainer"){if(r.value.expression.type==="JSXEmptyExpression")return;o[s]=g(r.value.expression,n,t);}}),o},ee=(e,n,t,o)=>{let r=O(n,t,o);Object.entries(r).forEach(([s,a])=>{if(s!=="key"){if(s==="children"){l(e,a);return}v(e,s,a);}});},S=(e,n,t)=>{let o=[];return e.forEach(r=>{switch(r.type){case "JSXText":{let s=A(r.value);s&&o.push(s);break}case "JSXExpressionContainer":{if(r.expression.type==="JSXEmptyExpression")break;o.push(g(r.expression,n,t));break}case "JSXSpreadChild":{let s=g(r.expression,n,t);s!=null&&o.push(s);break}case "JSXElement":case "JSXFragment":{o.push(w(r,n,t));break}}}),o},ne=(e,n,t,o)=>{let r=O(e.openingElement.attributes,n,o),s=S(e.children,n,o);s.length===1?r.children=s[0]:s.length>1&&(r.children=s);let a=t(r);if(_(a))throw new Error("Async jsx components are not supported.");return a},te=(e,n,t)=>{let o=e.openingElement,r=f(o.name),s=n.components.get(r);if(s)return ne(e,n,s,t);if(/[A-Z]/.test(r[0]??""))throw new Error(`Unknown component "${r}". Did you interpolate it with the template literal?`);let a=r==="svg"?"svg":t,i=r==="foreignObject"?null:a,c=a==="svg"?document.createElementNS("http://www.w3.org/2000/svg",r):document.createElement(r);return ee(c,o.attributes,n,a),S(e.children,n,i).forEach(d=>l(c,d)),c},w=(e,n,t)=>{if(e.type==="JSXFragment"){let o=document.createDocumentFragment();return S(e.children,n,t).forEach(s=>l(o,s)),o}return te(e,n,t)},P=(e,...n)=>{q();let t=j(e,n),o=parseSync("inline.jsx",t.source,N);if(o.errors.length>0)throw new Error(X(o.errors[0]));let r=k(o.program),s={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(a=>[a.name,a.value]))};return w(r,s,null)};await T();var me=P;
|
|
4
|
+
export{me as jsx};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import {parseSync}from'oxc-parser';import {createElement,Fragment}from'react';var N=/<\s*$/,$=/<\/\s*$/,F="__KX_EXPR__",P=0,C={lang:"jsx",sourceType:"module",range:true,preserveParens:true},S=e=>{let n=`[oxc-parser] ${e.message}`;if(e.labels?.length){let t=e.labels[0];t.message&&(n+=`
|
|
2
|
+
${t.message}`);}return e.codeframe&&(n+=`
|
|
3
|
+
${e.codeframe}`),n},T=e=>{for(let n of e.body)if(n.type==="ExpressionStatement"){let t=n.expression;if(t.type==="JSXElement"||t.type==="JSXFragment")return t}throw new Error("The jsx template must contain a single JSX element or fragment.")},l=e=>{switch(e.type){case "JSXIdentifier":return e.name;case "JSXNamespacedName":return `${e.namespace.name}:${e.name.name}`;case "JSXMemberExpression":return `${l(e.object)}.${e.property.name}`;default:return ""}},g=(e,n)=>{if(!e||typeof e!="object")return;let t=e;typeof t.type=="string"&&(n(t),Object.values(t).forEach(o=>{if(o){if(Array.isArray(o)){o.forEach(r=>g(r,n));return}typeof o=="object"&&g(o,n);}}));},X=e=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," ").trim();return t.length>0?t:""},O=(e,n)=>{let t=new Set;return g(e,o=>{o.type==="Identifier"&&n.placeholders.has(o.name)&&t.add(o.name);}),Array.from(t)},h=(e,n,t)=>{if(e.type==="JSXElement"||e.type==="JSXFragment")return t(e);if(!("range"in e)||!e.range)throw new Error("Unable to evaluate expression: missing source range information.");let[o,r]=e.range,s=n.source.slice(o,r),a=O(e,n);try{let c=new Function(...a,`"use strict"; return (${s});`),i=a.map(p=>n.placeholders.get(p));return c(...i)}catch(c){throw new Error(`Failed to evaluate expression ${s}: ${c.message}`)}},_=e=>{let n=e.replace(/[^a-zA-Z0-9_$]/g,"");return n?/[A-Za-z_$]/.test(n[0])?n:`Component${n}`:"Component"},j=(e,n,t)=>{let o=t.get(e);if(o)return o;let r=e.displayName||e.name||`Component${n.length}`,s=_(r??""),a=s,c=1;for(;n.some(p=>p.name===a);)a=`${s}${c++}`;let i={name:a,value:e};return n.push(i),t.set(e,i),i},w=(e,n)=>{let t=e.raw??e,o=new Map,r=[],s=new Map,a=t[0]??"",c=P++,i=0;for(let p=0;p<n.length;p++){let f=t[p]??"",x=t[p+1]??"",m=n[p],E=N.test(f)||$.test(f);if(E&&typeof m=="function"){let b=j(m,r,s);a+=b.name+x;continue}if(E&&typeof m=="string"){a+=m+x;continue}let J=`${F}${c}_${i++}__`;o.set(J,m),a+=J+x;}return {source:a,placeholders:o,bindings:r}};var M=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",v=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",u=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(v(n))throw new Error("Async values are not supported inside reactJsx template results.");if(Array.isArray(n)){n.forEach(t=>u(e,t));return}if(M(n)){for(let t of n)u(e,t);return}e.push(n);}},d=(e,n)=>h(e,n,t=>y(t,n)),L=(e,n)=>{let t={};return e.forEach(o=>{if(o.type==="JSXSpreadAttribute"){let s=d(o.argument,n);s&&typeof s=="object"&&!Array.isArray(s)&&Object.assign(t,s);return}let r=l(o.name);if(!o.value){t[r]=true;return}if(o.value.type==="Literal"){t[r]=o.value.value;return}if(o.value.type==="JSXExpressionContainer"){if(o.value.expression.type==="JSXEmptyExpression")return;t[r]=d(o.value.expression,n);}}),t},A=(e,n)=>{let t=[];return e.forEach(o=>{switch(o.type){case "JSXText":{let r=X(o.value);r&&t.push(r);break}case "JSXExpressionContainer":{if(o.expression.type==="JSXEmptyExpression")break;u(t,d(o.expression,n));break}case "JSXSpreadChild":{let r=d(o.expression,n);r!=null&&u(t,r);break}case "JSXElement":case "JSXFragment":{t.push(y(o,n));break}}}),t},R=(e,n,t)=>createElement(e,n,...t),z=(e,n)=>{let t=e.openingElement,o=l(t.name),r=n.components.get(o),s=L(t.attributes,n),a=A(e.children,n);if(r)return R(r,s,a);if(/[A-Z]/.test(o[0]??""))throw new Error(`Unknown component "${o}". Did you interpolate it with the template literal?`);return R(o,s,a)},y=(e,n)=>{if(e.type==="JSXFragment"){let t=A(e.children,n);return createElement(Fragment,null,...t)}return z(e,n)},V=(e,...n)=>{let t=w(e,n),o=parseSync("inline.jsx",t.source,C);if(o.errors.length>0)throw new Error(S(o.errors[0]));let r=T(o.program),s={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(a=>[a.name,a.value]))};return y(r,s)};export{V as reactJsx};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import {parseSync}from'oxc-parser';import {createElement,Fragment}from'react';var N=/<\s*$/,$=/<\/\s*$/,F="__KX_EXPR__",P=0,C={lang:"jsx",sourceType:"module",range:true,preserveParens:true},S=e=>{let n=`[oxc-parser] ${e.message}`;if(e.labels?.length){let t=e.labels[0];t.message&&(n+=`
|
|
2
|
+
${t.message}`);}return e.codeframe&&(n+=`
|
|
3
|
+
${e.codeframe}`),n},T=e=>{for(let n of e.body)if(n.type==="ExpressionStatement"){let t=n.expression;if(t.type==="JSXElement"||t.type==="JSXFragment")return t}throw new Error("The jsx template must contain a single JSX element or fragment.")},l=e=>{switch(e.type){case "JSXIdentifier":return e.name;case "JSXNamespacedName":return `${e.namespace.name}:${e.name.name}`;case "JSXMemberExpression":return `${l(e.object)}.${e.property.name}`;default:return ""}},g=(e,n)=>{if(!e||typeof e!="object")return;let t=e;typeof t.type=="string"&&(n(t),Object.values(t).forEach(o=>{if(o){if(Array.isArray(o)){o.forEach(r=>g(r,n));return}typeof o=="object"&&g(o,n);}}));},X=e=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," ").trim();return t.length>0?t:""},O=(e,n)=>{let t=new Set;return g(e,o=>{o.type==="Identifier"&&n.placeholders.has(o.name)&&t.add(o.name);}),Array.from(t)},h=(e,n,t)=>{if(e.type==="JSXElement"||e.type==="JSXFragment")return t(e);if(!("range"in e)||!e.range)throw new Error("Unable to evaluate expression: missing source range information.");let[o,r]=e.range,s=n.source.slice(o,r),a=O(e,n);try{let c=new Function(...a,`"use strict"; return (${s});`),i=a.map(p=>n.placeholders.get(p));return c(...i)}catch(c){throw new Error(`Failed to evaluate expression ${s}: ${c.message}`)}},_=e=>{let n=e.replace(/[^a-zA-Z0-9_$]/g,"");return n?/[A-Za-z_$]/.test(n[0])?n:`Component${n}`:"Component"},j=(e,n,t)=>{let o=t.get(e);if(o)return o;let r=e.displayName||e.name||`Component${n.length}`,s=_(r??""),a=s,c=1;for(;n.some(p=>p.name===a);)a=`${s}${c++}`;let i={name:a,value:e};return n.push(i),t.set(e,i),i},w=(e,n)=>{let t=e.raw??e,o=new Map,r=[],s=new Map,a=t[0]??"",c=P++,i=0;for(let p=0;p<n.length;p++){let f=t[p]??"",x=t[p+1]??"",m=n[p],E=N.test(f)||$.test(f);if(E&&typeof m=="function"){let b=j(m,r,s);a+=b.name+x;continue}if(E&&typeof m=="string"){a+=m+x;continue}let J=`${F}${c}_${i++}__`;o.set(J,m),a+=J+x;}return {source:a,placeholders:o,bindings:r}};var M=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",v=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",u=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(v(n))throw new Error("Async values are not supported inside reactJsx template results.");if(Array.isArray(n)){n.forEach(t=>u(e,t));return}if(M(n)){for(let t of n)u(e,t);return}e.push(n);}},d=(e,n)=>h(e,n,t=>y(t,n)),L=(e,n)=>{let t={};return e.forEach(o=>{if(o.type==="JSXSpreadAttribute"){let s=d(o.argument,n);s&&typeof s=="object"&&!Array.isArray(s)&&Object.assign(t,s);return}let r=l(o.name);if(!o.value){t[r]=true;return}if(o.value.type==="Literal"){t[r]=o.value.value;return}if(o.value.type==="JSXExpressionContainer"){if(o.value.expression.type==="JSXEmptyExpression")return;t[r]=d(o.value.expression,n);}}),t},A=(e,n)=>{let t=[];return e.forEach(o=>{switch(o.type){case "JSXText":{let r=X(o.value);r&&t.push(r);break}case "JSXExpressionContainer":{if(o.expression.type==="JSXEmptyExpression")break;u(t,d(o.expression,n));break}case "JSXSpreadChild":{let r=d(o.expression,n);r!=null&&u(t,r);break}case "JSXElement":case "JSXFragment":{t.push(y(o,n));break}}}),t},R=(e,n,t)=>createElement(e,n,...t),z=(e,n)=>{let t=e.openingElement,o=l(t.name),r=n.components.get(o),s=L(t.attributes,n),a=A(e.children,n);if(r)return R(r,s,a);if(/[A-Z]/.test(o[0]??""))throw new Error(`Unknown component "${o}". Did you interpolate it with the template literal?`);return R(o,s,a)},y=(e,n)=>{if(e.type==="JSXFragment"){let t=A(e.children,n);return createElement(Fragment,null,...t)}return z(e,n)},V=(e,...n)=>{let t=w(e,n),o=parseSync("inline.jsx",t.source,C);if(o.errors.length>0)throw new Error(S(o.errors[0]));let r=T(o.program),s={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(a=>[a.name,a.value]))};return y(r,s)};export{V as reactJsx};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/jsx",
|
|
3
|
-
"version": "1.2.0-rc.
|
|
3
|
+
"version": "1.2.0-rc.4",
|
|
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",
|
|
@@ -33,16 +33,31 @@
|
|
|
33
33
|
"import": "./dist/react/index.js",
|
|
34
34
|
"default": "./dist/react/index.js"
|
|
35
35
|
},
|
|
36
|
+
"./react/lite": {
|
|
37
|
+
"types": "./dist/react/index.d.ts",
|
|
38
|
+
"import": "./dist/lite/react/index.js",
|
|
39
|
+
"default": "./dist/lite/react/index.js"
|
|
40
|
+
},
|
|
36
41
|
"./node": {
|
|
37
42
|
"types": "./dist/node/index.d.ts",
|
|
38
43
|
"import": "./dist/node/index.js",
|
|
39
44
|
"default": "./dist/node/index.js"
|
|
40
45
|
},
|
|
46
|
+
"./node/lite": {
|
|
47
|
+
"types": "./dist/node/index.d.ts",
|
|
48
|
+
"import": "./dist/lite/node/index.js",
|
|
49
|
+
"default": "./dist/lite/node/index.js"
|
|
50
|
+
},
|
|
41
51
|
"./node/react": {
|
|
42
52
|
"types": "./dist/node/react/index.d.ts",
|
|
43
53
|
"import": "./dist/node/react/index.js",
|
|
44
54
|
"default": "./dist/node/react/index.js"
|
|
45
55
|
},
|
|
56
|
+
"./node/react/lite": {
|
|
57
|
+
"types": "./dist/node/react/index.d.ts",
|
|
58
|
+
"import": "./dist/lite/node/react/index.js",
|
|
59
|
+
"default": "./dist/lite/node/react/index.js"
|
|
60
|
+
},
|
|
46
61
|
"./loader": {
|
|
47
62
|
"import": "./dist/loader/jsx.js",
|
|
48
63
|
"default": "./dist/loader/jsx.js"
|
|
@@ -63,6 +78,7 @@
|
|
|
63
78
|
"prettier": "prettier -w .",
|
|
64
79
|
"test": "vitest run --coverage",
|
|
65
80
|
"test:watch": "vitest",
|
|
81
|
+
"test:e2e": "npm run build && npm run setup:wasm && npm run build:fixture && playwright test",
|
|
66
82
|
"build:fixture": "node scripts/build-rspack-fixture.mjs",
|
|
67
83
|
"demo:node-ssr": "node test/fixtures/node-ssr/render.mjs",
|
|
68
84
|
"dev": "vite dev --config vite.config.ts",
|
|
@@ -75,15 +91,17 @@
|
|
|
75
91
|
"devDependencies": {
|
|
76
92
|
"@eslint/js": "^9.39.1",
|
|
77
93
|
"@knighted/duel": "^2.1.6",
|
|
78
|
-
"@types
|
|
94
|
+
"@oxc-project/types": "^0.99.0",
|
|
95
|
+
"@playwright/test": "^1.57.0",
|
|
79
96
|
"@rspack/core": "^1.0.5",
|
|
80
97
|
"@types/jsdom": "^27.0.0",
|
|
98
|
+
"@types/node": "^22.10.1",
|
|
81
99
|
"@types/react": "^19.2.7",
|
|
82
100
|
"@types/react-dom": "^19.2.3",
|
|
83
101
|
"@vitest/coverage-v8": "^4.0.14",
|
|
84
102
|
"eslint": "^9.39.1",
|
|
85
103
|
"eslint-plugin-n": "^17.10.3",
|
|
86
|
-
"
|
|
104
|
+
"eslint-plugin-playwright": "^2.4.0",
|
|
87
105
|
"http-server": "^14.1.1",
|
|
88
106
|
"jsdom": "^27.2.0",
|
|
89
107
|
"lit": "^3.2.1",
|
|
@@ -103,9 +121,9 @@
|
|
|
103
121
|
"oxc-parser": "^0.99.0"
|
|
104
122
|
},
|
|
105
123
|
"peerDependencies": {
|
|
106
|
-
"react": ">=18",
|
|
107
124
|
"jsdom": "*",
|
|
108
|
-
"linkedom": "*"
|
|
125
|
+
"linkedom": "*",
|
|
126
|
+
"react": ">=18"
|
|
109
127
|
},
|
|
110
128
|
"peerDependenciesMeta": {
|
|
111
129
|
"react": {
|