@knighted/jsx 1.4.1 → 1.5.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
@@ -99,7 +99,7 @@ The React runtime shares the same template semantics as `jsx`, except it returns
99
99
 
100
100
  - `style` accepts either a string or an object. Object values handle CSS custom properties (`--token`) automatically.
101
101
  - `class` and `className` both work and can be strings or arrays.
102
- - Event handlers use the `on<Event>` naming convention (e.g. `onClick`).
102
+ - Event handlers use the `on<Event>` naming convention (e.g. `onClick`), support capture-phase variants via `on<Event>Capture`, and allow custom events with the `on:custom-event` syntax (descriptor objects with `{ handler, once, capture }` are also accepted).
103
103
  - `ref` supports callback refs as well as mutable `{ current }` objects.
104
104
  - `dangerouslySetInnerHTML` expects an object with an `__html` field, mirroring React.
105
105
 
@@ -286,6 +286,26 @@ import { reactJsx as nodeReactJsx } from '@knighted/jsx/node/react/lite'
286
286
 
287
287
  Each lite subpath ships the same API as its standard counterpart but is pre-minified and scoped to just that runtime (DOM, React, Node DOM, or Node React). Swap them in when you want the smallest possible bundles; otherwise the default exports keep working as-is.
288
288
 
289
+ ## Common gotchas
290
+
291
+ ### DocumentFragment reuse (DOM helper)
292
+
293
+ `jsx` returns actual DOM nodes, so fragments compile down to real `DocumentFragment` instances. The browser treats those fragments as one-time transport containers: append them to a parent, and the fragment empties itself as it moves its children. Unlike VDOM libraries (React, Preact, Solid), we do not clone fragments on your behalf, so storing a fragment and reusing it later will not work the way a React developer might expect.
294
+
295
+ ```ts
296
+ const header = jsx`
297
+ <>
298
+ <h1>Title</h1>
299
+ <p>Reusable? Only if you clone.</p>
300
+ </>
301
+ `
302
+
303
+ document.querySelector('header')!.append(header)
304
+ document.querySelector('footer')!.append(header) // footer stays empty
305
+ ```
306
+
307
+ When you need multiple copies, call the template again, wrap it in a helper (``const makeHeader = () => jsx`<...>`; makeHeader()``), or clone the fragment before reusing it (`footer.append(header.cloneNode(true))`). Components that return fragments are unaffected because every invocation produces a fresh fragment.
308
+
289
309
  ## Limitations
290
310
 
291
311
  - Requires a DOM-like environment (it throws when `document` is missing).
@@ -8,6 +8,9 @@ exports.detectPackageManager = detectPackageManager;
8
8
  exports.ensurePackageJson = ensurePackageJson;
9
9
  exports.runNpmPack = runNpmPack;
10
10
  exports.parsePackageName = parsePackageName;
11
+ exports.resolveBindingSpec = resolveBindingSpec;
12
+ exports.fetchLatestBindingVersion = fetchLatestBindingVersion;
13
+ exports.readLocalOxcParserVersion = readLocalOxcParserVersion;
11
14
  exports.installBinding = installBinding;
12
15
  exports.installRuntimeDeps = installRuntimeDeps;
13
16
  exports.isDependencyInstalled = isDependencyInstalled;
@@ -23,7 +26,10 @@ const promises_1 = __importDefault(require("node:readline/promises"));
23
26
  const node_module_1 = require("node:module");
24
27
  const node_url_1 = require("node:url");
25
28
  const tar_1 = require("tar");
26
- const DEFAULT_BINDING_SPEC = process.env.WASM_BINDING_PACKAGE ?? '@oxc-parser/binding-wasm32-wasi@^0.99.0';
29
+ const CLI_REQUIRE = (0, node_module_1.createRequire)(typeof __filename !== 'undefined' ? __filename : node_path_1.default.join(process.cwd(), 'noop.js'));
30
+ const DEFAULT_BINDING_PACKAGE_NAME = '@oxc-parser/binding-wasm32-wasi';
31
+ const DEFAULT_BINDING_PACKAGE = process.env.WASM_BINDING_PACKAGE ?? DEFAULT_BINDING_PACKAGE_NAME;
32
+ const DEFAULT_BINDING_SOURCE = process.env.WASM_BINDING_PACKAGE ? 'env' : 'default';
27
33
  const RUNTIME_DEPS = ['@napi-rs/wasm-runtime', '@emnapi/runtime', '@emnapi/core'];
28
34
  const SUPPORTED_PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
29
35
  // Node emits a noisy ExperimentalWarning whenever the WASI shim loads; silence just that message.
@@ -48,7 +54,9 @@ function parseArgs(argv) {
48
54
  verbose: false,
49
55
  force: false,
50
56
  skipConfig: true,
51
- wasmPackage: DEFAULT_BINDING_SPEC,
57
+ wasmPackage: DEFAULT_BINDING_PACKAGE,
58
+ wasmVersion: undefined,
59
+ wasmPackageSource: DEFAULT_BINDING_SOURCE,
52
60
  };
