@knighted/jsx 1.2.0-rc.4 → 1.2.0

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 CHANGED
@@ -136,6 +136,8 @@ npx http-server test/fixtures/rspack-app -p 4173
136
136
 
137
137
  Visit `http://localhost:4173` (or whichever port you pick) to interact with both the Lit + React hybrid demo and the React-mode bundle.
138
138
 
139
+ Need a deeper dive into loader behavior and options? Check out [`src/loader/README.md`](src/loader/README.md) for a full walkthrough.
140
+
139
141
  ## Node / SSR usage
140
142
 
141
143
  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:
@@ -250,7 +252,7 @@ Build the fixture locally with `npx next build test/fixtures/next-app` (or run `
250
252
 
251
253
  ### Interpolations
252
254
 
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}`.
255
+ - 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.).
254
256
  - Every expression can be any JavaScript value: primitives, arrays/iterables, DOM nodes, functions, other `jsx` results, or custom component references.
255
257
  - Async values (Promises) are not supported. Resolve them before passing into the template.
256
258
 
package/dist/cjs/jsx.cjs CHANGED
@@ -162,10 +162,10 @@ const evaluateJsxChildren = (children, ctx, namespace) => {
162
162
  children.forEach(child => {
163
163
  switch (child.type) {
164
164
  case 'JSXText': {
165
- const text = (0, shared_js_1.normalizeJsxText)(child.value);
166
- if (text) {
167
- resolved.push(text);
168
- }
165
+ const segments = (0, shared_js_1.normalizeJsxTextSegments)(child.value, ctx.placeholders);
166
+ segments.forEach(segment => {
167
+ resolved.push(segment);
168
+ });
169
169
  break;
170
170
  }
171
171
  case 'JSXExpressionContainer': {
@@ -31,10 +31,14 @@ class ReactTemplateBuilder {
31
31
  children.forEach(child => {
32
32
  switch (child.type) {
33
33
  case 'JSXText': {
34
- const text = normalizeJsxTextValue(child.value);
35
- if (text) {
36
- compiled.push(JSON.stringify(text));
37
- }
34
+ const segments = normalizeJsxTextSegments(child.value, this.placeholderMap);
35
+ segments.forEach(segment => {
36
+ if (segment.kind === 'text') {
37
+ compiled.push(JSON.stringify(segment.value));
38
+ return;
39
+ }
40
+ compiled.push(segment.value);
41
+ });
38
42
  break;
39
43
  }
40
44
  case 'JSXExpressionContainer': {
@@ -154,6 +158,9 @@ class ReactTemplateBuilder {
154
158
  }
155
159
  return node.name;
156
160
  }
161
+ if (node.type === 'Literal') {
162
+ return JSON.stringify(node.value);
163
+ }
157
164
  if ('range' in node && Array.isArray(node.range)) {
158
165
  throw new Error('[jsx-loader] Unable to inline complex expressions in react mode.');
159
166
  }
@@ -198,6 +205,7 @@ const getTemplateExpressionContext = (left, right) => {
198
205
  return { type: 'childText' };
199
206
  };
200
207
  const TEMPLATE_EXPR_PLACEHOLDER_PREFIX = '__JSX_LOADER_TEMPLATE_EXPR_';
208
+ const TEMPLATE_EXPR_PLACEHOLDER_PATTERN = new RegExp(`${TEMPLATE_EXPR_PLACEHOLDER_PREFIX}\\d+__`, 'g');
201
209
  const MODULE_PARSER_OPTIONS = {
202
210
  lang: 'tsx',
203
211
  sourceType: 'module',
@@ -392,10 +400,47 @@ const extractJsxRoot = (program) => {
392
400
  }
393
401
  throw new Error('[jsx-loader] Expected the template to contain a single JSX root node.');
394
402
  };
395
- const normalizeJsxTextValue = (value) => {
403
+ const normalizeJsxTextSegments = (value, placeholders) => {
396
404
  const collapsed = value.replace(/\r/g, '').replace(/\n\s+/g, ' ');
397
- const trimmed = collapsed.trim();
398
- return trimmed.length > 0 ? trimmed : '';
405
+ const leadingWhitespace = value.match(/^\s*/)?.[0] ?? '';
406
+ const trailingWhitespace = value.match(/\s*$/)?.[0] ?? '';
407
+ const trimStart = /\n/.test(leadingWhitespace);
408
+ const trimEnd = /\n/.test(trailingWhitespace);
409
+ let normalized = collapsed;
410
+ if (trimStart) {
411
+ normalized = normalized.replace(/^\s+/, '');
412
+ }
413
+ if (trimEnd) {
414
+ normalized = normalized.replace(/\s+$/, '');
415
+ }
416
+ if (normalized.length === 0 || normalized.trim().length === 0) {
417
+ return [];
418
+ }
419
+ const segments = [];
420
+ TEMPLATE_EXPR_PLACEHOLDER_PATTERN.lastIndex = 0;
421
+ let cursor = 0;
422
+ let match;
423
+ while ((match = TEMPLATE_EXPR_PLACEHOLDER_PATTERN.exec(normalized))) {
424
+ const index = match.index;
425
+ const slice = normalized.slice(cursor, index);
426
+ if (slice) {
427
+ segments.push({ kind: 'text', value: slice });
428
+ }
429
+ const marker = match[0];
430
+ const expression = placeholders.get(marker);
431
+ if (expression) {
432
+ segments.push({ kind: 'expression', value: expression });
433
+ }
434
+ else {
435
+ segments.push({ kind: 'text', value: marker });
436
+ }
437
+ cursor = index + marker.length;
438
+ }
439
+ const remainder = normalized.slice(cursor);
440
+ if (remainder) {
441
+ segments.push({ kind: 'text', value: remainder });
442
+ }
443
+ return segments;
399
444
  };
400
445
  const TAG_PLACEHOLDER_PREFIX = '__JSX_LOADER_TAG_EXPR_';
401
446
  const buildTemplateSource = (quasis, expressions, source, tag) => {
@@ -72,10 +72,8 @@ const evaluateReactJsxChildren = (children, ctx) => {
72
72
  children.forEach(child => {
73
73
  switch (child.type) {
74
74
  case 'JSXText': {
75
- const text = (0, shared_js_1.normalizeJsxText)(child.value);
76
- if (text) {
77
- resolved.push(text);
78
- }
75
+ const segments = (0, shared_js_1.normalizeJsxTextSegments)(child.value, ctx.placeholders);
76
+ segments.forEach(segment => appendReactChild(resolved, segment));
79
77
  break;
80
78
  }
81
79
  case 'JSXExpressionContainer': {
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildTemplate = exports.ensureBinding = exports.sanitizeIdentifier = exports.evaluateExpression = exports.collectPlaceholderNames = exports.normalizeJsxText = exports.walkAst = exports.getIdentifierName = exports.extractRootNode = exports.formatParserError = exports.parserOptions = void 0;
3
+ exports.buildTemplate = exports.ensureBinding = exports.sanitizeIdentifier = exports.evaluateExpression = exports.collectPlaceholderNames = exports.normalizeJsxTextSegments = exports.walkAst = exports.getIdentifierName = exports.extractRootNode = exports.formatParserError = exports.parserOptions = exports.placeholderPattern = exports.PLACEHOLDER_PREFIX = void 0;
4
4
  const OPEN_TAG_RE = /<\s*$/;
5
5
  const CLOSE_TAG_RE = /<\/\s*$/;
6
- const PLACEHOLDER_PREFIX = '__KX_EXPR__';
6
+ exports.PLACEHOLDER_PREFIX = '__KX_EXPR__';
7
+ exports.placeholderPattern = new RegExp(`${exports.PLACEHOLDER_PREFIX}\\d+_\\d+__`, 'g');
7
8
  let invocationCounter = 0;
8
9
  exports.parserOptions = {
9
10
  lang: 'jsx',
@@ -73,12 +74,48 @@ const walkAst = (node, visitor) => {
73
74
  });
74
75
  };
75
76
  exports.walkAst = walkAst;
76
- const normalizeJsxText = (value) => {
77
+ const normalizeJsxTextSegments = (value, placeholders) => {
77
78
  const collapsed = value.replace(/\r/g, '').replace(/\n\s+/g, ' ');
78
- const trimmed = collapsed.trim();
79
- return trimmed.length > 0 ? trimmed : '';
79
+ const leadingWhitespace = value.match(/^\s*/)?.[0] ?? '';
80
+ const trailingWhitespace = value.match(/\s*$/)?.[0] ?? '';
81
+ const trimStart = /\n/.test(leadingWhitespace);
82
+ const trimEnd = /\n/.test(trailingWhitespace);
83
+ let normalized = collapsed;
84
+ if (trimStart) {
85
+ normalized = normalized.replace(/^\s+/, '');
86
+ }
87
+ if (trimEnd) {
88
+ normalized = normalized.replace(/\s+$/, '');
89
+ }
90
+ if (normalized.length === 0 || normalized.trim().length === 0) {
91
+ return [];
92
+ }
93
+ const segments = [];
94
+ exports.placeholderPattern.lastIndex = 0;
95
+ let cursor = 0;
96
+ let match;
97
+ while ((match = exports.placeholderPattern.exec(normalized))) {
98
+ const index = match.index;
99
+ const slice = normalized.slice(cursor, index);
100
+ if (slice) {
101
+ segments.push(slice);
102
+ }
103
+ const token = match[0];
104
+ if (placeholders.has(token)) {
105
+ segments.push(placeholders.get(token));
106
+ }
107
+ else {
108
+ segments.push(token);
109
+ }
110
+ cursor = index + token.length;
111
+ }
112
+ const remainder = normalized.slice(cursor);
113
+ if (remainder) {
114
+ segments.push(remainder);
115
+ }
116
+ return segments;
80
117
  };
81
- exports.normalizeJsxText = normalizeJsxText;
118
+ exports.normalizeJsxTextSegments = normalizeJsxTextSegments;
82
119
  const collectPlaceholderNames = (expression, ctx) => {
83
120
  const placeholders = new Set();
84
121
  (0, exports.walkAst)(expression, node => {
@@ -160,7 +197,7 @@ const buildTemplate = (strings, values) => {
160
197
  source += value + nextChunk;
161
198
  continue;
162
199
  }
163
- const placeholder = `${PLACEHOLDER_PREFIX}${templateId}_${placeholderIndex++}__`;
200
+ const placeholder = `${exports.PLACEHOLDER_PREFIX}${templateId}_${placeholderIndex++}__`;
164
201
  placeholders.set(placeholder, value);
165
202
  source += placeholder + nextChunk;
166
203
  }
@@ -1,5 +1,7 @@
1
1
  import type { Expression, JSXElement, JSXFragment, JSXIdentifier, JSXMemberExpression, JSXNamespacedName, Program } from '@oxc-project/types';
2
2
  import type { OxcError, ParserOptions } from 'oxc-parser';
3
+ export declare const PLACEHOLDER_PREFIX = "__KX_EXPR__";
4
+ export declare const placeholderPattern: RegExp;
3
5
  type AnyTemplateFunction = (...args: any[]) => unknown;
4
6
  type AnyTemplateConstructor = abstract new (...args: any[]) => unknown;
5
7
  export type TemplateComponent = (AnyTemplateFunction | AnyTemplateConstructor) & {
@@ -29,7 +31,7 @@ type AnyOxcNode = {
29
31
  [key: string]: unknown;
30
32
  };
31
33
  export declare const walkAst: (node: unknown, visitor: (target: AnyOxcNode) => void) => void;
32
- export declare const normalizeJsxText: (value: string) => string;
34
+ export declare const normalizeJsxTextSegments: (value: string, placeholders: Map<string, unknown>) => unknown[];
33
35
  export declare const collectPlaceholderNames: <TComponent extends TemplateComponent>(expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>) => string[];
34
36
  export declare const evaluateExpression: <TComponent extends TemplateComponent>(expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>, evaluateJsxNode: (node: JSXElement | JSXFragment) => unknown) => unknown;
35
37
  export declare const sanitizeIdentifier: (value: string) => string;
package/dist/jsx.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parseSync } from 'oxc-parser';
2
- import { buildTemplate, evaluateExpression, extractRootNode, formatParserError, getIdentifierName, normalizeJsxText, parserOptions, } from './runtime/shared.js';
2
+ import { buildTemplate, evaluateExpression, extractRootNode, formatParserError, getIdentifierName, normalizeJsxTextSegments, parserOptions, } from './runtime/shared.js';
3
3
  const ensureDomAvailable = () => {
4
4
  if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
5
5
  throw new Error('The jsx template tag requires a DOM-like environment (document missing).');
@@ -159,10 +159,10 @@ const evaluateJsxChildren = (children, ctx, namespace) => {
159
159
  children.forEach(child => {
160
160
  switch (child.type) {
161
161
  case 'JSXText': {
162
- const text = normalizeJsxText(child.value);
163
- if (text) {
164
- resolved.push(text);
165
- }
162
+ const segments = normalizeJsxTextSegments(child.value, ctx.placeholders);
163
+ segments.forEach(segment => {
164
+ resolved.push(segment);
165
+ });
166
166
  break;
167
167
  }
168
168
  case 'JSXExpressionContainer': {
@@ -1,4 +1,4 @@
1
- import {parseSync}from'oxc-parser';var R=/<\s*$/,j=/<\/\s*$/,F="__KX_EXPR__",$=0,S={lang:"jsx",sourceType:"module",range:true,preserveParens:true},C=e=>{let n=`[oxc-parser] ${e.message}`;if(e.labels?.length){let t=e.labels[0];t.message&&(n+=`
1
+ import {parseSync}from'oxc-parser';var $=/<\s*$/,_=/<\/\s*$/,C="__KX_EXPR__",S=new RegExp(`${C}\\d+_\\d+__`,"g"),F=0,b={lang:"jsx",sourceType:"module",range:true,preserveParens:true},w=e=>{let n=`[oxc-parser] ${e.message}`;if(e.labels?.length){let t=e.labels[0];t.message&&(n+=`
2
2
  ${t.message}`);}return e.codeframe&&(n+=`
3
- ${e.codeframe}`),n},b=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.")},d=e=>{switch(e.type){case "JSXIdentifier":return e.name;case "JSXNamespacedName":return `${e.namespace.name}:${e.name.name}`;case "JSXMemberExpression":return `${d(e.object)}.${e.property.name}`;default:return ""}},x=(e,n)=>{if(!e||typeof e!="object")return;let t=e;typeof t.type=="string"&&(n(t),Object.values(t).forEach(r=>{if(r){if(Array.isArray(r)){r.forEach(o=>x(o,n));return}typeof r=="object"&&x(r,n);}}));},w=e=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," ").trim();return t.length>0?t:""},P=(e,n)=>{let t=new Set;return x(e,r=>{r.type==="Identifier"&&n.placeholders.has(r.name)&&t.add(r.name);}),Array.from(t)},T=(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[r,o]=e.range,s=n.source.slice(r,o),a=P(e,n);try{let i=new Function(...a,`"use strict"; return (${s});`),p=a.map(c=>n.placeholders.get(c));return i(...p)}catch(i){throw new Error(`Failed to evaluate expression ${s}: ${i.message}`)}},_=e=>{let n=e.replace(/[^a-zA-Z0-9_$]/g,"");return n?/[A-Za-z_$]/.test(n[0])?n:`Component${n}`:"Component"},O=(e,n,t)=>{let r=t.get(e);if(r)return r;let o=e.displayName||e.name||`Component${n.length}`,s=_(o??""),a=s,i=1;for(;n.some(c=>c.name===a);)a=`${s}${i++}`;let p={name:a,value:e};return n.push(p),t.set(e,p),p},X=(e,n)=>{let t=e.raw??e,r=new Map,o=[],s=new Map,a=t[0]??"",i=$++,p=0;for(let c=0;c<n.length;c++){let u=t[c]??"",g=t[c+1]??"",m=n[c],E=R.test(u)||j.test(u);if(E&&typeof m=="function"){let A=O(m,o,s);a+=A.name+g;continue}if(E&&typeof m=="string"){a+=m+g;continue}let h=`${F}${i}_${p++}__`;r.set(h,m),a+=h+g;}return {source:a,placeholders:r,bindings:o}};var M=()=>{if(typeof document>"u"||typeof document.createElement!="function")throw new Error("The jsx template tag requires a DOM-like environment (document missing).")},D=e=>typeof Node>"u"?false:e instanceof Node||e instanceof DocumentFragment,L=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",N=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",B=(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 r=t,o=e.style;if(!o)return;let s=o;Object.entries(r).forEach(([a,i])=>{if(i!=null){if(a.startsWith("--")){o.setProperty(a,String(i));return}s[a]=i;}});return}if(typeof t=="function"&&n.startsWith("on")){let r=n.slice(2).toLowerCase();e.addEventListener(r,t);return}if(n==="class"||n==="className"){let r=Array.isArray(t)?t.filter(Boolean).join(" "):String(t);e.setAttribute("class",r);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(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(L(n)){for(let t of n)l(e,t);return}if(D(n)){e.appendChild(n);return}e.appendChild(document.createTextNode(String(n)));}},f=(e,n,t)=>T(e,n,r=>J(r,n,t)),k=(e,n,t)=>{let r={};return e.forEach(o=>{if(o.type==="JSXSpreadAttribute"){let a=f(o.argument,n,t);a&&typeof a=="object"&&!Array.isArray(a)&&Object.assign(r,a);return}let s=d(o.name);if(!o.value){r[s]=true;return}if(o.value.type==="Literal"){r[s]=o.value.value;return}if(o.value.type==="JSXExpressionContainer"){if(o.value.expression.type==="JSXEmptyExpression")return;r[s]=f(o.value.expression,n,t);}}),r},z=(e,n,t,r)=>{let o=k(n,t,r);Object.entries(o).forEach(([s,a])=>{if(s!=="key"){if(s==="children"){l(e,a);return}B(e,s,a);}});},y=(e,n,t)=>{let r=[];return e.forEach(o=>{switch(o.type){case "JSXText":{let s=w(o.value);s&&r.push(s);break}case "JSXExpressionContainer":{if(o.expression.type==="JSXEmptyExpression")break;r.push(f(o.expression,n,t));break}case "JSXSpreadChild":{let s=f(o.expression,n,t);s!=null&&r.push(s);break}case "JSXElement":case "JSXFragment":{r.push(J(o,n,t));break}}}),r},W=(e,n,t,r)=>{let o=k(e.openingElement.attributes,n,r),s=y(e.children,n,r);s.length===1?o.children=s[0]:s.length>1&&(o.children=s);let a=t(o);if(N(a))throw new Error("Async jsx components are not supported.");return a},V=(e,n,t)=>{let r=e.openingElement,o=d(r.name),s=n.components.get(o);if(s)return W(e,n,s,t);if(/[A-Z]/.test(o[0]??""))throw new Error(`Unknown component "${o}". Did you interpolate it with the template literal?`);let a=o==="svg"?"svg":t,i=o==="foreignObject"?null:a,p=a==="svg"?document.createElementNS("http://www.w3.org/2000/svg",o):document.createElement(o);return z(p,r.attributes,n,a),y(e.children,n,i).forEach(u=>l(p,u)),p},J=(e,n,t)=>{if(e.type==="JSXFragment"){let r=document.createDocumentFragment();return y(e.children,n,t).forEach(s=>l(r,s)),r}return V(e,n,t)},H=(e,...n)=>{M();let t=X(e,n),r=parseSync("inline.jsx",t.source,S);if(r.errors.length>0)throw new Error(C(r.errors[0]));let o=b(r.program),s={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(a=>[a.name,a.value]))};return J(o,s,null)};
4
- export{H as jsx};
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.")},x=e=>{switch(e.type){case "JSXIdentifier":return e.name;case "JSXNamespacedName":return `${e.namespace.name}:${e.name.name}`;case "JSXMemberExpression":return `${x(e.object)}.${e.property.name}`;default:return ""}},h=(e,n)=>{if(!e||typeof e!="object")return;let t=e;typeof t.type=="string"&&(n(t),Object.values(t).forEach(r=>{if(r){if(Array.isArray(r)){r.forEach(o=>h(o,n));return}typeof r=="object"&&h(r,n);}}));},X=(e,n)=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," "),r=e.match(/^\s*/)?.[0]??"",o=e.match(/\s*$/)?.[0]??"",a=/\n/.test(r),s=/\n/.test(o),i=t;if(a&&(i=i.replace(/^\s+/,"")),s&&(i=i.replace(/\s+$/,"")),i.length===0||i.trim().length===0)return [];let p=[];S.lastIndex=0;let c=0,l;for(;l=S.exec(i);){let m=l.index,f=i.slice(c,m);f&&p.push(f);let u=l[0];n.has(u)?p.push(n.get(u)):p.push(u),c=m+u.length;}let d=i.slice(c);return d&&p.push(d),p},P=(e,n)=>{let t=new Set;return h(e,r=>{r.type==="Identifier"&&n.placeholders.has(r.name)&&t.add(r.name);}),Array.from(t)},N=(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[r,o]=e.range,a=n.source.slice(r,o),s=P(e,n);try{let i=new Function(...s,`"use strict"; return (${a});`),p=s.map(c=>n.placeholders.get(c));return i(...p)}catch(i){throw new Error(`Failed to evaluate expression ${a}: ${i.message}`)}},I=e=>{let n=e.replace(/[^a-zA-Z0-9_$]/g,"");return n?/[A-Za-z_$]/.test(n[0])?n:`Component${n}`:"Component"},O=(e,n,t)=>{let r=t.get(e);if(r)return r;let o=e.displayName||e.name||`Component${n.length}`,a=I(o??""),s=a,i=1;for(;n.some(c=>c.name===s);)s=`${a}${i++}`;let p={name:s,value:e};return n.push(p),t.set(e,p),p},k=(e,n)=>{let t=e.raw??e,r=new Map,o=[],a=new Map,s=t[0]??"",i=F++,p=0;for(let c=0;c<n.length;c++){let l=t[c]??"",d=t[c+1]??"",m=n[c],f=$.test(l)||_.test(l);if(f&&typeof m=="function"){let j=O(m,o,a);s+=j.name+d;continue}if(f&&typeof m=="string"){s+=m+d;continue}let u=`${C}${i}_${p++}__`;r.set(u,m),s+=u+d;}return {source:s,placeholders:r,bindings:o}};var D=()=>{if(typeof document>"u"||typeof document.createElement!="function")throw new Error("The jsx template tag requires a DOM-like environment (document missing).")},L=e=>typeof Node>"u"?false:e instanceof Node||e instanceof DocumentFragment,B=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",A=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",W=(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 r=t,o=e.style;if(!o)return;let a=o;Object.entries(r).forEach(([s,i])=>{if(i!=null){if(s.startsWith("--")){o.setProperty(s,String(i));return}a[s]=i;}});return}if(typeof t=="function"&&n.startsWith("on")){let r=n.slice(2).toLowerCase();e.addEventListener(r,t);return}if(n==="class"||n==="className"){let r=Array.isArray(t)?t.filter(Boolean).join(" "):String(t);e.setAttribute("class",r);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));}},g=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(A(n))throw new Error("Async values are not supported inside jsx template results.");if(Array.isArray(n)){n.forEach(t=>g(e,t));return}if(B(n)){for(let t of n)g(e,t);return}if(L(n)){e.appendChild(n);return}e.appendChild(document.createTextNode(String(n)));}},y=(e,n,t)=>N(e,n,r=>J(r,n,t)),R=(e,n,t)=>{let r={};return e.forEach(o=>{if(o.type==="JSXSpreadAttribute"){let s=y(o.argument,n,t);s&&typeof s=="object"&&!Array.isArray(s)&&Object.assign(r,s);return}let a=x(o.name);if(!o.value){r[a]=true;return}if(o.value.type==="Literal"){r[a]=o.value.value;return}if(o.value.type==="JSXExpressionContainer"){if(o.value.expression.type==="JSXEmptyExpression")return;r[a]=y(o.value.expression,n,t);}}),r},z=(e,n,t,r)=>{let o=R(n,t,r);Object.entries(o).forEach(([a,s])=>{if(a!=="key"){if(a==="children"){g(e,s);return}W(e,a,s);}});},E=(e,n,t)=>{let r=[];return e.forEach(o=>{switch(o.type){case "JSXText":{X(o.value,n.placeholders).forEach(s=>{r.push(s);});break}case "JSXExpressionContainer":{if(o.expression.type==="JSXEmptyExpression")break;r.push(y(o.expression,n,t));break}case "JSXSpreadChild":{let a=y(o.expression,n,t);a!=null&&r.push(a);break}case "JSXElement":case "JSXFragment":{r.push(J(o,n,t));break}}}),r},V=(e,n,t,r)=>{let o=R(e.openingElement.attributes,n,r),a=E(e.children,n,r);a.length===1?o.children=a[0]:a.length>1&&(o.children=a);let s=t(o);if(A(s))throw new Error("Async jsx components are not supported.");return s},H=(e,n,t)=>{let r=e.openingElement,o=x(r.name),a=n.components.get(o);if(a)return V(e,n,a,t);if(/[A-Z]/.test(o[0]??""))throw new Error(`Unknown component "${o}". Did you interpolate it with the template literal?`);let s=o==="svg"?"svg":t,i=o==="foreignObject"?null:s,p=s==="svg"?document.createElementNS("http://www.w3.org/2000/svg",o):document.createElement(o);return z(p,r.attributes,n,s),E(e.children,n,i).forEach(l=>g(p,l)),p},J=(e,n,t)=>{if(e.type==="JSXFragment"){let r=document.createDocumentFragment();return E(e.children,n,t).forEach(a=>g(r,a)),r}return H(e,n,t)},Z=(e,...n)=>{D();let t=k(e,n),r=parseSync("inline.jsx",t.source,b);if(r.errors.length>0)throw new Error(w(r.errors[0]));let o=T(r.program),a={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(s=>[s.name,s.value]))};return J(o,a,null)};
4
+ export{Z as jsx};
@@ -1,4 +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+=`
1
+ import {parseSync}from'oxc-parser';var C="<!doctype html><html><body></body></html>",$=["window","self","document","HTMLElement","Element","Node","DocumentFragment","customElements","Text","Comment","MutationObserver","navigator"],L=()=>typeof document<"u"&&typeof document.createElement=="function",F=e=>{let n=globalThis,t=e;$.forEach(r=>{n[r]===void 0&&t[r]!==void 0&&(n[r]=t[r]);});},E=async()=>{let{parseHTML:e}=await import('linkedom'),{window:n}=e(C);return n},S=async()=>{let{JSDOM:e}=await import('jsdom'),{window:n}=new e(C);return n},W=()=>{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"},B=()=>{let e=W();return e==="linkedom"?[E,S]:e==="jsdom"?[S,E]:[E,S]},H=async()=>{let e=[];for(let t of B())try{return await t()}catch(r){e.push(r);}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)},x=null,T=async()=>{if(!L())return x||(x=(async()=>{let e=await H();F(e);})().catch(e=>{throw x=null,e})),x};var G=/<\s*$/,z=/<\/\s*$/,X="__KX_EXPR__",N=new RegExp(`${X}\\d+_\\d+__`,"g"),K=0,k={lang:"jsx",sourceType:"module",range:true,preserveParens:true},A=e=>{let n=`[oxc-parser] ${e.message}`;if(e.labels?.length){let t=e.labels[0];t.message&&(n+=`
2
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};
3
+ ${e.codeframe}`),n},R=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.")},y=e=>{switch(e.type){case "JSXIdentifier":return e.name;case "JSXNamespacedName":return `${e.namespace.name}:${e.name.name}`;case "JSXMemberExpression":return `${y(e.object)}.${e.property.name}`;default:return ""}},w=(e,n)=>{if(!e||typeof e!="object")return;let t=e;typeof t.type=="string"&&(n(t),Object.values(t).forEach(r=>{if(r){if(Array.isArray(r)){r.forEach(o=>w(o,n));return}typeof r=="object"&&w(r,n);}}));},_=(e,n)=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," "),r=e.match(/^\s*/)?.[0]??"",o=e.match(/\s*$/)?.[0]??"",a=/\n/.test(r),s=/\n/.test(o),i=t;if(a&&(i=i.replace(/^\s+/,"")),s&&(i=i.replace(/\s+$/,"")),i.length===0||i.trim().length===0)return [];let c=[];N.lastIndex=0;let p=0,m;for(;m=N.exec(i);){let l=m.index,f=i.slice(p,l);f&&c.push(f);let d=m[0];n.has(d)?c.push(n.get(d)):c.push(d),p=l+d.length;}let u=i.slice(p);return u&&c.push(u),c},V=(e,n)=>{let t=new Set;return w(e,r=>{r.type==="Identifier"&&n.placeholders.has(r.name)&&t.add(r.name);}),Array.from(t)},j=(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[r,o]=e.range,a=n.source.slice(r,o),s=V(e,n);try{let i=new Function(...s,`"use strict"; return (${a});`),c=s.map(p=>n.placeholders.get(p));return i(...c)}catch(i){throw new Error(`Failed to evaluate expression ${a}: ${i.message}`)}},U=e=>{let n=e.replace(/[^a-zA-Z0-9_$]/g,"");return n?/[A-Za-z_$]/.test(n[0])?n:`Component${n}`:"Component"},Z=(e,n,t)=>{let r=t.get(e);if(r)return r;let o=e.displayName||e.name||`Component${n.length}`,a=U(o??""),s=a,i=1;for(;n.some(p=>p.name===s);)s=`${a}${i++}`;let c={name:s,value:e};return n.push(c),t.set(e,c),c},P=(e,n)=>{let t=e.raw??e,r=new Map,o=[],a=new Map,s=t[0]??"",i=K++,c=0;for(let p=0;p<n.length;p++){let m=t[p]??"",u=t[p+1]??"",l=n[p],f=G.test(m)||z.test(m);if(f&&typeof l=="function"){let D=Z(l,o,a);s+=D.name+u;continue}if(f&&typeof l=="string"){s+=l+u;continue}let d=`${X}${i}_${c++}__`;r.set(d,l),s+=d+u;}return {source:s,placeholders:r,bindings:o}};var Y=()=>{if(typeof document>"u"||typeof document.createElement!="function")throw new Error("The jsx template tag requires a DOM-like environment (document missing).")},Q=e=>typeof Node>"u"?false:e instanceof Node||e instanceof DocumentFragment,v=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",O=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",ee=(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 r=t,o=e.style;if(!o)return;let a=o;Object.entries(r).forEach(([s,i])=>{if(i!=null){if(s.startsWith("--")){o.setProperty(s,String(i));return}a[s]=i;}});return}if(typeof t=="function"&&n.startsWith("on")){let r=n.slice(2).toLowerCase();e.addEventListener(r,t);return}if(n==="class"||n==="className"){let r=Array.isArray(t)?t.filter(Boolean).join(" "):String(t);e.setAttribute("class",r);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));}},g=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(O(n))throw new Error("Async values are not supported inside jsx template results.");if(Array.isArray(n)){n.forEach(t=>g(e,t));return}if(v(n)){for(let t of n)g(e,t);return}if(Q(n)){e.appendChild(n);return}e.appendChild(document.createTextNode(String(n)));}},h=(e,n,t)=>j(e,n,r=>b(r,n,t)),I=(e,n,t)=>{let r={};return e.forEach(o=>{if(o.type==="JSXSpreadAttribute"){let s=h(o.argument,n,t);s&&typeof s=="object"&&!Array.isArray(s)&&Object.assign(r,s);return}let a=y(o.name);if(!o.value){r[a]=true;return}if(o.value.type==="Literal"){r[a]=o.value.value;return}if(o.value.type==="JSXExpressionContainer"){if(o.value.expression.type==="JSXEmptyExpression")return;r[a]=h(o.value.expression,n,t);}}),r},ne=(e,n,t,r)=>{let o=I(n,t,r);Object.entries(o).forEach(([a,s])=>{if(a!=="key"){if(a==="children"){g(e,s);return}ee(e,a,s);}});},J=(e,n,t)=>{let r=[];return e.forEach(o=>{switch(o.type){case "JSXText":{_(o.value,n.placeholders).forEach(s=>{r.push(s);});break}case "JSXExpressionContainer":{if(o.expression.type==="JSXEmptyExpression")break;r.push(h(o.expression,n,t));break}case "JSXSpreadChild":{let a=h(o.expression,n,t);a!=null&&r.push(a);break}case "JSXElement":case "JSXFragment":{r.push(b(o,n,t));break}}}),r},te=(e,n,t,r)=>{let o=I(e.openingElement.attributes,n,r),a=J(e.children,n,r);a.length===1?o.children=a[0]:a.length>1&&(o.children=a);let s=t(o);if(O(s))throw new Error("Async jsx components are not supported.");return s},re=(e,n,t)=>{let r=e.openingElement,o=y(r.name),a=n.components.get(o);if(a)return te(e,n,a,t);if(/[A-Z]/.test(o[0]??""))throw new Error(`Unknown component "${o}". Did you interpolate it with the template literal?`);let s=o==="svg"?"svg":t,i=o==="foreignObject"?null:s,c=s==="svg"?document.createElementNS("http://www.w3.org/2000/svg",o):document.createElement(o);return ne(c,r.attributes,n,s),J(e.children,n,i).forEach(m=>g(c,m)),c},b=(e,n,t)=>{if(e.type==="JSXFragment"){let r=document.createDocumentFragment();return J(e.children,n,t).forEach(a=>g(r,a)),r}return re(e,n,t)},M=(e,...n)=>{Y();let t=P(e,n),r=parseSync("inline.jsx",t.source,k);if(r.errors.length>0)throw new Error(A(r.errors[0]));let o=R(r.program),a={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(s=>[s.name,s.value]))};return b(o,a,null)};await T();var le=M;
4
+ export{le as jsx};
@@ -1,3 +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+=`
1
+ import {parseSync}from'oxc-parser';import {createElement,Fragment}from'react';var F=/<\s*$/,P=/<\/\s*$/,h="__KX_EXPR__",C=new RegExp(`${h}\\d+_\\d+__`,"g"),_=0,S={lang:"jsx",sourceType:"module",range:true,preserveParens:true},T=e=>{let n=`[oxc-parser] ${e.message}`;if(e.labels?.length){let t=e.labels[0];t.message&&(n+=`
2
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};
3
+ ${e.codeframe}`),n},w=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(r=>{if(r){if(Array.isArray(r)){r.forEach(o=>E(o,n));return}typeof r=="object"&&E(r,n);}}));},X=(e,n)=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," "),r=e.match(/^\s*/)?.[0]??"",o=e.match(/\s*$/)?.[0]??"",s=/\n/.test(r),a=/\n/.test(o),p=t;if(s&&(p=p.replace(/^\s+/,"")),a&&(p=p.replace(/\s+$/,"")),p.length===0||p.trim().length===0)return [];let c=[];C.lastIndex=0;let i=0,u;for(;u=C.exec(p);){let m=u.index,d=p.slice(i,m);d&&c.push(d);let l=u[0];n.has(l)?c.push(n.get(l)):c.push(l),i=m+l.length;}let g=p.slice(i);return g&&c.push(g),c},I=(e,n)=>{let t=new Set;return E(e,r=>{r.type==="Identifier"&&n.placeholders.has(r.name)&&t.add(r.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[r,o]=e.range,s=n.source.slice(r,o),a=I(e,n);try{let p=new Function(...a,`"use strict"; return (${s});`),c=a.map(i=>n.placeholders.get(i));return p(...c)}catch(p){throw new Error(`Failed to evaluate expression ${s}: ${p.message}`)}},O=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 r=t.get(e);if(r)return r;let o=e.displayName||e.name||`Component${n.length}`,s=O(o??""),a=s,p=1;for(;n.some(i=>i.name===a);)a=`${s}${p++}`;let c={name:a,value:e};return n.push(c),t.set(e,c),c},k=(e,n)=>{let t=e.raw??e,r=new Map,o=[],s=new Map,a=t[0]??"",p=_++,c=0;for(let i=0;i<n.length;i++){let u=t[i]??"",g=t[i+1]??"",m=n[i],d=F.test(u)||P.test(u);if(d&&typeof m=="function"){let $=j(m,o,s);a+=$.name+g;continue}if(d&&typeof m=="string"){a+=m+g;continue}let l=`${h}${p}_${c++}__`;r.set(l,m),a+=l+g;}return {source:a,placeholders:r,bindings:o}};var z=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",L=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",x=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(L(n))throw new Error("Async values are not supported inside reactJsx template results.");if(Array.isArray(n)){n.forEach(t=>x(e,t));return}if(z(n)){for(let t of n)x(e,t);return}e.push(n);}},y=(e,n)=>R(e,n,t=>J(t,n)),v=(e,n)=>{let t={};return e.forEach(r=>{if(r.type==="JSXSpreadAttribute"){let s=y(r.argument,n);s&&typeof s=="object"&&!Array.isArray(s)&&Object.assign(t,s);return}let o=f(r.name);if(!r.value){t[o]=true;return}if(r.value.type==="Literal"){t[o]=r.value.value;return}if(r.value.type==="JSXExpressionContainer"){if(r.value.expression.type==="JSXEmptyExpression")return;t[o]=y(r.value.expression,n);}}),t},N=(e,n)=>{let t=[];return e.forEach(r=>{switch(r.type){case "JSXText":{X(r.value,n.placeholders).forEach(s=>x(t,s));break}case "JSXExpressionContainer":{if(r.expression.type==="JSXEmptyExpression")break;x(t,y(r.expression,n));break}case "JSXSpreadChild":{let o=y(r.expression,n);o!=null&&x(t,o);break}case "JSXElement":case "JSXFragment":{t.push(J(r,n));break}}}),t},A=(e,n,t)=>createElement(e,n,...t),V=(e,n)=>{let t=e.openingElement,r=f(t.name),o=n.components.get(r),s=v(t.attributes,n),a=N(e.children,n);if(o)return A(o,s,a);if(/[A-Z]/.test(r[0]??""))throw new Error(`Unknown component "${r}". Did you interpolate it with the template literal?`);return A(r,s,a)},J=(e,n)=>{if(e.type==="JSXFragment"){let t=N(e.children,n);return createElement(Fragment,null,...t)}return V(e,n)},Z=(e,...n)=>{let t=k(e,n),r=parseSync("inline.jsx",t.source,S);if(r.errors.length>0)throw new Error(T(r.errors[0]));let o=w(r.program),s={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(a=>[a.name,a.value]))};return J(o,s)};export{Z as reactJsx};
@@ -1,3 +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+=`
1
+ import {parseSync}from'oxc-parser';import {createElement,Fragment}from'react';var F=/<\s*$/,P=/<\/\s*$/,h="__KX_EXPR__",C=new RegExp(`${h}\\d+_\\d+__`,"g"),_=0,S={lang:"jsx",sourceType:"module",range:true,preserveParens:true},T=e=>{let n=`[oxc-parser] ${e.message}`;if(e.labels?.length){let t=e.labels[0];t.message&&(n+=`
2
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};
3
+ ${e.codeframe}`),n},w=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(r=>{if(r){if(Array.isArray(r)){r.forEach(o=>E(o,n));return}typeof r=="object"&&E(r,n);}}));},X=(e,n)=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," "),r=e.match(/^\s*/)?.[0]??"",o=e.match(/\s*$/)?.[0]??"",s=/\n/.test(r),a=/\n/.test(o),p=t;if(s&&(p=p.replace(/^\s+/,"")),a&&(p=p.replace(/\s+$/,"")),p.length===0||p.trim().length===0)return [];let c=[];C.lastIndex=0;let i=0,u;for(;u=C.exec(p);){let m=u.index,d=p.slice(i,m);d&&c.push(d);let l=u[0];n.has(l)?c.push(n.get(l)):c.push(l),i=m+l.length;}let g=p.slice(i);return g&&c.push(g),c},I=(e,n)=>{let t=new Set;return E(e,r=>{r.type==="Identifier"&&n.placeholders.has(r.name)&&t.add(r.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[r,o]=e.range,s=n.source.slice(r,o),a=I(e,n);try{let p=new Function(...a,`"use strict"; return (${s});`),c=a.map(i=>n.placeholders.get(i));return p(...c)}catch(p){throw new Error(`Failed to evaluate expression ${s}: ${p.message}`)}},O=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 r=t.get(e);if(r)return r;let o=e.displayName||e.name||`Component${n.length}`,s=O(o??""),a=s,p=1;for(;n.some(i=>i.name===a);)a=`${s}${p++}`;let c={name:a,value:e};return n.push(c),t.set(e,c),c},k=(e,n)=>{let t=e.raw??e,r=new Map,o=[],s=new Map,a=t[0]??"",p=_++,c=0;for(let i=0;i<n.length;i++){let u=t[i]??"",g=t[i+1]??"",m=n[i],d=F.test(u)||P.test(u);if(d&&typeof m=="function"){let $=j(m,o,s);a+=$.name+g;continue}if(d&&typeof m=="string"){a+=m+g;continue}let l=`${h}${p}_${c++}__`;r.set(l,m),a+=l+g;}return {source:a,placeholders:r,bindings:o}};var z=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",L=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",x=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(L(n))throw new Error("Async values are not supported inside reactJsx template results.");if(Array.isArray(n)){n.forEach(t=>x(e,t));return}if(z(n)){for(let t of n)x(e,t);return}e.push(n);}},y=(e,n)=>R(e,n,t=>J(t,n)),v=(e,n)=>{let t={};return e.forEach(r=>{if(r.type==="JSXSpreadAttribute"){let s=y(r.argument,n);s&&typeof s=="object"&&!Array.isArray(s)&&Object.assign(t,s);return}let o=f(r.name);if(!r.value){t[o]=true;return}if(r.value.type==="Literal"){t[o]=r.value.value;return}if(r.value.type==="JSXExpressionContainer"){if(r.value.expression.type==="JSXEmptyExpression")return;t[o]=y(r.value.expression,n);}}),t},N=(e,n)=>{let t=[];return e.forEach(r=>{switch(r.type){case "JSXText":{X(r.value,n.placeholders).forEach(s=>x(t,s));break}case "JSXExpressionContainer":{if(r.expression.type==="JSXEmptyExpression")break;x(t,y(r.expression,n));break}case "JSXSpreadChild":{let o=y(r.expression,n);o!=null&&x(t,o);break}case "JSXElement":case "JSXFragment":{t.push(J(r,n));break}}}),t},A=(e,n,t)=>createElement(e,n,...t),V=(e,n)=>{let t=e.openingElement,r=f(t.name),o=n.components.get(r),s=v(t.attributes,n),a=N(e.children,n);if(o)return A(o,s,a);if(/[A-Z]/.test(r[0]??""))throw new Error(`Unknown component "${r}". Did you interpolate it with the template literal?`);return A(r,s,a)},J=(e,n)=>{if(e.type==="JSXFragment"){let t=N(e.children,n);return createElement(Fragment,null,...t)}return V(e,n)},Z=(e,...n)=>{let t=k(e,n),r=parseSync("inline.jsx",t.source,S);if(r.errors.length>0)throw new Error(T(r.errors[0]));let o=w(r.program),s={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(a=>[a.name,a.value]))};return J(o,s)};export{Z as reactJsx};
@@ -25,10 +25,14 @@ class ReactTemplateBuilder {
25
25
  children.forEach(child => {
26
26
  switch (child.type) {
27
27
  case 'JSXText': {
28
- const text = normalizeJsxTextValue(child.value);
29
- if (text) {
30
- compiled.push(JSON.stringify(text));
31
- }
28
+ const segments = normalizeJsxTextSegments(child.value, this.placeholderMap);
29
+ segments.forEach(segment => {
30
+ if (segment.kind === 'text') {
31
+ compiled.push(JSON.stringify(segment.value));
32
+ return;
33
+ }
34
+ compiled.push(segment.value);
35
+ });
32
36
  break;
33
37
  }
34
38
  case 'JSXExpressionContainer': {
@@ -148,6 +152,9 @@ class ReactTemplateBuilder {
148
152
  }
149
153
  return node.name;
150
154
  }
155
+ if (node.type === 'Literal') {
156
+ return JSON.stringify(node.value);
157
+ }
151
158
  if ('range' in node && Array.isArray(node.range)) {
152
159
  throw new Error('[jsx-loader] Unable to inline complex expressions in react mode.');
153
160
  }
@@ -192,6 +199,7 @@ const getTemplateExpressionContext = (left, right) => {
192
199
  return { type: 'childText' };
193
200
  };
194
201
  const TEMPLATE_EXPR_PLACEHOLDER_PREFIX = '__JSX_LOADER_TEMPLATE_EXPR_';
202
+ const TEMPLATE_EXPR_PLACEHOLDER_PATTERN = new RegExp(`${TEMPLATE_EXPR_PLACEHOLDER_PREFIX}\\d+__`, 'g');
195
203
  const MODULE_PARSER_OPTIONS = {
196
204
  lang: 'tsx',
197
205
  sourceType: 'module',
@@ -386,10 +394,47 @@ const extractJsxRoot = (program) => {
386
394
  }
387
395
  throw new Error('[jsx-loader] Expected the template to contain a single JSX root node.');
388
396
  };
389
- const normalizeJsxTextValue = (value) => {
397
+ const normalizeJsxTextSegments = (value, placeholders) => {
390
398
  const collapsed = value.replace(/\r/g, '').replace(/\n\s+/g, ' ');
391
- const trimmed = collapsed.trim();
392
- return trimmed.length > 0 ? trimmed : '';
399
+ const leadingWhitespace = value.match(/^\s*/)?.[0] ?? '';
400
+ const trailingWhitespace = value.match(/\s*$/)?.[0] ?? '';
401
+ const trimStart = /\n/.test(leadingWhitespace);
402
+ const trimEnd = /\n/.test(trailingWhitespace);
403
+ let normalized = collapsed;
404
+ if (trimStart) {
405
+ normalized = normalized.replace(/^\s+/, '');
406
+ }
407
+ if (trimEnd) {
408
+ normalized = normalized.replace(/\s+$/, '');
409
+ }
410
+ if (normalized.length === 0 || normalized.trim().length === 0) {
411
+ return [];
412
+ }
413
+ const segments = [];
414
+ TEMPLATE_EXPR_PLACEHOLDER_PATTERN.lastIndex = 0;
415
+ let cursor = 0;
416
+ let match;
417
+ while ((match = TEMPLATE_EXPR_PLACEHOLDER_PATTERN.exec(normalized))) {
418
+ const index = match.index;
419
+ const slice = normalized.slice(cursor, index);
420
+ if (slice) {
421
+ segments.push({ kind: 'text', value: slice });
422
+ }
423
+ const marker = match[0];
424
+ const expression = placeholders.get(marker);
425
+ if (expression) {
426
+ segments.push({ kind: 'expression', value: expression });
427
+ }
428
+ else {
429
+ segments.push({ kind: 'text', value: marker });
430
+ }
431
+ cursor = index + marker.length;
432
+ }
433
+ const remainder = normalized.slice(cursor);
434
+ if (remainder) {
435
+ segments.push({ kind: 'text', value: remainder });
436
+ }
437
+ return segments;
393
438
  };
394
439
  const TAG_PLACEHOLDER_PREFIX = '__JSX_LOADER_TAG_EXPR_';
395
440
  const buildTemplateSource = (quasis, expressions, source, tag) => {
@@ -1,5 +1,5 @@
1
1
  import { parseSync } from 'oxc-parser';
2
- import { buildTemplate, evaluateExpression, extractRootNode, formatParserError, getIdentifierName, normalizeJsxText, parserOptions, } from '../runtime/shared.js';
2
+ import { buildTemplate, evaluateExpression, extractRootNode, formatParserError, getIdentifierName, normalizeJsxTextSegments, parserOptions, } from '../runtime/shared.js';
3
3
  import { Fragment, createElement, } from 'react';
4
4
  const isIterable = (value) => {
5
5
  if (!value || typeof value === 'string') {
@@ -69,10 +69,8 @@ const evaluateReactJsxChildren = (children, ctx) => {
69
69
  children.forEach(child => {
70
70
  switch (child.type) {
71
71
  case 'JSXText': {
72
- const text = normalizeJsxText(child.value);
73
- if (text) {
74
- resolved.push(text);
75
- }
72
+ const segments = normalizeJsxTextSegments(child.value, ctx.placeholders);
73
+ segments.forEach(segment => appendReactChild(resolved, segment));
76
74
  break;
77
75
  }
78
76
  case 'JSXExpressionContainer': {
@@ -1,5 +1,7 @@
1
1
  import type { Expression, JSXElement, JSXFragment, JSXIdentifier, JSXMemberExpression, JSXNamespacedName, Program } from '@oxc-project/types';
2
2
  import type { OxcError, ParserOptions } from 'oxc-parser';
3
+ export declare const PLACEHOLDER_PREFIX = "__KX_EXPR__";
4
+ export declare const placeholderPattern: RegExp;
3
5
  type AnyTemplateFunction = (...args: any[]) => unknown;
4
6
  type AnyTemplateConstructor = abstract new (...args: any[]) => unknown;
5
7
  export type TemplateComponent = (AnyTemplateFunction | AnyTemplateConstructor) & {
@@ -29,7 +31,7 @@ type AnyOxcNode = {
29
31
  [key: string]: unknown;
30
32
  };
31
33
  export declare const walkAst: (node: unknown, visitor: (target: AnyOxcNode) => void) => void;
32
- export declare const normalizeJsxText: (value: string) => string;
34
+ export declare const normalizeJsxTextSegments: (value: string, placeholders: Map<string, unknown>) => unknown[];
33
35
  export declare const collectPlaceholderNames: <TComponent extends TemplateComponent>(expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>) => string[];
34
36
  export declare const evaluateExpression: <TComponent extends TemplateComponent>(expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>, evaluateJsxNode: (node: JSXElement | JSXFragment) => unknown) => unknown;
35
37
  export declare const sanitizeIdentifier: (value: string) => string;
@@ -1,6 +1,7 @@
1
1
  const OPEN_TAG_RE = /<\s*$/;
2
2
  const CLOSE_TAG_RE = /<\/\s*$/;
3
- const PLACEHOLDER_PREFIX = '__KX_EXPR__';
3
+ export const PLACEHOLDER_PREFIX = '__KX_EXPR__';
4
+ export const placeholderPattern = new RegExp(`${PLACEHOLDER_PREFIX}\\d+_\\d+__`, 'g');
4
5
  let invocationCounter = 0;
5
6
  export const parserOptions = {
6
7
  lang: 'jsx',
@@ -66,10 +67,46 @@ export const walkAst = (node, visitor) => {
66
67
  }
67
68
  });
68
69
  };
69
- export const normalizeJsxText = (value) => {
70
+ export const normalizeJsxTextSegments = (value, placeholders) => {
70
71
  const collapsed = value.replace(/\r/g, '').replace(/\n\s+/g, ' ');
71
- const trimmed = collapsed.trim();
72
- return trimmed.length > 0 ? trimmed : '';
72
+ const leadingWhitespace = value.match(/^\s*/)?.[0] ?? '';
73
+ const trailingWhitespace = value.match(/\s*$/)?.[0] ?? '';
74
+ const trimStart = /\n/.test(leadingWhitespace);
75
+ const trimEnd = /\n/.test(trailingWhitespace);
76
+ let normalized = collapsed;
77
+ if (trimStart) {
78
+ normalized = normalized.replace(/^\s+/, '');
79
+ }
80
+ if (trimEnd) {
81
+ normalized = normalized.replace(/\s+$/, '');
82
+ }
83
+ if (normalized.length === 0 || normalized.trim().length === 0) {
84
+ return [];
85
+ }
86
+ const segments = [];
87
+ placeholderPattern.lastIndex = 0;
88
+ let cursor = 0;
89
+ let match;
90
+ while ((match = placeholderPattern.exec(normalized))) {
91
+ const index = match.index;
92
+ const slice = normalized.slice(cursor, index);
93
+ if (slice) {
94
+ segments.push(slice);
95
+ }
96
+ const token = match[0];
97
+ if (placeholders.has(token)) {
98
+ segments.push(placeholders.get(token));
99
+ }
100
+ else {
101
+ segments.push(token);
102
+ }
103
+ cursor = index + token.length;
104
+ }
105
+ const remainder = normalized.slice(cursor);
106
+ if (remainder) {
107
+ segments.push(remainder);
108
+ }
109
+ return segments;
73
110
  };
74
111
  export const collectPlaceholderNames = (expression, ctx) => {
75
112
  const placeholders = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/jsx",
3
- "version": "1.2.0-rc.4",
3
+ "version": "1.2.0",
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",
@@ -65,17 +65,19 @@
65
65
  "./package.json": "./package.json"
66
66
  },
67
67
  "engines": {
68
- "node": ">=22"
68
+ "node": ">=22.3.0"
69
69
  },
70
70
  "engineStrict": true,
71
71
  "scripts": {
72
72
  "build": "duel && npm run build:lite",
73
+ "prepare": "husky",
73
74
  "precheck-types": "npm run build",
74
75
  "check-types": "npm run check-types:lib && npm run check-types:demo",
75
76
  "check-types:lib": "tsc --noEmit --project tsconfig.json",
76
77
  "check-types:demo": "tsc --noEmit --project examples/browser/tsconfig.json",
77
78
  "lint": "eslint src test",
78
79
  "prettier": "prettier -w .",
80
+ "prettier:check": "prettier --check .",
79
81
  "test": "vitest run --coverage",
80
82
  "test:watch": "vitest",
81
83
  "test:e2e": "npm run build && npm run setup:wasm && npm run build:fixture && playwright test",
@@ -103,7 +105,9 @@
103
105
  "eslint-plugin-n": "^17.10.3",
104
106
  "eslint-plugin-playwright": "^2.4.0",
105
107
  "http-server": "^14.1.1",
108
+ "husky": "^9.1.7",
106
109
  "jsdom": "^27.2.0",
110
+ "lint-staged": "^16.2.7",
107
111
  "lit": "^3.2.1",
108
112
  "next": "^16.0.0",
109
113
  "prettier": "^3.7.3",
@@ -158,5 +162,9 @@
158
162
  "printWidth": 90,
159
163
  "semi": false,
160
164
  "singleQuote": true
165
+ },
166
+ "lint-staged": {
167
+ "*.{js,jsx,ts,tsx,mjs,cjs,cts,mts}": "eslint --max-warnings=0",
168
+ "*.{js,jsx,ts,tsx,mjs,cjs,cts,mts,json,md,css,scss,html}": "prettier --check"
161
169
  }
162
170
  }