@knighted/jsx 1.1.0 → 1.2.0-rc.1

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/dist/jsx.js CHANGED
@@ -1,55 +1,10 @@
1
1
  import { parseSync } from 'oxc-parser';
2
- const OPEN_TAG_RE = /<\s*$/;
3
- const CLOSE_TAG_RE = /<\/\s*$/;
4
- const PLACEHOLDER_PREFIX = '__KX_EXPR__';
5
- let invocationCounter = 0;
6
- const parserOptions = {
7
- lang: 'jsx',
8
- sourceType: 'module',
9
- range: true,
10
- preserveParens: true,
11
- };
2
+ import { buildTemplate, evaluateExpression, extractRootNode, formatParserError, getIdentifierName, normalizeJsxText, parserOptions, } from './runtime/shared.js';
12
3
  const ensureDomAvailable = () => {
13
4
  if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
14
5
  throw new Error('The jsx template tag requires a DOM-like environment (document missing).');
15
6
  }
16
7
  };
17
- const formatParserError = (error) => {
18
- let message = `[oxc-parser] ${error.message}`;
19
- if (error.labels?.length) {
20
- const label = error.labels[0];
21
- if (label.message) {
22
- message += `\n${label.message}`;
23
- }
24
- }
25
- if (error.codeframe) {
26
- message += `\n${error.codeframe}`;
27
- }
28
- return message;
29
- };
30
- const extractRootNode = (program) => {
31
- for (const statement of program.body) {
32
- if (statement.type === 'ExpressionStatement') {
33
- const expression = statement.expression;
34
- if (expression.type === 'JSXElement' || expression.type === 'JSXFragment') {
35
- return expression;
36
- }
37
- }
38
- }
39
- throw new Error('The jsx template must contain a single JSX element or fragment.');
40
- };
41
- const getIdentifierName = (identifier) => {
42
- switch (identifier.type) {
43
- case 'JSXIdentifier':
44
- return identifier.name;
45
- case 'JSXNamespacedName':
46
- return `${identifier.namespace.name}:${identifier.name.name}`;
47
- case 'JSXMemberExpression':
48
- return `${getIdentifierName(identifier.object)}.${identifier.property.name}`;
49
- default:
50
- return '';
51
- }
52
- };
53
8
  const isNodeLike = (value) => {
54
9
  if (typeof Node === 'undefined') {
55
10
  return false;
@@ -68,11 +23,6 @@ const isPromiseLike = (value) => {
68
23
  }
69
24
  return typeof value.then === 'function';
70
25
  };
71
- const normalizeJsxText = (value) => {
72
- const collapsed = value.replace(/\r/g, '').replace(/\n\s+/g, ' ');
73
- const trimmed = collapsed.trim();
74
- return trimmed.length > 0 ? trimmed : '';
75
- };
76
26
  const setDomProp = (element, name, value) => {
77
27
  if (value === false || value === null || value === undefined) {
78
28
  return;
@@ -162,11 +112,12 @@ const appendChildValue = (parent, value) => {
162
112
  }
163
113
  parent.appendChild(document.createTextNode(String(value)));
164
114
  };
165
- const resolveAttributes = (attributes, ctx) => {
115
+ const evaluateExpressionWithNamespace = (expression, ctx, namespace) => evaluateExpression(expression, ctx, node => evaluateJsxNode(node, ctx, namespace));
116
+ const resolveAttributes = (attributes, ctx, namespace) => {
166
117
  const props = {};
167
118
  attributes.forEach(attribute => {
168
119
  if (attribute.type === 'JSXSpreadAttribute') {
169
- const spreadValue = evaluateExpression(attribute.argument, ctx);
120
+ const spreadValue = evaluateExpressionWithNamespace(attribute.argument, ctx, namespace);
170
121
  if (spreadValue && typeof spreadValue === 'object' && !Array.isArray(spreadValue)) {
171
122
  Object.assign(props, spreadValue);
172
123
  }
@@ -185,13 +136,13 @@ const resolveAttributes = (attributes, ctx) => {
185
136
  if (attribute.value.expression.type === 'JSXEmptyExpression') {
186
137
  return;
187
138
  }
188
- props[name] = evaluateExpression(attribute.value.expression, ctx);
139
+ props[name] = evaluateExpressionWithNamespace(attribute.value.expression, ctx, namespace);
189
140
  }
190
141
  });
191
142
  return props;
192
143
  };
193
- const applyDomAttributes = (element, attributes, ctx) => {
194
- const props = resolveAttributes(attributes, ctx);
144
+ const applyDomAttributes = (element, attributes, ctx, namespace) => {
145
+ const props = resolveAttributes(attributes, ctx, namespace);
195
146
  Object.entries(props).forEach(([name, value]) => {
196
147
  if (name === 'key') {
197
148
  return;
@@ -218,11 +169,11 @@ const evaluateJsxChildren = (children, ctx, namespace) => {
218
169
  if (child.expression.type === 'JSXEmptyExpression') {
219
170
  break;
220
171
  }
221
- resolved.push(evaluateExpression(child.expression, ctx));
172
+ resolved.push(evaluateExpressionWithNamespace(child.expression, ctx, namespace));
222
173
  break;
223
174
  }
224
175
  case 'JSXSpreadChild': {
225
- const spreadValue = evaluateExpression(child.expression, ctx);
176
+ const spreadValue = evaluateExpressionWithNamespace(child.expression, ctx, namespace);
226
177
  if (spreadValue !== undefined && spreadValue !== null) {
227
178
  resolved.push(spreadValue);
228
179
  }
@@ -238,7 +189,7 @@ const evaluateJsxChildren = (children, ctx, namespace) => {
238
189
  return resolved;
239
190
  };
240
191
  const evaluateComponent = (element, ctx, component, namespace) => {
241
- const props = resolveAttributes(element.openingElement.attributes, ctx);
192
+ const props = resolveAttributes(element.openingElement.attributes, ctx, namespace);
242
193
  const childValues = evaluateJsxChildren(element.children, ctx, namespace);
243
194
  if (childValues.length === 1) {
244
195
  props.children = childValues[0];
@@ -267,7 +218,7 @@ const evaluateJsxElement = (element, ctx, namespace) => {
267
218
  const domElement = nextNamespace === 'svg'
268
219
  ? document.createElementNS('http://www.w3.org/2000/svg', tagName)
269
220
  : document.createElement(tagName);
270
- applyDomAttributes(domElement, opening.attributes, ctx);
221
+ applyDomAttributes(domElement, opening.attributes, ctx, nextNamespace);
271
222
  const childValues = evaluateJsxChildren(element.children, ctx, childNamespace);
272
223
  childValues.forEach(value => appendChildValue(domElement, value));
273
224
  return domElement;
@@ -281,111 +232,6 @@ const evaluateJsxNode = (node, ctx, namespace) => {
281
232
  }
282
233
  return evaluateJsxElement(node, ctx, namespace);
283
234
  };
284
- const walkAst = (node, visitor) => {
285
- if (!node || typeof node !== 'object') {
286
- return;
287
- }
288
- const candidate = node;
289
- if (typeof candidate.type !== 'string') {
290
- return;
291
- }
292
- visitor(candidate);
293
- Object.values(candidate).forEach(value => {
294
- if (!value) {
295
- return;
296
- }
297
- if (Array.isArray(value)) {
298
- value.forEach(child => walkAst(child, visitor));
299
- return;
300
- }
301
- if (typeof value === 'object') {
302
- walkAst(value, visitor);
303
- }
304
- });
305
- };
306
- const collectPlaceholderNames = (expression, ctx) => {
307
- const placeholders = new Set();
308
- walkAst(expression, node => {
309
- if (node.type === 'Identifier' && ctx.placeholders.has(node.name)) {
310
- placeholders.add(node.name);
311
- }
312
- });
313
- return Array.from(placeholders);
314
- };
315
- const evaluateExpression = (expression, ctx) => {
316
- if (expression.type === 'JSXElement' || expression.type === 'JSXFragment') {
317
- return evaluateJsxNode(expression, ctx, null);
318
- }
319
- if (!('range' in expression) || !expression.range) {
320
- throw new Error('Unable to evaluate expression: missing source range information.');
321
- }
322
- const [start, end] = expression.range;
323
- const source = ctx.source.slice(start, end);
324
- const placeholders = collectPlaceholderNames(expression, ctx);
325
- try {
326
- const evaluator = new Function(...placeholders, `"use strict"; return (${source});`);
327
- const args = placeholders.map(name => ctx.placeholders.get(name));
328
- return evaluator(...args);
329
- }
330
- catch (error) {
331
- throw new Error(`Failed to evaluate expression ${source}: ${error.message}`);
332
- }
333
- };
334
- const sanitizeIdentifier = (value) => {
335
- const cleaned = value.replace(/[^a-zA-Z0-9_$]/g, '');
336
- if (!cleaned) {
337
- return 'Component';
338
- }
339
- if (!/[A-Za-z_$]/.test(cleaned[0])) {
340
- return `Component${cleaned}`;
341
- }
342
- return cleaned;
343
- };
344
- const ensureBinding = (value, bindings, bindingLookup) => {
345
- const existing = bindingLookup.get(value);
346
- if (existing) {
347
- return existing;
348
- }
349
- const descriptor = value.displayName || value.name || `Component${bindings.length}`;
350
- const baseName = sanitizeIdentifier(descriptor);
351
- let candidate = baseName;
352
- let suffix = 1;
353
- while (bindings.some(binding => binding.name === candidate)) {
354
- candidate = `${baseName}${suffix++}`;
355
- }
356
- const binding = { name: candidate, value };
357
- bindings.push(binding);
358
- bindingLookup.set(value, binding);
359
- return binding;
360
- };
361
- const buildTemplate = (strings, values) => {
362
- const raw = strings.raw ?? strings;
363
- const placeholders = new Map();
364
- const bindings = [];
365
- const bindingLookup = new Map();
366
- let source = raw[0] ?? '';
367
- const templateId = invocationCounter++;
368
- let placeholderIndex = 0;
369
- for (let idx = 0; idx < values.length; idx++) {
370
- const chunk = raw[idx] ?? '';
371
- const nextChunk = raw[idx + 1] ?? '';
372
- const value = values[idx];
373
- const isTagNamePosition = OPEN_TAG_RE.test(chunk) || CLOSE_TAG_RE.test(chunk);
374
- if (isTagNamePosition && typeof value === 'function') {
375
- const binding = ensureBinding(value, bindings, bindingLookup);
376
- source += binding.name + nextChunk;
377
- continue;
378
- }
379
- if (isTagNamePosition && typeof value === 'string') {
380
- source += value + nextChunk;
381
- continue;
382
- }
383
- const placeholder = `${PLACEHOLDER_PREFIX}${templateId}_${placeholderIndex++}__`;
384
- placeholders.set(placeholder, value);
385
- source += placeholder + nextChunk;
386
- }
387
- return { source, placeholders, bindings };
388
- };
389
235
  export const jsx = (templates, ...values) => {
390
236
  ensureDomAvailable();
391
237
  const build = buildTemplate(templates, values);
@@ -1,4 +1,4 @@
1
- import {parseSync}from'oxc-parser';var N=/<\s*$/,C=/<\/\s*$/,k="__KX_EXPR__",A=0,R={lang:"jsx",sourceType:"module",range:true,preserveParens:true},j=()=>{if(typeof document>"u"||typeof document.createElement!="function")throw new Error("The jsx template tag requires a DOM-like environment (document missing).")},$=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 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+=`
2
2
  ${t.message}`);}return e.codeframe&&(n+=`
3
- ${e.codeframe}`),n},_=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 ""}},P=e=>typeof Node>"u"?false:e instanceof Node||e instanceof DocumentFragment,O=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",S=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",F=e=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," ").trim();return t.length>0?t:""},I=(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,s=e.style;if(!s)return;let o=s;Object.entries(r).forEach(([a,i])=>{if(i!=null){if(a.startsWith("--")){s.setProperty(a,String(i));return}o[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));}},u=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(S(n))throw new Error("Async values are not supported inside jsx template results.");if(Array.isArray(n)){n.forEach(t=>u(e,t));return}if(O(n)){for(let t of n)u(e,t);return}if(P(n)){e.appendChild(n);return}e.appendChild(document.createTextNode(String(n)));}},b=(e,n)=>{let t={};return e.forEach(r=>{if(r.type==="JSXSpreadAttribute"){let o=d(r.argument,n);o&&typeof o=="object"&&!Array.isArray(o)&&Object.assign(t,o);return}let s=y(r.name);if(!r.value){t[s]=true;return}if(r.value.type==="Literal"){t[s]=r.value.value;return}if(r.value.type==="JSXExpressionContainer"){if(r.value.expression.type==="JSXEmptyExpression")return;t[s]=d(r.value.expression,n);}}),t},T=(e,n,t)=>{let r=b(n,t);Object.entries(r).forEach(([s,o])=>{if(s!=="key"){if(s==="children"){u(e,o);return}I(e,s,o);}});},x=(e,n,t)=>{let r=[];return e.forEach(s=>{switch(s.type){case "JSXText":{let o=F(s.value);o&&r.push(o);break}case "JSXExpressionContainer":{if(s.expression.type==="JSXEmptyExpression")break;r.push(d(s.expression,n));break}case "JSXSpreadChild":{let o=d(s.expression,n);o!=null&&r.push(o);break}case "JSXElement":case "JSXFragment":{r.push(J(s,n,t));break}}}),r},M=(e,n,t,r)=>{let s=b(e.openingElement.attributes,n),o=x(e.children,n,r);o.length===1?s.children=o[0]:o.length>1&&(s.children=o);let a=t(s);if(S(a))throw new Error("Async jsx components are not supported.");return a},D=(e,n,t)=>{let r=e.openingElement,s=y(r.name),o=n.components.get(s);if(o)return M(e,n,o,t);if(/[A-Z]/.test(s[0]??""))throw new Error(`Unknown component "${s}". Did you interpolate it with the template literal?`);let a=s==="svg"?"svg":t,i=s==="foreignObject"?null:a,c=a==="svg"?document.createElementNS("http://www.w3.org/2000/svg",s):document.createElement(s);return T(c,r.attributes,n),x(e.children,n,i).forEach(m=>u(c,m)),c},J=(e,n,t)=>{if(e.type==="JSXFragment"){let r=document.createDocumentFragment();return x(e.children,n,t).forEach(o=>u(r,o)),r}return D(e,n,t)},g=(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(s=>g(s,n));return}typeof r=="object"&&g(r,n);}}));},L=(e,n)=>{let t=new Set;return g(e,r=>{r.type==="Identifier"&&n.placeholders.has(r.name)&&t.add(r.name);}),Array.from(t)},d=(e,n)=>{if(e.type==="JSXElement"||e.type==="JSXFragment")return J(e,n,null);if(!("range"in e)||!e.range)throw new Error("Unable to evaluate expression: missing source range information.");let[t,r]=e.range,s=n.source.slice(t,r),o=L(e,n);try{let a=new Function(...o,`"use strict"; return (${s});`),i=o.map(c=>n.placeholders.get(c));return a(...i)}catch(a){throw new Error(`Failed to evaluate expression ${s}: ${a.message}`)}},B=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 s=e.displayName||e.name||`Component${n.length}`,o=B(s),a=o,i=1;for(;n.some(p=>p.name===a);)a=`${o}${i++}`;let c={name:a,value:e};return n.push(c),t.set(e,c),c},V=(e,n)=>{let t=e.raw??e,r=new Map,s=[],o=new Map,a=t[0]??"",i=A++,c=0;for(let p=0;p<n.length;p++){let m=t[p]??"",f=t[p+1]??"",l=n[p],h=N.test(m)||C.test(m);if(h&&typeof l=="function"){let w=z(l,s,o);a+=w.name+f;continue}if(h&&typeof l=="string"){a+=l+f;continue}let E=`${k}${i}_${c++}__`;r.set(E,l),a+=E+f;}return {source:a,placeholders:r,bindings:s}},W=(e,...n)=>{j();let t=V(e,n),r=parseSync("inline.jsx",t.source,R);if(r.errors.length>0)throw new Error($(r.errors[0]));let s=_(r.program),o={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(a=>[a.name,a.value]))};return J(s,o,null)};
4
- export{W as jsx};
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};
@@ -7,8 +7,13 @@ type LoaderContext<TOptions> = {
7
7
  type LoaderOptions = {
8
8
  /**
9
9
  * Name of the tagged template function. Defaults to `jsx`.
10
+ * Deprecated in favor of `tags`.
10
11
  */
11
12
  tag?: string;
13
+ /**
14
+ * List of tagged template function names to transform. Defaults to `['jsx', 'reactJsx']`.
15
+ */
16
+ tags?: string[];
12
17
  };
13
18
  export default function jsxLoader(this: LoaderContext<LoaderOptions>, input: string | Buffer): void;
14
19
  export {};
@@ -42,7 +42,7 @@ const TEMPLATE_PARSER_OPTIONS = {
42
42
  range: true,
43
43
  preserveParens: true,
44
44
  };
45
- const DEFAULT_TAG = 'jsx';
45
+ const DEFAULT_TAGS = ['jsx', 'reactJsx'];
46
46
  const escapeTemplateChunk = (chunk) => chunk.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${');
47
47
  const formatParserError = (error) => {
48
48
  let message = `[jsx-loader] ${error.message}`;
@@ -181,15 +181,15 @@ const transformTemplateLiteral = (templateSource, resourcePath) => {
181
181
  const slots = collectSlots(result.program, templateSource);
182
182
  return renderTemplateWithSlots(templateSource, slots);
183
183
  };
184
- const isTargetTaggedTemplate = (node, source, tag) => {
184
+ const getTaggedTemplateName = (node) => {
185
185
  if (node.type !== 'TaggedTemplateExpression') {
186
- return false;
186
+ return null;
187
187
  }
188
188
  const tagNode = node.tag;
189
189
  if (tagNode.type !== 'Identifier') {
190
- return false;
190
+ return null;
191
191
  }
192
- return tagNode.name === tag;
192
+ return tagNode.name;
193
193
  };
194
194
  const TAG_PLACEHOLDER_PREFIX = '__JSX_LOADER_TAG_EXPR_';
195
195
  const buildTemplateSource = (quasis, expressions, source, tag) => {
@@ -301,8 +301,9 @@ const transformSource = (source, config) => {
301
301
  }
302
302
  const taggedTemplates = [];
303
303
  walkAst(ast.program, node => {
304
- if (isTargetTaggedTemplate(node, source, config.tag)) {
305
- taggedTemplates.push(node);
304
+ const tagName = getTaggedTemplateName(node);
305
+ if (tagName && config.tags.includes(tagName)) {
306
+ taggedTemplates.push({ node, tagName });
306
307
  }
307
308
  });
308
309
  if (!taggedTemplates.length) {
@@ -311,10 +312,11 @@ const transformSource = (source, config) => {
311
312
  const magic = new MagicString(source);
312
313
  let mutated = false;
313
314
  taggedTemplates
314
- .sort((a, b) => b.start - a.start)
315
- .forEach(node => {
315
+ .sort((a, b) => b.node.start - a.node.start)
316
+ .forEach(entry => {
317
+ const { node, tagName } = entry;
316
318
  const quasi = node.quasi;
317
- const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source, config.tag);
319
+ const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source, tagName);
318
320
  const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath);
319
321
  const restored = restoreTemplatePlaceholders(code, templateSource.placeholders);
320
322
  const templateChanged = changed || templateSource.mutated;
@@ -332,11 +334,20 @@ export default function jsxLoader(input) {
332
334
  const callback = this.async();
333
335
  try {
334
336
  const options = this.getOptions?.() ?? {};
335
- const tag = options.tag ?? DEFAULT_TAG;
337
+ const explicitTags = Array.isArray(options.tags)
338
+ ? options.tags.filter((value) => typeof value === 'string' && value.length > 0)
339
+ : null;
340
+ const legacyTag = typeof options.tag === 'string' && options.tag.length > 0 ? options.tag : null;
341
+ const tagList = explicitTags?.length
342
+ ? explicitTags
343
+ : legacyTag
344
+ ? [legacyTag]
345
+ : DEFAULT_TAGS;
346
+ const tags = Array.from(new Set(tagList));
336
347
  const source = typeof input === 'string' ? input : input.toString('utf8');
337
348
  const output = transformSource(source, {
338
349
  resourcePath: this.resourcePath,
339
- tag,
350
+ tags,
340
351
  });
341
352
  callback(null, output);
342
353
  }
@@ -0,0 +1 @@
1
+ export declare const ensureNodeDom: () => Promise<void>;
@@ -0,0 +1,83 @@
1
+ const DOM_TEMPLATE = '<!doctype html><html><body></body></html>';
2
+ const GLOBAL_KEYS = [
3
+ 'window',
4
+ 'self',
5
+ 'document',
6
+ 'HTMLElement',
7
+ 'Element',
8
+ 'Node',
9
+ 'DocumentFragment',
10
+ 'customElements',
11
+ 'Text',
12
+ 'Comment',
13
+ 'MutationObserver',
14
+ 'navigator',
15
+ ];
16
+ const hasDom = () => typeof document !== 'undefined' && typeof document.createElement === 'function';
17
+ const assignGlobalTargets = (windowObj) => {
18
+ const target = globalThis;
19
+ const source = windowObj;
20
+ GLOBAL_KEYS.forEach(key => {
21
+ if (target[key] === undefined && source[key] !== undefined) {
22
+ target[key] = source[key];
23
+ }
24
+ });
25
+ };
26
+ const loadLinkedom = async () => {
27
+ const { parseHTML } = await import('linkedom');
28
+ const { window } = parseHTML(DOM_TEMPLATE);
29
+ return window;
30
+ };
31
+ const loadJsdom = async () => {
32
+ const { JSDOM } = await import('jsdom');
33
+ const { window } = new JSDOM(DOM_TEMPLATE);
34
+ return window;
35
+ };
36
+ const parsePreference = () => {
37
+ const value = typeof process !== 'undefined' && process.env?.KNIGHTED_JSX_NODE_SHIM
38
+ ? process.env.KNIGHTED_JSX_NODE_SHIM.toLowerCase()
39
+ : undefined;
40
+ if (value === 'linkedom' || value === 'jsdom') {
41
+ return value;
42
+ }
43
+ return 'auto';
44
+ };
45
+ const selectLoaders = () => {
46
+ const pref = parsePreference();
47
+ if (pref === 'linkedom') {
48
+ return [loadLinkedom, loadJsdom];
49
+ }
50
+ if (pref === 'jsdom') {
51
+ return [loadJsdom, loadLinkedom];
52
+ }
53
+ return [loadLinkedom, loadJsdom];
54
+ };
55
+ const createShimWindow = async () => {
56
+ const errors = [];
57
+ for (const loader of selectLoaders()) {
58
+ try {
59
+ return await loader();
60
+ }
61
+ catch (error) {
62
+ errors.push(error);
63
+ }
64
+ }
65
+ const help = '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.';
66
+ throw new AggregateError(errors, help);
67
+ };
68
+ let bootstrapPromise = null;
69
+ export const ensureNodeDom = async () => {
70
+ if (hasDom()) {
71
+ return;
72
+ }
73
+ if (!bootstrapPromise) {
74
+ bootstrapPromise = (async () => {
75
+ const windowObj = await createShimWindow();
76
+ assignGlobalTargets(windowObj);
77
+ })().catch(error => {
78
+ bootstrapPromise = null;
79
+ throw error;
80
+ });
81
+ }
82
+ return bootstrapPromise;
83
+ };
@@ -0,0 +1,2 @@
1
+ export declare const jsx: (templates: TemplateStringsArray, ...values: unknown[]) => import("../jsx.js").JsxRenderable;
2
+ export type { JsxRenderable, JsxComponent } from '../jsx.js';
@@ -0,0 +1,4 @@
1
+ import { ensureNodeDom } from './bootstrap.js';
2
+ import { jsx as baseJsx } from '../jsx.js';
3
+ await ensureNodeDom();
4
+ export const jsx = baseJsx;
@@ -0,0 +1,2 @@
1
+ export { reactJsx } from '../../react/react-jsx.js';
2
+ export type { ReactJsxComponent } from '../../react/react-jsx.js';
@@ -0,0 +1 @@
1
+ export { reactJsx } from '../../react/react-jsx.js';
@@ -0,0 +1,2 @@
1
+ export { reactJsx } from './react-jsx.js';
2
+ export type { ReactJsxComponent } from './react-jsx.js';
@@ -0,0 +1 @@
1
+ export { reactJsx } from './react-jsx.js';
@@ -0,0 +1,5 @@
1
+ import { type ComponentType, type ReactElement, type ReactNode } from 'react';
2
+ export type ReactJsxComponent<Props = Record<string, unknown>> = ComponentType<Props & {
3
+ children?: ReactNode;
4
+ }>;
5
+ export declare const reactJsx: (templates: TemplateStringsArray, ...values: unknown[]) => ReactElement;
@@ -0,0 +1,138 @@
1
+ import { parseSync } from 'oxc-parser';
2
+ import { buildTemplate, evaluateExpression, extractRootNode, formatParserError, getIdentifierName, normalizeJsxText, parserOptions, } from '../runtime/shared.js';
3
+ import { Fragment, createElement, } from 'react';
4
+ const isIterable = (value) => {
5
+ if (!value || typeof value === 'string') {
6
+ return false;
7
+ }
8
+ return typeof value[Symbol.iterator] === 'function';
9
+ };
10
+ const isPromiseLike = (value) => {
11
+ if (!value || (typeof value !== 'object' && typeof value !== 'function')) {
12
+ return false;
13
+ }
14
+ return typeof value.then === 'function';
15
+ };
16
+ const appendReactChild = (bucket, value) => {
17
+ if (value === null || value === undefined) {
18
+ return;
19
+ }
20
+ if (typeof value === 'boolean') {
21
+ return;
22
+ }
23
+ if (isPromiseLike(value)) {
24
+ throw new Error('Async values are not supported inside reactJsx template results.');
25
+ }
26
+ if (Array.isArray(value)) {
27
+ value.forEach(entry => appendReactChild(bucket, entry));
28
+ return;
29
+ }
30
+ if (isIterable(value)) {
31
+ for (const entry of value) {
32
+ appendReactChild(bucket, entry);
33
+ }
34
+ return;
35
+ }
36
+ bucket.push(value);
37
+ };
38
+ const evaluateExpressionForReact = (expression, ctx) => evaluateExpression(expression, ctx, node => evaluateReactJsxNode(node, ctx));
39
+ const resolveAttributes = (attributes, ctx) => {
40
+ const props = {};
41
+ attributes.forEach(attribute => {
42
+ if (attribute.type === 'JSXSpreadAttribute') {
43
+ const spreadValue = evaluateExpressionForReact(attribute.argument, ctx);
44
+ if (spreadValue && typeof spreadValue === 'object' && !Array.isArray(spreadValue)) {
45
+ Object.assign(props, spreadValue);
46
+ }
47
+ return;
48
+ }
49
+ const name = getIdentifierName(attribute.name);
50
+ if (!attribute.value) {
51
+ props[name] = true;
52
+ return;
53
+ }
54
+ if (attribute.value.type === 'Literal') {
55
+ props[name] = attribute.value.value;
56
+ return;
57
+ }
58
+ if (attribute.value.type === 'JSXExpressionContainer') {
59
+ if (attribute.value.expression.type === 'JSXEmptyExpression') {
60
+ return;
61
+ }
62
+ props[name] = evaluateExpressionForReact(attribute.value.expression, ctx);
63
+ }
64
+ });
65
+ return props;
66
+ };
67
+ const evaluateReactJsxChildren = (children, ctx) => {
68
+ const resolved = [];
69
+ children.forEach(child => {
70
+ switch (child.type) {
71
+ case 'JSXText': {
72
+ const text = normalizeJsxText(child.value);
73
+ if (text) {
74
+ resolved.push(text);
75
+ }
76
+ break;
77
+ }
78
+ case 'JSXExpressionContainer': {
79
+ if (child.expression.type === 'JSXEmptyExpression') {
80
+ break;
81
+ }
82
+ appendReactChild(resolved, evaluateExpressionForReact(child.expression, ctx));
83
+ break;
84
+ }
85
+ case 'JSXSpreadChild': {
86
+ const spreadValue = evaluateExpressionForReact(child.expression, ctx);
87
+ if (spreadValue !== undefined && spreadValue !== null) {
88
+ appendReactChild(resolved, spreadValue);
89
+ }
90
+ break;
91
+ }
92
+ case 'JSXElement':
93
+ case 'JSXFragment': {
94
+ resolved.push(evaluateReactJsxNode(child, ctx));
95
+ break;
96
+ }
97
+ }
98
+ });
99
+ return resolved;
100
+ };
101
+ const createReactElement = (type, props, children) => {
102
+ return createElement(type, props, ...children);
103
+ };
104
+ const evaluateReactJsxElement = (element, ctx) => {
105
+ const opening = element.openingElement;
106
+ const tagName = getIdentifierName(opening.name);
107
+ const component = ctx.components.get(tagName);
108
+ const props = resolveAttributes(opening.attributes, ctx);
109
+ const childValues = evaluateReactJsxChildren(element.children, ctx);
110
+ if (component) {
111
+ return createReactElement(component, props, childValues);
112
+ }
113
+ if (/[A-Z]/.test(tagName[0] ?? '')) {
114
+ throw new Error(`Unknown component "${tagName}". Did you interpolate it with the template literal?`);
115
+ }
116
+ return createReactElement(tagName, props, childValues);
117
+ };
118
+ const evaluateReactJsxNode = (node, ctx) => {
119
+ if (node.type === 'JSXFragment') {
120
+ const children = evaluateReactJsxChildren(node.children, ctx);
121
+ return createElement(Fragment, null, ...children);
122
+ }
123
+ return evaluateReactJsxElement(node, ctx);
124
+ };
125
+ export const reactJsx = (templates, ...values) => {
126
+ const build = buildTemplate(templates, values);
127
+ const result = parseSync('inline.jsx', build.source, parserOptions);
128
+ if (result.errors.length > 0) {
129
+ throw new Error(formatParserError(result.errors[0]));
130
+ }
131
+ const root = extractRootNode(result.program);
132
+ const ctx = {
133
+ source: build.source,
134
+ placeholders: build.placeholders,
135
+ components: new Map(build.bindings.map(binding => [binding.name, binding.value])),
136
+ };
137
+ return evaluateReactJsxNode(root, ctx);
138
+ };