53
61
  for (let i = 0; i < argv.length; i += 1) {
54
62
  const arg = argv[i];
@@ -82,6 +90,14 @@ function parseArgs(argv) {
82
90
  if (!pkg)
83
91
  throw new Error('Missing value for --wasm-package');
84
92
  options.wasmPackage = pkg;
93
+ options.wasmPackageSource = 'flag';
94
+ i += 1;
95
+ }
96
+ else if (arg === '--wasm-version') {
97
+ const version = argv[i + 1];
98
+ if (!version)
99
+ throw new Error('Missing value for --wasm-version');
100
+ options.wasmVersion = version;
85
101
  i += 1;
86
102
  }
87
103
  else if (arg === '--help' || arg === '-h') {
@@ -92,7 +108,7 @@ function parseArgs(argv) {
92
108
  return options;
93
109
  }
94
110
  function printHelp() {
95
- console.log(`\nUsage: npx @knighted/jsx init [options]\n\nOptions:\n --package-manager, --pm <name> Choose npm | pnpm | yarn | bun\n --wasm-package <spec> Override binding package spec\n --config Prompt to help with loader config\n --skip-config Skip any loader config prompts (default)\n --dry-run Print actions without executing\n --force, --yes Assume yes for prompts\n --verbose Log extra detail\n -h, --help Show this help message\n`);
111
+ console.log(`\nUsage: npx @knighted/jsx init [options]\n\nOptions:\n --package-manager, --pm <name> Choose npm | pnpm | yarn | bun\n --wasm-package <spec> Override binding package spec\n --wasm-version <version> Pin version for the default binding\n --config Prompt to help with loader config\n --skip-config Skip any loader config prompts (default)\n --dry-run Print actions without executing\n --force, --yes Assume yes for prompts\n --verbose Log extra detail\n -h, --help Show this help message\n`);
96
112
  }
97
113
  function log(message) {
98
114
  console.log(message);
@@ -148,6 +164,73 @@ function parsePackageName(spec) {
148
164
  const [, name, version] = match;
149
165
  return { name, version };
150
166
  }
167
+ function readLocalOxcParserVersion(resolver = CLI_REQUIRE) {
168
+ try {
169
+ const pkg = resolver('oxc-parser/package.json');
170
+ if (pkg && typeof pkg.version === 'string') {
171
+ return pkg.version;
172
+ }
173
+ }
174
+ catch {
175
+ // Ignore resolution failures and fall back to registry lookup later.
176
+ }
177
+ return undefined;
178
+ }
179
+ function isRegistryResolvablePackage(name) {
180
+ if (!name)
181
+ return false;
182
+ if (name.startsWith('.') || name.startsWith('/'))
183
+ return false;
184
+ if (node_path_1.default.isAbsolute(name))
185
+ return false;
186
+ const blockedPrefixes = [
187
+ 'file:',
188
+ 'link:',
189
+ 'git+',
190
+ 'github:',
191
+ 'workspace:',
192
+ 'http://',
193
+ 'https://',
194
+ ];
195
+ return !blockedPrefixes.some(prefix => name.startsWith(prefix));
196
+ }
197
+ function fetchLatestBindingVersion(pkgName, execFn = node_child_process_1.execFileSync) {
198
+ const output = execFn('npm', ['view', pkgName, 'version', '--json'], {
199
+ encoding: 'utf8',
200
+ stdio: ['ignore', 'pipe', 'inherit'],
201
+ }).trim();
202
+ if (!output) {
203
+ throw new Error(`Unable to determine latest version for ${pkgName}`);
204
+ }
205
+ const parsed = JSON.parse(output);
206
+ if (typeof parsed === 'string')
207
+ return parsed;
208
+ if (Array.isArray(parsed) && parsed.length > 0) {
209
+ const last = parsed[parsed.length - 1];
210
+ if (typeof last === 'string')
211
+ return last;
212
+ }
213
+ throw new Error(`Unable to determine latest version for ${pkgName}`);
214
+ }
215
+ function resolveBindingSpec(spec, explicitVersion, fetchLatest = fetchLatestBindingVersion) {
216
+ let combinedSpec = spec;
217
+ if (explicitVersion) {
218
+ const { name } = parsePackageName(spec);
219
+ if (!name) {
220
+ throw new Error(`Unable to parse binding package spec: ${spec}`);
221
+ }
222
+ combinedSpec = `${name}@${explicitVersion}`;
223
+ }
224
+ const { name, version } = parsePackageName(combinedSpec);
225
+ if (!name) {
226
+ throw new Error(`Unable to parse binding package spec: ${spec}`);
227
+ }
228
+ if (version || !isRegistryResolvablePackage(name)) {
229
+ return { spec: combinedSpec, name, version };
230
+ }
231
+ const latest = fetchLatest(name);
232
+ return { spec: `${name}@${latest}`, name, version: latest };
233
+ }
151
234
  async function installBinding(spec, cwd, dryRun, verbose, pack = runNpmPack) {
152
235
  const { name, version } = parsePackageName(spec);
153
236
  const tarballName = pack(spec, cwd, dryRun, verbose);
@@ -248,13 +331,17 @@ async function maybeHandleConfigPrompt(skipConfig, force) {
248
331
  console.log(LOADER_CONFIG_EXAMPLE);
249
332
  }
250
333
  async function main(overrides = {}) {
251
- const { parseArgs: parseArgsImpl = parseArgs, ensurePackageJson: ensurePackageJsonImpl = ensurePackageJson, detectPackageManager: detectPackageManagerImpl = detectPackageManager, installRuntimeDeps: installRuntimeDepsImpl = installRuntimeDeps, installBinding: installBindingImpl = installBinding, persistBindingSpec: persistBindingSpecImpl = persistBindingSpec, verifyBinding: verifyBindingImpl = verifyBinding, maybeHandleConfigPrompt: maybeHandleConfigPromptImpl = maybeHandleConfigPrompt, log: logImpl = log, } = overrides;
334
+ const { parseArgs: parseArgsImpl = parseArgs, ensurePackageJson: ensurePackageJsonImpl = ensurePackageJson, detectPackageManager: detectPackageManagerImpl = detectPackageManager, installRuntimeDeps: installRuntimeDepsImpl = installRuntimeDeps, installBinding: installBindingImpl = installBinding, persistBindingSpec: persistBindingSpecImpl = persistBindingSpec, verifyBinding: verifyBindingImpl = verifyBinding, maybeHandleConfigPrompt: maybeHandleConfigPromptImpl = maybeHandleConfigPrompt, resolveBindingSpec: resolveBindingSpecImpl = resolveBindingSpec, readLocalOxcParserVersion: readLocalOxcParserVersionImpl = readLocalOxcParserVersion, log: logImpl = log, } = overrides;
252
335
  const options = parseArgsImpl(process.argv.slice(2));
253
336
  ensurePackageJsonImpl(options.cwd);
337
+ const bundledParserVersion = readLocalOxcParserVersionImpl();
338
+ const desiredBindingVersion = options.wasmVersion ??
339
+ (options.wasmPackageSource === 'default' ? bundledParserVersion : undefined);
340
+ const resolvedBinding = resolveBindingSpecImpl(options.wasmPackage, desiredBindingVersion);
254
341
  const packageManager = detectPackageManagerImpl(options.cwd, options.packageManager);
255
342
  logImpl(`> Using package manager: ${packageManager}`);
256
343
  const installedRuntimeDeps = installRuntimeDepsImpl(packageManager, RUNTIME_DEPS, options.cwd, options.dryRun, options.verbose);
257
- const binding = await installBindingImpl(options.wasmPackage, options.cwd, options.dryRun, options.verbose);
344
+ const binding = await installBindingImpl(resolvedBinding.spec, options.cwd, options.dryRun, options.verbose);
258
345
  persistBindingSpecImpl(options.cwd, binding.name, binding.version, options.dryRun, options.verbose);
259
346
  let resolvedPath;
260
347
  if (!options.dryRun) {
@@ -1,4 +1,5 @@
1
1
  import { execFileSync, spawnSync } from 'node:child_process';
2
+ import { createRequire } from 'node:module';
2
3
  declare const SUPPORTED_PACKAGE_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
3
4
  type PackageManager = (typeof SUPPORTED_PACKAGE_MANAGERS)[number];
4
5
  type CliOptions = {
@@ -9,6 +10,8 @@ type CliOptions = {
9
10
  skipConfig: boolean;
10
11
  packageManager?: PackageManager;
11
12
  wasmPackage: string;
13
+ wasmVersion?: string;
14
+ wasmPackageSource: 'default' | 'env' | 'flag';
12
15
  };
13
16
  declare function parseArgs(argv: string[]): CliOptions;
14
17
  declare function log(message: string): void;
@@ -22,6 +25,14 @@ declare function parsePackageName(spec: string): {
22
25
  name: string;
23
26
  version: string;
24
27
  };
28
+ type NodeRequireFn = ReturnType<typeof createRequire>;
29
+ declare function readLocalOxcParserVersion(resolver?: NodeRequireFn): string | undefined;
30
+ declare function fetchLatestBindingVersion(pkgName: string, execFn?: typeof execFileSync): string;
31
+ declare function resolveBindingSpec(spec: string, explicitVersion?: string, fetchLatest?: typeof fetchLatestBindingVersion): {
32
+ spec: string;
33
+ name: string;
34
+ version: string | undefined;
35
+ };
25
36
  type PackFunction = (spec: string, cwd: string, dryRun: boolean, verbose: boolean) => string;
26
37
  declare function installBinding(spec: string, cwd: string, dryRun: boolean, verbose: boolean, pack?: PackFunction): Promise<{
27
38
  targetDir: string;
@@ -45,7 +56,9 @@ type MainDeps = {
45
56
  persistBindingSpec: typeof persistBindingSpec;
46
57
  verifyBinding: typeof verifyBinding;
47
58
  maybeHandleConfigPrompt: typeof maybeHandleConfigPrompt;
59
+ resolveBindingSpec: typeof resolveBindingSpec;
60
+ readLocalOxcParserVersion: typeof readLocalOxcParserVersion;
48
61
  log: typeof log;
49
62
  };
50
63
  declare function main(overrides?: Partial<MainDeps>): Promise<void>;
51
- export { parseArgs, detectPackageManager, ensurePackageJson, runNpmPack, parsePackageName, installBinding, installRuntimeDeps, isDependencyInstalled, persistBindingSpec, verifyBinding, promptYesNo, maybeHandleConfigPrompt, main, };
64
+ export { parseArgs, detectPackageManager, ensurePackageJson, runNpmPack, parsePackageName, resolveBindingSpec, fetchLatestBindingVersion, readLocalOxcParserVersion, installBinding, installRuntimeDeps, isDependencyInstalled, persistBindingSpec, verifyBinding, promptYesNo, maybeHandleConfigPrompt, main, };
@@ -9,8 +9,25 @@ type DataAttributes = {
9
9
  type AriaAttributes = {
10
10
  [K in `aria-${string}`]?: string | number | boolean | null | undefined;
11
11
  };
12
+ type JsxEventListener<EV extends Event> = ((event: EV) => void) | {
13
+ handleEvent(event: EV): void;
14
+ };
15
+ type JsxEventDescriptor<EV extends Event> = {
16
+ handler: JsxEventListener<EV>;
17
+ capture?: boolean;
18
+ once?: boolean;
19
+ passive?: boolean;
20
+ signal?: AbortSignal;
21
+ };
22
+ type JsxEventProp<EV extends Event> = JsxEventListener<EV> | JsxEventDescriptor<EV>;
12
23
  type EventHandlers<T extends EventTarget> = {
13
- [K in keyof GlobalEventHandlersEventMap as `on${Capitalize<string & K>}`]?: (event: GlobalEventHandlersEventMap[K]) => void;
24
+ [K in keyof GlobalEventHandlersEventMap as `on${Capitalize<string & K>}`]?: JsxEventProp<GlobalEventHandlersEventMap[K]>;
25
+ } & {
26
+ [K in keyof GlobalEventHandlersEventMap as `on${Capitalize<string & K>}Capture`]?: JsxEventProp<GlobalEventHandlersEventMap[K]>;
27
+ } & {
28
+ [K in string as K extends '' ? never : `on:${K}`]?: JsxEventProp<CustomEvent<unknown>>;
29
+ } & {
30
+ [K in string as K extends '' ? never : `on:${K}Capture`]?: JsxEventProp<CustomEvent<unknown>>;
14
31
  };
15
32
  type ElementProps<Tag extends keyof HTMLElementTagNameMap> = Omit<Partial<HTMLElementTagNameMap[Tag]>, 'children'> & EventHandlers<HTMLElementTagNameMap[Tag]> & DataAttributes & AriaAttributes & {
16
33
  class?: string;
package/dist/cjs/jsx.cjs CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.jsx = void 0;
4
4
  const oxc_parser_1 = require("oxc-parser");
5
5
  const shared_js_1 = require("./runtime/shared.cjs");
6
+ const property_information_1 = require("property-information");
6
7
  const ensureDomAvailable = () => {
7
8
  if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
8
9
  throw new Error('The jsx template tag requires a DOM-like environment (document missing).');
@@ -26,8 +27,131 @@ const isPromiseLike = (value) => {
26
27
  }
27
28
  return typeof value.then === 'function';
28
29
  };
29
- const setDomProp = (element, name, value) => {
30
- if (value === false || value === null || value === undefined) {
30
+ const ATTRIBUTE_NAMESPACE_URIS = {
31
+ xlink: 'http://www.w3.org/1999/xlink',
32
+ xml: 'http://www.w3.org/XML/1998/namespace',
33
+ xmlns: 'http://www.w3.org/2000/xmlns/',
34
+ };
35
+ const getAttributeNamespace = (space) => {
36
+ if (!space || space === 'html' || space === 'svg') {
37
+ return undefined;
38
+ }
39
+ return ATTRIBUTE_NAMESPACE_URIS[space];
40
+ };
41
+ const getSchemaForNamespace = (namespace) => namespace === 'svg' ? property_information_1.svg : property_information_1.html;
42
+ const setAttributeValue = (element, info, value) => {
43
+ const namespaceUri = getAttributeNamespace(info.space);
44
+ const attrValue = String(value);
45
+ if (namespaceUri) {
46
+ element.setAttributeNS(namespaceUri, info.attribute, attrValue);
47
+ return;
48
+ }
49
+ element.setAttribute(info.attribute, attrValue);
50
+ };
51
+ const removeAttributeValue = (element, info) => {
52
+ const namespaceUri = getAttributeNamespace(info.space);
53
+ if (namespaceUri) {
54
+ element.removeAttributeNS(namespaceUri, info.attribute);
55
+ return;
56
+ }
57
+ element.removeAttribute(info.attribute);
58
+ };
59
+ const joinTokenList = (value, delimiter) => {
60
+ if (!Array.isArray(value)) {
61
+ return value;
62
+ }
63
+ return value.filter(Boolean).join(delimiter);
64
+ };
65
+ const shouldAssignProperty = (element, namespace, info) => {
66
+ if (namespace === 'svg') {
67
+ return false;
68
+ }
69
+ if (!info.property || info.property.includes(':')) {
70
+ return false;
71
+ }
72
+ return info.property in element;
73
+ };
74
+ const captureSuffix = 'Capture';
75
+ const stripCaptureSuffix = (rawName) => {
76
+ if (rawName.endsWith(captureSuffix) && rawName.length > captureSuffix.length) {
77
+ return { eventName: rawName.slice(0, -captureSuffix.length), capture: true };
78
+ }
79
+ return { eventName: rawName, capture: false };
80
+ };
81
+ const parseEventPropName = (name) => {
82
+ if (!name.startsWith('on')) {
83
+ return null;
84
+ }
85
+ if (name.startsWith('on:')) {
86
+ const raw = name.slice(3);
87
+ if (!raw) {
88
+ return null;
89
+ }
90
+ const parsed = stripCaptureSuffix(raw);
91
+ if (!parsed.eventName) {
92
+ return null;
93
+ }
94
+ return parsed;
95
+ }
96
+ const raw = name.slice(2);
97
+ if (!raw) {
98
+ return null;
99
+ }
100
+ const parsed = stripCaptureSuffix(raw);
101
+ if (!parsed.eventName) {
102
+ return null;
103
+ }
104
+ return {
105
+ eventName: parsed.eventName.toLowerCase(),
106
+ capture: parsed.capture,
107
+ };
108
+ };
109
+ const isEventListenerObject = (value) => {
110
+ if (!value || typeof value !== 'object') {
111
+ return false;
112
+ }
113
+ return ('handleEvent' in value &&
114
+ typeof value.handleEvent === 'function');
115
+ };
116
+ const isEventHandlerDescriptor = (value) => {
117
+ if (!value || typeof value !== 'object' || !('handler' in value)) {
118
+ return false;
119
+ }
120
+ const handler = value.handler;
121
+ if (typeof handler === 'function') {
122
+ return true;
123
+ }
124
+ return isEventListenerObject(handler);
125
+ };
126
+ const resolveEventHandlerValue = (value) => {
127
+ if (typeof value === 'function' || isEventListenerObject(value)) {
128
+ return { listener: value };
129
+ }
130
+ if (!isEventHandlerDescriptor(value)) {
131
+ return null;
132
+ }
133
+ const descriptor = value;
134
+ let options = descriptor.options ? { ...descriptor.options } : undefined;
135
+ const assignOption = (key, optionValue) => {
136
+ if (optionValue === undefined || optionValue === null) {
137
+ return;
138
+ }
139
+ if (!options) {
140
+ options = {};
141
+ }
142
+ options[key] = optionValue;
143
+ };
144
+ assignOption('capture', descriptor.capture);
145
+ assignOption('once', descriptor.once);
146
+ assignOption('passive', descriptor.passive);
147
+ assignOption('signal', descriptor.signal ?? undefined);
148
+ return {
149
+ listener: descriptor.handler,
150
+ options,
151
+ };
152
+ };
153
+ const setDomProp = (element, name, value, namespace) => {
154
+ if (value === null || value === undefined) {
31
155
  return;
32
156
  }
33
157
  if (name === 'dangerouslySetInnerHTML' &&
@@ -67,27 +191,75 @@ const setDomProp = (element, name, value) => {
67
191
  });
68
192
  return;
69
193
  }
70
- if (typeof value === 'function' && name.startsWith('on')) {
71
- const eventName = name.slice(2).toLowerCase();
72
- element.addEventListener(eventName, value);
194
+ const eventBinding = parseEventPropName(name);
195
+ if (eventBinding) {
196
+ const handlerValue = resolveEventHandlerValue(value);
197
+ if (handlerValue) {
198
+ let options = handlerValue.options ? { ...handlerValue.options } : undefined;
199
+ if (eventBinding.capture) {
200
+ if (!options) {
201
+ options = { capture: true };
202
+ }
203
+ else {
204
+ options.capture = true;
205
+ }
206
+ }
207
+ element.addEventListener(eventBinding.eventName, handlerValue.listener, options);
208
+ return;
209
+ }
210
+ }
211
+ const info = (0, property_information_1.find)(getSchemaForNamespace(namespace), name);
212
+ const elementWithIndex = element;
213
+ const canAssignProperty = shouldAssignProperty(element, namespace, info);
214
+ if (info.mustUseProperty) {
215
+ const nextValue = info.boolean ? Boolean(value) : value;
216
+ elementWithIndex[info.property] = nextValue;
73
217
  return;
74
218
  }
75
- if (name === 'class' || name === 'className') {
76
- const classValue = Array.isArray(value)
77
- ? value.filter(Boolean).join(' ')
78
- : String(value);
79
- element.setAttribute('class', classValue);
219
+ if (info.boolean) {
220
+ const boolValue = Boolean(value);
221
+ if (canAssignProperty) {
222
+ elementWithIndex[info.property] = boolValue;
223
+ }
224
+ if (boolValue) {
225
+ setAttributeValue(element, info, '');
226
+ }
227
+ else {
228
+ removeAttributeValue(element, info);
229
+ }
80
230
  return;
81
231
  }
82
- if (name === 'htmlFor') {
83
- element.setAttribute('for', String(value));
232
+ let normalizedValue = value;
233
+ if (info.spaceSeparated) {
234
+ normalizedValue = joinTokenList(value, ' ');
235
+ }
236
+ else if (info.commaSeparated) {
237
+ normalizedValue = joinTokenList(value, ',');
238
+ }
239
+ else if (info.commaOrSpaceSeparated) {
240
+ normalizedValue = joinTokenList(value, ' ');
241
+ }
242
+ if (info.booleanish && typeof normalizedValue === 'boolean') {
243
+ normalizedValue = normalizedValue ? 'true' : 'false';
244
+ }
245
+ if (info.overloadedBoolean) {
246
+ if (normalizedValue === false) {
247
+ removeAttributeValue(element, info);
248
+ return;
249
+ }
250
+ if (normalizedValue === true) {
251
+ setAttributeValue(element, info, '');
252
+ return;
253
+ }
254
+ }
255
+ if (canAssignProperty) {
256
+ elementWithIndex[info.property] = normalizedValue;
84
257
  return;
85
258
  }
86
- if (name in element && !name.includes('-')) {
87
- element[name] = value;
259
+ if (normalizedValue === false) {
88
260
  return;
89
261
  }
90
- element.setAttribute(name, value === true ? '' : String(value));
262
+ setAttributeValue(element, info, normalizedValue);
91
263
  };
92
264
  const appendChildValue = (parent, value) => {
93
265
  if (value === null || value === undefined) {
@@ -154,7 +326,7 @@ const applyDomAttributes = (element, attributes, ctx, namespace) => {
154
326
  appendChildValue(element, value);
155
327
  return;
156
328
  }
157
- setDomProp(element, name, value);
329
+ setDomProp(element, name, value, namespace);
158
330
  });
159
331
  };
160
332
  const evaluateJsxChildren = (children, ctx, namespace) => {
@@ -1,4 +1,5 @@
1
1
  import { execFileSync, spawnSync } from 'node:child_process';
2
+ import { createRequire } from 'node:module';
2
3
  declare const SUPPORTED_PACKAGE_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
3
4
  type PackageManager = (typeof SUPPORTED_PACKAGE_MANAGERS)[number];
4
5
  type CliOptions = {
@@ -9,6 +10,8 @@ type CliOptions = {
9
10
  skipConfig: boolean;
10
11
  packageManager?: PackageManager;
11
12
  wasmPackage: string;
13
+ wasmVersion?: string;
14
+ wasmPackageSource: 'default' | 'env' | 'flag';
12
15
  };
13
16
  declare function parseArgs(argv: string[]): CliOptions;
14
17
  declare function log(message: string): void;
@@ -22,6 +25,14 @@ declare function parsePackageName(spec: string): {
22
25
  name: string;
23
26
  version: string;
24
27
  };
28
+ type NodeRequireFn = ReturnType<typeof createRequire>;
29
+ declare function readLocalOxcParserVersion(resolver?: NodeRequireFn): string | undefined;
30
+ declare function fetchLatestBindingVersion(pkgName: string, execFn?: typeof execFileSync): string;
31
+ declare function resolveBindingSpec(spec: string, explicitVersion?: string, fetchLatest?: typeof fetchLatestBindingVersion): {
32
+ spec: string;
33
+ name: string;
34
+ version: string | undefined;
35
+ };
25
36
  type PackFunction = (spec: string, cwd: string, dryRun: boolean, verbose: boolean) => string;
26
37
  declare function installBinding(spec: string, cwd: string, dryRun: boolean, verbose: boolean, pack?: PackFunction): Promise<{
27
38
  targetDir: string;
@@ -45,7 +56,9 @@ type MainDeps = {
45
56
  persistBindingSpec: typeof persistBindingSpec;
46
57
  verifyBinding: typeof verifyBinding;
47
58
  maybeHandleConfigPrompt: typeof maybeHandleConfigPrompt;
59
+ resolveBindingSpec: typeof resolveBindingSpec;
60
+ readLocalOxcParserVersion: typeof readLocalOxcParserVersion;
48
61
  log: typeof log;
49
62
  };
50
63
  declare function main(overrides?: Partial<MainDeps>): Promise<void>;
51
- export { parseArgs, detectPackageManager, ensurePackageJson, runNpmPack, parsePackageName, installBinding, installRuntimeDeps, isDependencyInstalled, persistBindingSpec, verifyBinding, promptYesNo, maybeHandleConfigPrompt, main, };
64
+ export { parseArgs, detectPackageManager, ensurePackageJson, runNpmPack, parsePackageName, resolveBindingSpec, fetchLatestBindingVersion, readLocalOxcParserVersion, installBinding, installRuntimeDeps, isDependencyInstalled, persistBindingSpec, verifyBinding, promptYesNo, maybeHandleConfigPrompt, main, };
package/dist/cli/init.js CHANGED
@@ -7,7 +7,12 @@ import { createRequire } from 'module';
7
7
  import { pathToFileURL } from 'url';
8
8
  import { extract } from 'tar';
9
9
 
10
- var DEFAULT_BINDING_SPEC = process.env.WASM_BINDING_PACKAGE ?? "@oxc-parser/binding-wasm32-wasi@^0.99.0";
10
+ var CLI_REQUIRE = createRequire(
11
+ typeof __filename !== "undefined" ? __filename : path.join(process.cwd(), "noop.js")
12
+ );
13
+ var DEFAULT_BINDING_PACKAGE_NAME = "@oxc-parser/binding-wasm32-wasi";
14
+ var DEFAULT_BINDING_PACKAGE = process.env.WASM_BINDING_PACKAGE ?? DEFAULT_BINDING_PACKAGE_NAME;
15
+ var DEFAULT_BINDING_SOURCE = process.env.WASM_BINDING_PACKAGE ? "env" : "default";
11
16
  var RUNTIME_DEPS = ["@napi-rs/wasm-runtime", "@emnapi/runtime", "@emnapi/core"];
12
17
  var SUPPORTED_PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
13
18
  var WASI_WARNING_SNIPPET = "WASI is an experimental feature";
@@ -31,7 +36,9 @@ function parseArgs(argv) {
31
36
  verbose: false,
32
37
  force: false,
33
38
  skipConfig: true,
34
- wasmPackage: DEFAULT_BINDING_SPEC
39
+ wasmPackage: DEFAULT_BINDING_PACKAGE,
40
+ wasmVersion: void 0,
41
+ wasmPackageSource: DEFAULT_BINDING_SOURCE
35
42
  };
36
43
  for (let i = 0; i < argv.length; i += 1) {
37
44
  const arg = argv[i];
@@ -57,6 +64,12 @@ function parseArgs(argv) {
57
64
  const pkg = argv[i + 1];
58
65
  if (!pkg) throw new Error("Missing value for --wasm-package");
59
66
  options.wasmPackage = pkg;
67
+ options.wasmPackageSource = "flag";
68
+ i += 1;
69
+ } else if (arg === "--wasm-version") {
70
+ const version = argv[i + 1];
71
+ if (!version) throw new Error("Missing value for --wasm-version");
72
+ options.wasmVersion = version;
60
73
  i += 1;
61
74
  } else if (arg === "--help" || arg === "-h") {
62
75
  printHelp();
@@ -73,6 +86,7 @@ Usage: npx @knighted/jsx init [options]
73
86
  Options:
74
87
  --package-manager, --pm <name> Choose npm | pnpm | yarn | bun
75
88
  --wasm-package <spec> Override binding package spec
89
+ --wasm-version <version> Pin version for the default binding
76
90
  --config Prompt to help with loader config
77
91
  --skip-config Skip any loader config prompts (default)
78
92
  --dry-run Print actions without executing
@@ -128,6 +142,66 @@ function parsePackageName(spec) {
128
142
  const [, name, version] = match;
129
143
  return { name, version };
130
144
  }
145
+ function readLocalOxcParserVersion(resolver = CLI_REQUIRE) {
146
+ try {
147
+ const pkg = resolver("oxc-parser/package.json");
148
+ if (pkg && typeof pkg.version === "string") {
149
+ return pkg.version;
150
+ }
151
+ } catch {
152
+ }
153
+ return void 0;
154
+ }
155
+ function isRegistryResolvablePackage(name) {
156
+ if (!name) return false;
157
+ if (name.startsWith(".") || name.startsWith("/")) return false;
158
+ if (path.isAbsolute(name)) return false;
159
+ const blockedPrefixes = [
160
+ "file:",
161
+ "link:",
162
+ "git+",
163
+ "github:",
164
+ "workspace:",
165
+ "http://",
166
+ "https://"
167
+ ];
168
+ return !blockedPrefixes.some((prefix) => name.startsWith(prefix));
169
+ }
170
+ function fetchLatestBindingVersion(pkgName, execFn = execFileSync) {
171
+ const output = execFn("npm", ["view", pkgName, "version", "--json"], {
172
+ encoding: "utf8",
173
+ stdio: ["ignore", "pipe", "inherit"]
174
+ }).trim();
175
+ if (!output) {
176
+ throw new Error(`Unable to determine latest version for ${pkgName}`);
177
+ }
178
+ const parsed = JSON.parse(output);
179
+ if (typeof parsed === "string") return parsed;
180
+ if (Array.isArray(parsed) && parsed.length > 0) {
181
+ const last = parsed[parsed.length - 1];
182
+ if (typeof last === "string") return last;
183
+ }
184
+ throw new Error(`Unable to determine latest version for ${pkgName}`);
185
+ }
186
+ function resolveBindingSpec(spec, explicitVersion, fetchLatest = fetchLatestBindingVersion) {
187
+ let combinedSpec = spec;
188
+ if (explicitVersion) {
189
+ const { name: name2 } = parsePackageName(spec);
190
+ if (!name2) {
191
+ throw new Error(`Unable to parse binding package spec: ${spec}`);
192
+ }
193
+ combinedSpec = `${name2}@${explicitVersion}`;
194
+ }
195
+ const { name, version } = parsePackageName(combinedSpec);
196
+ if (!name) {
197
+ throw new Error(`Unable to parse binding package spec: ${spec}`);
198
+ }
199
+ if (version || !isRegistryResolvablePackage(name)) {
200
+ return { spec: combinedSpec, name, version };
201
+ }
202
+ const latest = fetchLatest(name);
203
+ return { spec: `${name}@${latest}`, name, version: latest };
204
+ }
131
205
  async function installBinding(spec, cwd, dryRun, verbose, pack = runNpmPack) {
132
206
  const { name, version } = parsePackageName(spec);
133
207
  const tarballName = pack(spec, cwd, dryRun, verbose);
@@ -240,10 +314,18 @@ async function main(overrides = {}) {
240
314
  persistBindingSpec: persistBindingSpecImpl = persistBindingSpec,
241
315
  verifyBinding: verifyBindingImpl = verifyBinding,
242
316
  maybeHandleConfigPrompt: maybeHandleConfigPromptImpl = maybeHandleConfigPrompt,
317
+ resolveBindingSpec: resolveBindingSpecImpl = resolveBindingSpec,
318
+ readLocalOxcParserVersion: readLocalOxcParserVersionImpl = readLocalOxcParserVersion,
243
319
  log: logImpl = log
244
320
  } = overrides;
245
321
  const options = parseArgsImpl(process.argv.slice(2));
246
322
  ensurePackageJsonImpl(options.cwd);
323
+ const bundledParserVersion = readLocalOxcParserVersionImpl();
324
+ const desiredBindingVersion = options.wasmVersion ?? (options.wasmPackageSource === "default" ? bundledParserVersion : void 0);
325
+ const resolvedBinding = resolveBindingSpecImpl(
326
+ options.wasmPackage,
327
+ desiredBindingVersion
328
+ );
247
329
  const packageManager = detectPackageManagerImpl(options.cwd, options.packageManager);
248
330
  logImpl(`> Using package manager: ${packageManager}`);
249
331
  const installedRuntimeDeps = installRuntimeDepsImpl(
@@ -254,7 +336,7 @@ async function main(overrides = {}) {
254
336
  options.verbose
255
337
  );
256
338
  const binding = await installBindingImpl(
257
- options.wasmPackage,
339
+ resolvedBinding.spec,
258
340
  options.cwd,
259
341
  options.dryRun,
260
342
  options.verbose
@@ -303,4 +385,4 @@ function suppressExperimentalWasiWarning() {
303
385
  });
304
386
  }
305
387
 
306
- export { detectPackageManager, ensurePackageJson, installBinding, installRuntimeDeps, isDependencyInstalled, main, maybeHandleConfigPrompt, parseArgs, parsePackageName, persistBindingSpec, promptYesNo, runNpmPack, verifyBinding };
388
+ export { detectPackageManager, ensurePackageJson, fetchLatestBindingVersion, installBinding, installRuntimeDeps, isDependencyInstalled, main, maybeHandleConfigPrompt, parseArgs, parsePackageName, persistBindingSpec, promptYesNo, readLocalOxcParserVersion, resolveBindingSpec, runNpmPack, verifyBinding };
@@ -9,8 +9,25 @@ type DataAttributes = {
9
9
  type AriaAttributes = {
10
10
  [K in `aria-${string}`]?: string | number | boolean | null | undefined;
11
11
  };
12
+ type JsxEventListener<EV extends Event> = ((event: EV) => void) | {
13
+ handleEvent(event: EV): void;
14
+ };
15
+ type JsxEventDescriptor<EV extends Event> = {
16
+ handler: JsxEventListener<EV>;
17
+ capture?: boolean;
18
+ once?: boolean;
19
+ passive?: boolean;
20
+ signal?: AbortSignal;
21
+ };
22
+ type JsxEventProp<EV extends Event> = JsxEventListener<EV> | JsxEventDescriptor<EV>;
12
23
  type EventHandlers<T extends EventTarget> = {
13
- [K in keyof GlobalEventHandlersEventMap as `on${Capitalize<string & K>}`]?: (event: GlobalEventHandlersEventMap[K]) => void;
24
+ [K in keyof GlobalEventHandlersEventMap as `on${Capitalize<string & K>}`]?: JsxEventProp<GlobalEventHandlersEventMap[K]>;
25
+ } & {
26
+ [K in keyof GlobalEventHandlersEventMap as `on${Capitalize<string & K>}Capture`]?: JsxEventProp<GlobalEventHandlersEventMap[K]>;
27
+ } & {
28
+ [K in string as K extends '' ? never : `on:${K}`]?: JsxEventProp<CustomEvent<unknown>>;
29
+ } & {
30
+ [K in string as K extends '' ? never : `on:${K}Capture`]?: JsxEventProp<CustomEvent<unknown>>;
14
31
  };
15
32
  type ElementProps<Tag extends keyof HTMLElementTagNameMap> = Omit<Partial<HTMLElementTagNameMap[Tag]>, 'children'> & EventHandlers<HTMLElementTagNameMap[Tag]> & DataAttributes & AriaAttributes & {
16
33
  class?: string;
package/dist/jsx.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { parseSync } from 'oxc-parser';
2
2
  import { buildTemplate, evaluateExpression, extractRootNode, formatParserError, getIdentifierName, normalizeJsxTextSegments, parserOptions, } from './runtime/shared.js';
3
+ import { find as findPropertyInfo, html as htmlProperties, svg as svgProperties, } from 'property-information';
3
4
  const ensureDomAvailable = () => {
4
5
  if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
5
6
  throw new Error('The jsx template tag requires a DOM-like environment (document missing).');
@@ -23,8 +24,131 @@ const isPromiseLike = (value) => {
23
24
  }
24
25
  return typeof value.then === 'function';
25
26
  };
26
- const setDomProp = (element, name, value) => {
27
- if (value === false || value === null || value === undefined) {
27
+ const ATTRIBUTE_NAMESPACE_URIS = {
28
+ xlink: 'http://www.w3.org/1999/xlink',
29
+ xml: 'http://www.w3.org/XML/1998/namespace',
30
+ xmlns: 'http://www.w3.org/2000/xmlns/',
31
+ };
32
+ const getAttributeNamespace = (space) => {
33
+ if (!space || space === 'html' || space === 'svg') {
34
+ return undefined;
35
+ }
36
+ return ATTRIBUTE_NAMESPACE_URIS[space];
37
+ };
38
+ const getSchemaForNamespace = (namespace) => namespace === 'svg' ? svgProperties : htmlProperties;
39
+ const setAttributeValue = (element, info, value) => {
40
+ const namespaceUri = getAttributeNamespace(info.space);
41
+ const attrValue = String(value);
42
+ if (namespaceUri) {
43
+ element.setAttributeNS(namespaceUri, info.attribute, attrValue);
44
+ return;
45
+ }
46
+ element.setAttribute(info.attribute, attrValue);
47
+ };
48
+ const removeAttributeValue = (element, info) => {
49
+ const namespaceUri = getAttributeNamespace(info.space);
50
+ if (namespaceUri) {
51
+ element.removeAttributeNS(namespaceUri, info.attribute);
52
+ return;
53
+ }
54
+ element.removeAttribute(info.attribute);
55
+ };
56
+ const joinTokenList = (value, delimiter) => {
57
+ if (!Array.isArray(value)) {
58
+ return value;
59
+ }
60
+ return value.filter(Boolean).join(delimiter);
61
+ };
62
+ const shouldAssignProperty = (element, namespace, info) => {
63
+ if (namespace === 'svg') {
64
+ return false;
65
+ }
66
+ if (!info.property || info.property.includes(':')) {
67
+ return false;
68
+ }
69
+ return info.property in element;
70
+ };
71
+ const captureSuffix = 'Capture';
72
+ const stripCaptureSuffix = (rawName) => {
73
+ if (rawName.endsWith(captureSuffix) && rawName.length > captureSuffix.length) {
74
+ return { eventName: rawName.slice(0, -captureSuffix.length), capture: true };
75
+ }
76
+ return { eventName: rawName, capture: false };
77
+ };
78
+ const parseEventPropName = (name) => {
79
+ if (!name.startsWith('on')) {
80
+ return null;
81
+ }
82
+ if (name.startsWith('on:')) {
83
+ const raw = name.slice(3);
84
+ if (!raw) {
85
+ return null;
86
+ }
87
+ const parsed = stripCaptureSuffix(raw);
88
+ if (!parsed.eventName) {
89
+ return null;
90
+ }
91
+ return parsed;
92
+ }
93
+ const raw = name.slice(2);
94
+ if (!raw) {
95
+ return null;
96
+ }
97
+ const parsed = stripCaptureSuffix(raw);
98
+ if (!parsed.eventName) {
99
+ return null;
100
+ }
101
+ return {
102
+ eventName: parsed.eventName.toLowerCase(),
103
+ capture: parsed.capture,
104
+ };
105
+ };
106
+ const isEventListenerObject = (value) => {
107
+ if (!value || typeof value !== 'object') {
108
+ return false;
109
+ }
110
+ return ('handleEvent' in value &&
111
+ typeof value.handleEvent === 'function');
112
+ };
113
+ const isEventHandlerDescriptor = (value) => {
114
+ if (!value || typeof value !== 'object' || !('handler' in value)) {
115
+ return false;
116
+ }
117
+ const handler = value.handler;
118
+ if (typeof handler === 'function') {
119
+ return true;
120
+ }
121
+ return isEventListenerObject(handler);
122
+ };
123
+ const resolveEventHandlerValue = (value) => {
124
+ if (typeof value === 'function' || isEventListenerObject(value)) {
125
+ return { listener: value };
126
+ }
127
+ if (!isEventHandlerDescriptor(value)) {
128
+ return null;
129
+ }
130
+ const descriptor = value;
131
+ let options = descriptor.options ? { ...descriptor.options } : undefined;
132
+ const assignOption = (key, optionValue) => {
133
+ if (optionValue === undefined || optionValue === null) {
134
+ return;
135
+ }
136
+ if (!options) {
137
+ options = {};
138
+ }
139
+ options[key] = optionValue;
140
+ };
141
+ assignOption('capture', descriptor.capture);
142
+ assignOption('once', descriptor.once);
143
+ assignOption('passive', descriptor.passive);
144
+ assignOption('signal', descriptor.signal ?? undefined);
145
+ return {
146
+ listener: descriptor.handler,
147
+ options,
148
+ };
149
+ };
150
+ const setDomProp = (element, name, value, namespace) => {
151
+ if (value === null || value === undefined) {
28
152
  return;
29
153
  }
30
154
  if (name === 'dangerouslySetInnerHTML' &&
@@ -64,27 +188,75 @@ const setDomProp = (element, name, value) => {
64
188
  });
65
189
  return;
66
190
  }
67
- if (typeof value === 'function' && name.startsWith('on')) {
68
- const eventName = name.slice(2).toLowerCase();
69
- element.addEventListener(eventName, value);
191
+ const eventBinding = parseEventPropName(name);
192
+ if (eventBinding) {
193
+ const handlerValue = resolveEventHandlerValue(value);
194
+ if (handlerValue) {
195
+ let options = handlerValue.options ? { ...handlerValue.options } : undefined;
196
+ if (eventBinding.capture) {
197
+ if (!options) {
198
+ options = { capture: true };
199
+ }
200
+ else {
201
+ options.capture = true;
202
+ }
203
+ }
204
+ element.addEventListener(eventBinding.eventName, handlerValue.listener, options);
205
+ return;
206
+ }
207
+ }
208
+ const info = findPropertyInfo(getSchemaForNamespace(namespace), name);
209
+ const elementWithIndex = element;
210
+ const canAssignProperty = shouldAssignProperty(element, namespace, info);
211
+ if (info.mustUseProperty) {
212
+ const nextValue = info.boolean ? Boolean(value) : value;
213
+ elementWithIndex[info.property] = nextValue;
70
214
  return;
71
215
  }
72
- if (name === 'class' || name === 'className') {
73
- const classValue = Array.isArray(value)
74
- ? value.filter(Boolean).join(' ')
75
- : String(value);
76
- element.setAttribute('class', classValue);
216
+ if (info.boolean) {
217
+ const boolValue = Boolean(value);
218
+ if (canAssignProperty) {
219
+ elementWithIndex[info.property] = boolValue;
220
+ }
221
+ if (boolValue) {
222
+ setAttributeValue(element, info, '');
223
+ }
224
+ else {
225
+ removeAttributeValue(element, info);
226
+ }
77
227
  return;
78
228
  }
79
- if (name === 'htmlFor') {
80
- element.setAttribute('for', String(value));
229
+ let normalizedValue = value;
230
+ if (info.spaceSeparated) {
231
+ normalizedValue = joinTokenList(value, ' ');
232
+ }
233
+ else if (info.commaSeparated) {
234
+ normalizedValue = joinTokenList(value, ',');
235
+ }
236
+ else if (info.commaOrSpaceSeparated) {
237
+ normalizedValue = joinTokenList(value, ' ');
238
+ }
239
+ if (info.booleanish && typeof normalizedValue === 'boolean') {
240
+ normalizedValue = normalizedValue ? 'true' : 'false';
241
+ }
242
+ if (info.overloadedBoolean) {
243
+ if (normalizedValue === false) {
244
+ removeAttributeValue(element, info);
245
+ return;
246
+ }
247
+ if (normalizedValue === true) {
248
+ setAttributeValue(element, info, '');
249
+ return;
250
+ }
251
+ }
252
+ if (canAssignProperty) {
253
+ elementWithIndex[info.property] = normalizedValue;
81
254
  return;
82
255
  }
83
- if (name in element && !name.includes('-')) {
84
- element[name] = value;
256
+ if (normalizedValue === false) {
85
257
  return;
86
258
  }
87
- element.setAttribute(name, value === true ? '' : String(value));
259
+ setAttributeValue(element, info, normalizedValue);
88
260
  };
89
261
  const appendChildValue = (parent, value) => {
90
262
  if (value === null || value === undefined) {
@@ -151,7 +323,7 @@ const applyDomAttributes = (element, attributes, ctx, namespace) => {
151
323
  appendChildValue(element, value);
152
324
  return;
153
325
  }
154
- setDomProp(element, name, value);
326
+ setDomProp(element, name, value, namespace);
155
327
  });
156
328
  };
157
329
  const evaluateJsxChildren = (children, ctx, namespace) => {
@@ -1,4 +1,4 @@
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+=`
1
+ import {parseSync}from'oxc-parser';import {find,svg,html}from'property-information';var F=/<\s*$/,B=/<\/\s*$/,T="__KX_EXPR__",C=new RegExp(`${T}\\d+_\\d+__`,"g"),M=0,N={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},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};
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.")},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 ""}},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),c=t;if(s&&(c=c.replace(/^\s+/,"")),a&&(c=c.replace(/\s+$/,"")),c.length===0||c.trim().length===0)return [];let i=[];C.lastIndex=0;let p=0,l;for(;l=C.exec(c);){let u=l.index,m=c.slice(p,u);m&&i.push(m);let f=l[0];n.has(f)?i.push(n.get(f)):i.push(f),p=u+f.length;}let d=c.slice(p);return d&&i.push(d),i},D=(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)},P=(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=D(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}`)}},W=e=>{let n=e.replace(/[^a-zA-Z0-9_$]/g,"");return n?/[A-Za-z_$]/.test(n[0])?n:`Component${n}`:"Component"},H=(e,n,t)=>{let r=t.get(e);if(r)return r;let o=e.displayName||e.name||`Component${n.length}`,s=W(o??""),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},R=(e,n)=>{let t=e.raw??e,r=new Map,o=[],s=new Map,a=t[0]??"",c=M++,i=0;for(let p=0;p<n.length;p++){let l=t[p]??"",d=t[p+1]??"",u=n[p],m=F.test(l)||B.test(l);if(m&&typeof u=="function"){let $=H(u,o,s);a+=$.name+d;continue}if(m&&typeof u=="string"){a+=u+d;continue}let f=`${T}${c}_${i++}__`;r.set(f,u),a+=f+d;}return {source:a,placeholders:r,bindings:o}};var Z=()=>{if(typeof document>"u"||typeof document.createElement!="function")throw new Error("The jsx template tag requires a DOM-like environment (document missing).")},G=e=>typeof Node>"u"?false:e instanceof Node||e instanceof DocumentFragment,q=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",Q={xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"},I=e=>{if(!(!e||e==="html"||e==="svg"))return Q[e]},Y=e=>e==="svg"?svg:html,h=(e,n,t)=>{let r=I(n.space),o=String(t);if(r){e.setAttributeNS(r,n.attribute,o);return}e.setAttribute(n.attribute,o);},j=(e,n)=>{let t=I(n.space);if(t){e.removeAttributeNS(t,n.attribute);return}e.removeAttribute(n.attribute);},b=(e,n)=>Array.isArray(e)?e.filter(Boolean).join(n):e,ee=(e,n,t)=>n==="svg"||!t.property||t.property.includes(":")?false:t.property in e,S="Capture",O=e=>e.endsWith(S)&&e.length>S.length?{eventName:e.slice(0,-S.length),capture:true}:{eventName:e,capture:false},ne=e=>{if(!e.startsWith("on"))return null;if(e.startsWith("on:")){let r=e.slice(3);if(!r)return null;let o=O(r);return o.eventName?o:null}let n=e.slice(2);if(!n)return null;let t=O(n);return t.eventName?{eventName:t.eventName.toLowerCase(),capture:t.capture}:null},L=e=>!e||typeof e!="object"?false:"handleEvent"in e&&typeof e.handleEvent=="function",te=e=>{if(!e||typeof e!="object"||!("handler"in e))return false;let n=e.handler;return typeof n=="function"?true:L(n)},re=e=>{if(typeof e=="function"||L(e))return {listener:e};if(!te(e))return null;let n=e,t=n.options?{...n.options}:void 0,r=(o,s)=>{s!=null&&(t||(t={}),t[o]=s);};return r("capture",n.capture),r("once",n.once),r("passive",n.passive),r("signal",n.signal??void 0),{listener:n.handler,options:t}},oe=(e,n,t,r)=>{if(t==null)return;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 p=t,l=e.style;if(!l)return;let d=l;Object.entries(p).forEach(([u,m])=>{if(m!=null){if(u.startsWith("--")){l.setProperty(u,String(m));return}d[u]=m;}});return}let o=ne(n);if(o){let p=re(t);if(p){let l=p.options?{...p.options}:void 0;o.capture&&(l?l.capture=true:l={capture:true}),e.addEventListener(o.eventName,p.listener,l);return}}let s=find(Y(r),n),a=e,c=ee(e,r,s);if(s.mustUseProperty){let p=s.boolean?!!t:t;a[s.property]=p;return}if(s.boolean){let p=!!t;c&&(a[s.property]=p),p?h(e,s,""):j(e,s);return}let i=t;if(s.spaceSeparated?i=b(t," "):s.commaSeparated?i=b(t,","):s.commaOrSpaceSeparated&&(i=b(t," ")),s.booleanish&&typeof i=="boolean"&&(i=i?"true":"false"),s.overloadedBoolean){if(i===false){j(e,s);return}if(i===true){h(e,s,"");return}}if(c){a[s.property]=i;return}i!==false&&h(e,s,i);},g=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(v(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(q(n)){for(let t of n)g(e,t);return}if(G(n)){e.appendChild(n);return}e.appendChild(document.createTextNode(String(n)));}},x=(e,n,t)=>P(e,n,r=>w(r,n,t)),_=(e,n,t)=>{let r={};return e.forEach(o=>{if(o.type==="JSXSpreadAttribute"){let a=x(o.argument,n,t);a&&typeof a=="object"&&!Array.isArray(a)&&Object.assign(r,a);return}let s=y(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]=x(o.value.expression,n,t);}}),r},se=(e,n,t,r)=>{let o=_(n,t,r);Object.entries(o).forEach(([s,a])=>{if(s!=="key"){if(s==="children"){g(e,a);return}oe(e,s,a,r);}});},J=(e,n,t)=>{let r=[];return e.forEach(o=>{switch(o.type){case "JSXText":{X(o.value,n.placeholders).forEach(a=>{r.push(a);});break}case "JSXExpressionContainer":{if(o.expression.type==="JSXEmptyExpression")break;r.push(x(o.expression,n,t));break}case "JSXSpreadChild":{let s=x(o.expression,n,t);s!=null&&r.push(s);break}case "JSXElement":case "JSXFragment":{r.push(w(o,n,t));break}}}),r},ae=(e,n,t,r)=>{let o=_(e.openingElement.attributes,n,r),s=J(e.children,n,r);s.length===1?o.children=s[0]:s.length>1&&(o.children=s);let a=t(o);if(v(a))throw new Error("Async jsx components are not supported.");return a},ie=(e,n,t)=>{let r=e.openingElement,o=y(r.name),s=n.components.get(o);if(s)return ae(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,c=o==="foreignObject"?null:a,i=a==="svg"?document.createElementNS("http://www.w3.org/2000/svg",o):document.createElement(o);return se(i,r.attributes,n,a),J(e.children,n,c).forEach(l=>g(i,l)),i},w=(e,n,t)=>{if(e.type==="JSXFragment"){let r=document.createDocumentFragment();return J(e.children,n,t).forEach(s=>g(r,s)),r}return ie(e,n,t)},pe=(e,...n)=>{Z();let t=R(e,n),r=parseSync("inline.jsx",t.source,N);if(r.errors.length>0)throw new Error(A(r.errors[0]));let o=k(r.program),s={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(a=>[a.name,a.value]))};return w(o,s,null)};
4
+ export{pe as jsx};
@@ -1,4 +1,4 @@
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+=`
1
+ import {parseSync}from'oxc-parser';import {find,svg,html}from'property-information';var k="<!doctype html><html><body></body></html>",V=["window","self","document","HTMLElement","Element","Node","DocumentFragment","customElements","Text","Comment","MutationObserver","navigator"],K=()=>typeof document<"u"&&typeof document.createElement=="function",U=e=>{let n=globalThis,t=e;V.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(k);return n},S=async()=>{let{JSDOM:e}=await import('jsdom'),{window:n}=new e(k);return n},z=()=>{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"},G=()=>{let e=z();return e==="linkedom"?[E,S]:e==="jsdom"?[S,E]:[E,S]},Z=async()=>{let e=[];for(let t of G())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)},y=null,A=async()=>{if(!K())return y||(y=(async()=>{let e=await Z();U(e);})().catch(e=>{throw y=null,e})),y};var q=/<\s*$/,Y=/<\/\s*$/,P="__KX_EXPR__",X=new RegExp(`${P}\\d+_\\d+__`,"g"),Q=0,O={lang:"jsx",sourceType:"module",range:true,preserveParens:true},R=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},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};
3
+ ${e.codeframe}`),n},j=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.")},h=e=>{switch(e.type){case "JSXIdentifier":return e.name;case "JSXNamespacedName":return `${e.namespace.name}:${e.name.name}`;case "JSXMemberExpression":return `${h(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);}}));},v=(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),i=/\n/.test(o),p=t;if(s&&(p=p.replace(/^\s+/,"")),i&&(p=p.replace(/\s+$/,"")),p.length===0||p.trim().length===0)return [];let a=[];X.lastIndex=0;let c=0,l;for(;l=X.exec(p);){let m=l.index,u=p.slice(c,m);u&&a.push(u);let f=l[0];n.has(f)?a.push(n.get(f)):a.push(f),c=m+f.length;}let d=p.slice(c);return d&&a.push(d),a},ee=(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)},L=(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),i=ee(e,n);try{let p=new Function(...i,`"use strict"; return (${s});`),a=i.map(c=>n.placeholders.get(c));return p(...a)}catch(p){throw new Error(`Failed to evaluate expression ${s}: ${p.message}`)}},ne=e=>{let n=e.replace(/[^a-zA-Z0-9_$]/g,"");return n?/[A-Za-z_$]/.test(n[0])?n:`Component${n}`:"Component"},te=(e,n,t)=>{let r=t.get(e);if(r)return r;let o=e.displayName||e.name||`Component${n.length}`,s=ne(o??""),i=s,p=1;for(;n.some(c=>c.name===i);)i=`${s}${p++}`;let a={name:i,value:e};return n.push(a),t.set(e,a),a},_=(e,n)=>{let t=e.raw??e,r=new Map,o=[],s=new Map,i=t[0]??"",p=Q++,a=0;for(let c=0;c<n.length;c++){let l=t[c]??"",d=t[c+1]??"",m=n[c],u=q.test(l)||Y.test(l);if(u&&typeof m=="function"){let W=te(m,o,s);i+=W.name+d;continue}if(u&&typeof m=="string"){i+=m+d;continue}let f=`${P}${p}_${a++}__`;r.set(f,m),i+=f+d;}return {source:i,placeholders:r,bindings:o}};var ae=()=>{if(typeof document>"u"||typeof document.createElement!="function")throw new Error("The jsx template tag requires a DOM-like environment (document missing).")},ce=e=>typeof Node>"u"?false:e instanceof Node||e instanceof DocumentFragment,pe=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",M=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",le={xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"},$=e=>{if(!(!e||e==="html"||e==="svg"))return le[e]},me=e=>e==="svg"?svg:html,b=(e,n,t)=>{let r=$(n.space),o=String(t);if(r){e.setAttributeNS(r,n.attribute,o);return}e.setAttribute(n.attribute,o);},I=(e,n)=>{let t=$(n.space);if(t){e.removeAttributeNS(t,n.attribute);return}e.removeAttribute(n.attribute);},J=(e,n)=>Array.isArray(e)?e.filter(Boolean).join(n):e,ue=(e,n,t)=>n==="svg"||!t.property||t.property.includes(":")?false:t.property in e,C="Capture",D=e=>e.endsWith(C)&&e.length>C.length?{eventName:e.slice(0,-C.length),capture:true}:{eventName:e,capture:false},de=e=>{if(!e.startsWith("on"))return null;if(e.startsWith("on:")){let r=e.slice(3);if(!r)return null;let o=D(r);return o.eventName?o:null}let n=e.slice(2);if(!n)return null;let t=D(n);return t.eventName?{eventName:t.eventName.toLowerCase(),capture:t.capture}:null},F=e=>!e||typeof e!="object"?false:"handleEvent"in e&&typeof e.handleEvent=="function",fe=e=>{if(!e||typeof e!="object"||!("handler"in e))return false;let n=e.handler;return typeof n=="function"?true:F(n)},ge=e=>{if(typeof e=="function"||F(e))return {listener:e};if(!fe(e))return null;let n=e,t=n.options?{...n.options}:void 0,r=(o,s)=>{s!=null&&(t||(t={}),t[o]=s);};return r("capture",n.capture),r("once",n.once),r("passive",n.passive),r("signal",n.signal??void 0),{listener:n.handler,options:t}},ye=(e,n,t,r)=>{if(t==null)return;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 c=t,l=e.style;if(!l)return;let d=l;Object.entries(c).forEach(([m,u])=>{if(u!=null){if(m.startsWith("--")){l.setProperty(m,String(u));return}d[m]=u;}});return}let o=de(n);if(o){let c=ge(t);if(c){let l=c.options?{...c.options}:void 0;o.capture&&(l?l.capture=true:l={capture:true}),e.addEventListener(o.eventName,c.listener,l);return}}let s=find(me(r),n),i=e,p=ue(e,r,s);if(s.mustUseProperty){let c=s.boolean?!!t:t;i[s.property]=c;return}if(s.boolean){let c=!!t;p&&(i[s.property]=c),c?b(e,s,""):I(e,s);return}let a=t;if(s.spaceSeparated?a=J(t," "):s.commaSeparated?a=J(t,","):s.commaOrSpaceSeparated&&(a=J(t," ")),s.booleanish&&typeof a=="boolean"&&(a=a?"true":"false"),s.overloadedBoolean){if(a===false){I(e,s);return}if(a===true){b(e,s,"");return}}if(p){i[s.property]=a;return}a!==false&&b(e,s,a);},g=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(M(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(pe(n)){for(let t of n)g(e,t);return}if(ce(n)){e.appendChild(n);return}e.appendChild(document.createTextNode(String(n)));}},x=(e,n,t)=>L(e,n,r=>N(r,n,t)),B=(e,n,t)=>{let r={};return e.forEach(o=>{if(o.type==="JSXSpreadAttribute"){let i=x(o.argument,n,t);i&&typeof i=="object"&&!Array.isArray(i)&&Object.assign(r,i);return}let s=h(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]=x(o.value.expression,n,t);}}),r},he=(e,n,t,r)=>{let o=B(n,t,r);Object.entries(o).forEach(([s,i])=>{if(s!=="key"){if(s==="children"){g(e,i);return}ye(e,s,i,r);}});},T=(e,n,t)=>{let r=[];return e.forEach(o=>{switch(o.type){case "JSXText":{v(o.value,n.placeholders).forEach(i=>{r.push(i);});break}case "JSXExpressionContainer":{if(o.expression.type==="JSXEmptyExpression")break;r.push(x(o.expression,n,t));break}case "JSXSpreadChild":{let s=x(o.expression,n,t);s!=null&&r.push(s);break}case "JSXElement":case "JSXFragment":{r.push(N(o,n,t));break}}}),r},xe=(e,n,t,r)=>{let o=B(e.openingElement.attributes,n,r),s=T(e.children,n,r);s.length===1?o.children=s[0]:s.length>1&&(o.children=s);let i=t(o);if(M(i))throw new Error("Async jsx components are not supported.");return i},Ee=(e,n,t)=>{let r=e.openingElement,o=h(r.name),s=n.components.get(o);if(s)return xe(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 i=o==="svg"?"svg":t,p=o==="foreignObject"?null:i,a=i==="svg"?document.createElementNS("http://www.w3.org/2000/svg",o):document.createElement(o);return he(a,r.attributes,n,i),T(e.children,n,p).forEach(l=>g(a,l)),a},N=(e,n,t)=>{if(e.type==="JSXFragment"){let r=document.createDocumentFragment();return T(e.children,n,t).forEach(s=>g(r,s)),r}return Ee(e,n,t)},H=(e,...n)=>{ae();let t=_(e,n),r=parseSync("inline.jsx",t.source,O);if(r.errors.length>0)throw new Error(R(r.errors[0]));let o=j(r.program),s={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(i=>[i.name,i.value]))};return N(o,s,null)};await A();var Ae=H;
4
+ export{Ae as jsx};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/jsx",
3
- "version": "1.4.1",
3
+ "version": "1.5.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",
@@ -89,6 +89,7 @@
89
89
  "check-types:lib": "tsc --noEmit --project tsconfig.json",
90
90
  "check-types:demo": "tsc --noEmit --project examples/browser/tsconfig.json",
91
91
  "lint": "eslint src test",
92
+ "cycles": "madge src --circular --extensions ts,tsx,js,jsx --ts-config tsconfig.json",
92
93
  "prettier": "prettier -w .",
93
94
  "prettier:check": "prettier --check .",
94
95
  "test": "KNIGHTED_JSX_CLI_TEST=1 vitest run --coverage",
@@ -107,7 +108,7 @@
107
108
  "devDependencies": {
108
109
  "@eslint/js": "^9.39.1",
109
110
  "@knighted/duel": "^2.1.6",
110
- "@oxc-project/types": "^0.99.0",
111
+ "@oxc-project/types": "^0.105.0",
111
112
  "@playwright/test": "^1.57.0",
112
113
  "@rspack/core": "^1.0.5",
113
114
  "@types/jsdom": "^27.0.0",
@@ -115,14 +116,17 @@
115
116
  "@types/react": "^19.2.7",
116
117
  "@types/react-dom": "^19.2.3",
117
118
  "@vitest/coverage-v8": "^4.0.14",
119
+ "@vitest/eslint-plugin": "^1.6.4",
118
120
  "eslint": "^9.39.1",
119
121
  "eslint-plugin-n": "^17.10.3",
120
122
  "eslint-plugin-playwright": "^2.4.0",
123
+ "eslint-plugin-unicorn": "^62.0.0",
121
124
  "http-server": "^14.1.1",
122
125
  "husky": "^9.1.7",
123
126
  "jsdom": "^27.2.0",
124
127
  "lint-staged": "^16.2.7",
125
128
  "lit": "^3.2.1",
129
+ "madge": "^8.0.0",
126
130
  "next": "^16.0.0",
127
131
  "prettier": "^3.7.3",
128
132
  "react": "^19.0.0",
@@ -135,7 +139,8 @@
135
139
  },
136
140
  "dependencies": {
137
141
  "magic-string": "^0.30.21",
138
- "oxc-parser": "^0.99.0",
142
+ "oxc-parser": "^0.105.0",
143
+ "property-information": "^7.1.0",
139
144
  "tar": "^7.4.3"
140
145
  },
141
146
  "peerDependencies": {
@@ -155,9 +160,14 @@
155
160
  }
156
161
  },
157
162
  "optionalDependencies": {
158
- "@oxc-parser/binding-darwin-arm64": "^0.99.0",
159
- "@oxc-parser/binding-linux-x64-gnu": "^0.99.0",
160
- "@oxc-parser/binding-wasm32-wasi": "^0.99.0"
163
+ "@oxc-parser/binding-darwin-arm64": "^0.105.0",
164
+ "@oxc-parser/binding-linux-x64-gnu": "^0.105.0",
165
+ "@oxc-parser/binding-wasm32-wasi": "0.105.0"
166
+ },
167
+ "overrides": {
168
+ "module-lookup-amd": {
169
+ "glob": "^9.0.0"
170
+ }
161
171
  },
162
172
  "files": [
163
173
  "dist"
@@ -178,6 +188,7 @@
178
188
  "singleQuote": true
179
189
  },
180
190
  "lint-staged": {
191
+ "src/**/*.{ts,tsx,js,jsx}": "npm run cycles",
181
192
  "*.{js,jsx,ts,tsx,mjs,cjs,cts,mts}": "eslint --max-warnings=0",
182
193
  "*.{js,jsx,ts,tsx,mjs,cjs,cts,mts,json,md,css,scss,html}": "prettier --check"
183
194
  }