@knighted/jsx 1.3.1 → 1.4.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
@@ -18,10 +18,10 @@ A runtime JSX template tag backed by the [`oxc-parser`](https://github.com/oxc-p
18
18
  - [React runtime](#react-runtime-reactjsx)
19
19
  - [Loader integration](#loader-integration)
20
20
  - [Node / SSR usage](#node--ssr-usage)
21
- - [Next.js integration](#nextjs-integration)
22
21
  - [Browser usage](#browser-usage)
22
+ - [TypeScript plugin](docs/ts-plugin.md)
23
+ - [TypeScript guide](docs/typescript.md)
23
24
  - [Component testing](docs/testing.md)
24
- - [Testing & demos](#testing)
25
25
  - [CLI setup](docs/cli.md)
26
26
 
27
27
  ## Installation
@@ -31,7 +31,7 @@ npm install @knighted/jsx
31
31
  ```
32
32
 
33
33
  > [!IMPORTANT]
34
- > This package is ESM-only and targets browsers or ESM-aware bundlers. `require()` is not supported; use native `import`/`<script type="module">` and a DOM-like environment.
34
+ > `@knighted/jsx` ships as ESM-only. The dual-mode `.cjs` artifacts we build internally are not published.
35
35
 
36
36
  > [!NOTE]
37
37
  > Planning to use the React runtime (`@knighted/jsx/react`)? Install `react@>=18` and `react-dom@>=18` alongside this package so the helper can create elements and render them through ReactDOM.
@@ -95,6 +95,51 @@ createRoot(document.getElementById('root')!).render(reactJsx`<${App} />`)
95
95
 
96
96
  The React runtime shares the same template semantics as `jsx`, except it returns React elements (via `React.createElement`) so you can embed other React components with `<${MyComponent} />` and use hooks/state as usual. The helper lives in a separate subpath so DOM-only consumers never pay the React dependency cost.
97
97
 
98
+ ### DOM-specific props
99
+
100
+ - `style` accepts either a string or an object. Object values handle CSS custom properties (`--token`) automatically.
101
+ - `class` and `className` both work and can be strings or arrays.
102
+ - Event handlers use the `on<Event>` naming convention (e.g. `onClick`).
103
+ - `ref` supports callback refs as well as mutable `{ current }` objects.
104
+ - `dangerouslySetInnerHTML` expects an object with an `__html` field, mirroring React.
105
+
106
+ ### Fragments & SVG
107
+
108
+ Use JSX fragments (`<>...</>`) for multi-root templates. SVG trees automatically switch to the `http://www.w3.org/2000/svg` namespace once they enter an `<svg>` tag, and fall back inside `<foreignObject>`.
109
+
110
+ ### Interpolations and components
111
+
112
+ - `${...}` works exactly like JSX braces: drop expressions anywhere (text, attributes, spreads, conditionals) and the runtime keeps the original syntax. Text nodes do not need extra wrapping—`Count is ${value}` already works.
113
+ - Interpolated values can be primitives, DOM nodes, arrays/iterables, other `jsx` trees, or component functions. Resolve Promises before passing them in.
114
+ - Inline components are just functions/classes you interpolate as the tag name; they receive props plus optional `children` and can return anything `jsx` accepts.
115
+
116
+ ```ts
117
+ const Button = ({ variant = 'primary' }) => {
118
+ let count = 3
119
+
120
+ return jsx`
121
+ <button
122
+ data-variant=${variant}
123
+ onClick=${() => {
124
+ count += 1
125
+ console.log(`Count is now ${count}`)
126
+ }}
127
+ >
128
+ Count is ${count}
129
+ </button>
130
+ `
131
+ }
132
+
133
+ const view = jsx`
134
+ <section>
135
+ <p>Inline components can manage their own state.</p>
136
+ <${Button} variant="ghost" />
137
+ </section>
138
+ `
139
+
140
+ document.body.append(view)
141
+ ```
142
+
98
143
  ## Loader integration
99
144
 
100
145
  Use the published loader entry (`@knighted/jsx/loader`) when you want your bundler to rewrite tagged template literals at build time. The loader finds every ` jsx`` ` (and, by default, ` reactJsx`` ` ) invocation, rebuilds the template with real JSX semantics, and hands back transformed source that can run in any environment.
@@ -122,24 +167,9 @@ export default {
122
167
  }
123
168
  ```
124
169
 
125
- Pair the loader with your existing TypeScript/JSX transpiler (SWC, Babel, Rspack’s builtin loader, etc.) so regular React components and the tagged templates can live side by side. The demo fixture under `test/fixtures/rspack-app` shows a full setup that mixes Lit and React, and there is also a standalone walkthrough at [morganney/jsx-loader-demo](https://github.com/morganney/jsx-loader-demo):
170
+ Pair the loader with your existing TypeScript/JSX transpiler (SWC, Babel, Rspack’s builtin loader, etc.) so regular React components and the tagged templates can live side by side.
126
171
 
127
- ```sh
128
- npm run build
129
- npm run setup:wasm
130
- npm run build:fixture
131
- ```
132
-
133
- Then point a static server at the fixture root (which serves `index.html` plus the bundled `dist/hybrid.js` and `dist/reactMode.js`) to see it in a browser:
134
-
135
- ```sh
136
- # Serve the rspack fixture from the repo root
137
- npx http-server test/fixtures/rspack-app -p 4173
138
- ```
139
-
140
- Visit `http://localhost:4173` (or whichever port you pick) to interact with both the Lit + React hybrid demo and the React-mode bundle.
141
-
142
- Need a deeper dive into loader behavior and options? Check out [`src/loader/README.md`](src/loader/README.md) for a full walkthrough.
172
+ Need a deeper dive into loader behavior and options? Check out [`src/loader/README.md`](src/loader/README.md). There is also a standalone walkthrough at [morganney/jsx-loader-demo](https://github.com/morganney/jsx-loader-demo).
143
173
 
144
174
  ## Node / SSR usage
145
175
 
@@ -175,126 +205,46 @@ console.log(shell.outerHTML)
175
205
 
176
206
  This repository ships a ready-to-run fixture under `test/fixtures/node-ssr` that uses the Node entry to render a Lit shell plus a React subtree through `ReactDOMServer.renderToString`. Run `npm run build` once to emit `dist/`, then execute `npm run demo:node-ssr` to log the generated markup.
177
207
 
178
- ## Next.js integration
208
+ See how to [integrate with Next.js](./docs/nextjs-integration.md).
179
209
 
180
- > [!IMPORTANT]
181
- > Next already compiles `.tsx/.jsx` files, so you do not need this helper to author regular components. The loader only adds value when you want to reuse the tagged template runtime during SSR—mixing DOM nodes built by `jsx` with React markup, rendering shared utilities on the server, or processing tagged templates outside the usual component pipeline.
182
-
183
- Next (and Remix/other Webpack-based SSR stacks) can run the loader by adding a post-loader to the framework config so the template tags are rewritten after SWC/Babel transpilation. The fixture under `test/fixtures/next-app` ships a complete example that mixes DOM and React helpers during SSR so you can pre-render DOM snippets (for emails, HTML streams, CMS content, etc.) while still returning React components from your pages. The important bits live in `next.config.mjs`:
184
-
185
- ```js
186
- import path from 'node:path'
187
- import { fileURLToPath } from 'node:url'
210
+ ## TypeScript integration
188
211
 
189
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
190
- const repoRoot = path.resolve(__dirname, '../../..')
191
- const distDir = path.join(repoRoot, 'dist')
212
+ The [`@knighted/jsx-ts-plugin`](docs/ts-plugin.md) keeps DOM (`jsx`) and React (`reactJsx`) templates type-safe with a single config block. The plugin maps each helper to the right mode by default, so you can mix DOM nodes and React components in the same file without juggling multiple plugin entries.
192
213
 
193
- export default {
194
- output: 'export',
195
- webpack(config) {
196
- config.resolve.alias = {
197
- ...(config.resolve.alias ?? {}),
198
- '@knighted/jsx': path.join(distDir, 'index.js'),
199
- '@knighted/jsx/react': path.join(distDir, 'react/index.js'),
200
- }
201
-
202
- config.module.rules.push({
203
- test: /\.[jt]sx?$/,
204
- include: path.join(__dirname, 'pages'),
205
- enforce: 'post',
206
- use: [{ loader: path.join(distDir, 'loader/jsx.js') }],
207
- })
208
-
209
- return config
214
+ ```jsonc
215
+ // tsconfig.json
216
+ {
217
+ "compilerOptions": {
218
+ "plugins": [
219
+ {
220
+ "name": "@knighted/jsx-ts-plugin",
221
+ "tagModes": {
222
+ "jsx": "dom",
223
+ "reactJsx": "react",
224
+ },
225
+ },
226
+ ],
210
227
  },
211
228
  }
212
229
  ```
213
230
 
214
- Inside `pages/index.tsx` you can freely mix the helpers. The snippet below uses `jsx` on the server to prebuild a DOM fragment and then injects that HTML alongside a normal React component on the client:
231
+ - Choose **TypeScript: Select TypeScript Version Use Workspace Version** in VS Code so the plugin loads from `node_modules`.
232
+ - Run `tsc --noEmit` (or your build step) to surface the same diagnostics your editor shows.
233
+ - Set `jsxImportSource` to `@knighted/jsx` when compiling `.tsx` helpers. The package publishes the `@knighted/jsx/jsx-runtime` module TypeScript expects. The runtime export exists solely for diagnostics and will throw if you call it at execution time—switch back to tagged templates before shipping code.
234
+ - Drop `/* @jsx-dom */` or `/* @jsx-react */` immediately before a tagged template when you need a one-off override.
235
+ - Import the `JsxRenderable` helper type from `@knighted/jsx` whenever you annotate DOM-facing utilities without the plugin:
215
236
 
216
- ```ts
217
- import type { GetServerSideProps } from 'next'
218
- import { jsx } from '@knighted/jsx'
219
- import { reactJsx } from '@knighted/jsx/react'
237
+ ```ts
238
+ import type { JsxRenderable } from '@knighted/jsx'
220
239
 
221
- const buildDomShell = () =>
222
- jsx`
223
- <section data-kind="dom-runtime">
224
- <h2>DOM runtime</h2>
225
- <p>Rendered as static HTML on the server</p>
226
- </section>
227
- `
228
-
229
- export const getServerSideProps: GetServerSideProps = async () => {
230
- return {
231
- props: {
232
- domShell: buildDomShell().outerHTML,
233
- },
234
- }
235
- }
236
-
237
- const ReactBadge = () =>
238
- reactJsx`
239
- <button type="button">React badge</button>
240
- `
241
-
242
- type PageProps = { domShell: string }
243
-
244
- export default function Page({ domShell }: PageProps) {
245
- return reactJsx`
246
- <main>
247
- <${ReactBadge} />
248
- <div dangerouslySetInnerHTML={${{ __html: domShell }}}></div>
249
- </main>
250
- `
251
- }
252
- ```
240
+ const coerceToDom = (value: unknown): JsxRenderable => value ?? ''
241
+ const view = jsx`<section>${coerceToDom(data)}</section>`
242
+ ```
253
243
 
254
- Build the fixture locally with `npx next build test/fixtures/next-app` (or run `npx vitest run test/next-fixture.test.ts`) to verify the integration end to end. You can adapt the same pattern in `app/` routes, API handlers, or server actions whenever you need DOM output generated by the tagged template runtime.
255
-
256
- ### Interpolations
257
-
258
- - All dynamic values are provided through standard template literal expressions (`${...}`) and map to JSX exactly where they appear. Interpolations used as text children no longer need an extra `{...}` wrapper—the runtime automatically recognizes placeholders inside text segments (so `Count is ${value}` just works). Use the usual JSX braces when the syntax requires them (`className={${value}}`, `{...props}`, conditionals, etc.).
259
- - Every expression can be any JavaScript value: primitives, arrays/iterables, DOM nodes, functions, other `jsx` results, or custom component references.
260
- - Async values (Promises) are not supported. Resolve them before passing into the template.
261
-
262
- ### Components
263
-
264
- You can inline components by interpolating the function used for the tag name. The component receives a props object plus the optional `children` prop and can return anything that `jsx` can render (DOM nodes, strings, fragments, other arrays, ...).
265
-
266
- ```ts
267
- const Button = ({ children, variant = 'primary' }) => {
268
- const el = document.createElement('button')
269
- el.dataset.variant = variant
270
- el.append(children ?? '')
271
- return el
272
- }
273
-
274
- const label = 'Tap me'
275
-
276
- const view = jsx`
277
- <section>
278
- <${Button} variant="ghost">
279
- ${label}
280
- </${Button}>
281
- </section>
282
- `
283
-
284
- document.body.append(view)
285
- ```
286
-
287
- ### Fragments & SVG
288
-
289
- Use JSX fragments (`<>...</>`) for multi-root templates. SVG trees automatically switch to the `http://www.w3.org/2000/svg` namespace once they enter an `<svg>` tag, and fall back inside `<foreignObject>`.
290
-
291
- ### DOM-specific props
244
+ > [!TIP]
245
+ > Full `tsconfig` examples (single config or split React + DOM helper projects) live in [docs/typescript.md](docs/typescript.md).
292
246
 
293
- - `style` accepts either a string or an object. Object values handle CSS custom properties (`--token`) automatically.
294
- - `class` and `className` both work and can be strings or arrays.
295
- - Event handlers use the `on<Event>` naming convention (e.g. `onClick`).
296
- - `ref` supports callback refs as well as mutable `{ current }` objects.
297
- - `dangerouslySetInnerHTML` expects an object with an `__html` field, mirroring React.
247
+ Head over to [docs/ts-plugin.md](docs/ts-plugin.md) for deeper guidance, advanced options, and troubleshooting tips.
298
248
 
299
249
  ## Browser usage
300
250
 
@@ -303,26 +253,26 @@ When you are not using a bundler, load the module directly from a CDN that under
303
253
  ```html
304
254
  <script type="module">
305
255
  import { jsx } from 'https://esm.sh/@knighted/jsx'
256
+ import { reactJsx } from 'https://esm.sh/@knighted/jsx/react'
257
+ import { useState } from 'https://esm.sh/react@19'
258
+ import { createRoot } from 'https://esm.sh/react-dom@19/client'
306
259
 
307
- const message = jsx`<p>Hello from the browser</p>`
308
- document.body.append(message)
309
- </script>
310
- ```
311
-
312
- If you are building locally with Vite/Rollup/Webpack make sure the WASM binding is installable so the bundler can resolve `@oxc-parser/binding-wasm32-wasi` (details below).
260
+ const reactMount = jsx`<div data-kind="react-mount" />`
313
261
 
314
- ### Installing the WASM binding locally
315
-
316
- `@oxc-parser/binding-wasm32-wasi` publishes with `"cpu": ["wasm32"]`, so npm/yarn/pnpm skip it on macOS and Linux unless you override the platform guard. Run the helper script after cloning (or whenever you clean `node_modules`) to pull the binding into place for the Vite demo and any other local bundler builds:
262
+ const CounterButton = () => {
263
+ const [count, setCount] = useState(0)
264
+ return reactJsx`
265
+ <button type="button" onClick={${() => setCount(value => value + 1)}}>
266
+ Count is ${count}
267
+ </button>
268
+ `
269
+ }
317
270
 
318
- ```sh
319
- npm run setup:wasm
271
+ document.body.append(reactMount)
272
+ createRoot(reactMount).render(reactJsx`<${CounterButton} />`)
273
+ </script>
320
274
  ```
321
275
 
322
- The script downloads the published tarball via `npm pack`, extracts it into `node_modules/@oxc-parser/binding-wasm32-wasi`, and removes the temporary archive so your lockfile stays untouched. If you need to test a different binding build, set `WASM_BINDING_PACKAGE` before running the script (for example, `WASM_BINDING_PACKAGE=@oxc-parser/binding-wasm32-wasi@0.100.0 npm run setup:wasm`).
323
-
324
- Prefer the manual route? You can still run `npm_config_ignore_platform=true npm install --no-save @oxc-parser/binding-wasm32-wasi@^0.99.0`, but the script above replicates the vendored behavior with less ceremony.
325
-
326
276
  ### Lite bundle entry
327
277
 
328
278
  If you already run this package through your own bundler you can trim a few extra kilobytes by importing the minified entries:
@@ -336,44 +286,6 @@ import { reactJsx as nodeReactJsx } from '@knighted/jsx/node/react/lite'
336
286
 
337
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.
338
288
 
339
- ## Testing
340
-
341
- Looking for guidance on testing your own components with `jsx` or `reactJsx`? See
342
- [docs/testing.md](docs/testing.md) for DOM and React runtime examples. The commands
343
- below cover the library's internal test suites.
344
-
345
- Run the Vitest suite (powered by jsdom) to exercise the DOM runtime and component support:
346
-
347
- ```sh
348
- npm run test
349
- ```
350
-
351
- Tests live in `test/jsx.test.ts` and cover DOM props/events, custom components, fragments, and iterable children so you can see exactly how the template tag is meant to be used.
352
-
353
- Need full end-to-end coverage? The Playwright suite boots the CDN demo (`examples/esm-demo.html`) and the loader-backed Rspack fixture to verify nested trees, sibling structures, and interop with Lit/React:
354
-
355
- ```sh
356
- npm run test:e2e
357
- ```
358
-
359
- > [!NOTE]
360
- > The e2e script builds the library, installs the WASM parser binding, bundles the loader fixture, and then runs `playwright test`. Make sure Playwright browsers are installed locally (`npx playwright install --with-deps chromium`).
361
-
362
- ## Browser demo / Vite build
363
-
364
- This repo ships with a ready-to-run Vite demo under `examples/browser` that bundles the library (make sure you have installed the WASM binding via the command above first). Use it for a full end-to-end verification in a real browser (the demo imports `@knighted/jsx/lite` so you can confirm the lighter entry behaves identically):
365
-
366
- ```sh
367
- # Start a dev server at http://localhost:5173
368
- npm run dev
369
-
370
- # Produce a production Rollup build and preview it
371
- npm run build:demo
372
- npm run preview
373
- ```
374
-
375
- For a zero-build verification of the lite bundle, open `examples/esm-demo-lite.html` locally (double-click or run `open examples/esm-demo-lite.html`) or visit the deployed GitHub Pages build produced by `.github/workflows/deploy-demo.yml` (it serves that same lite HTML demo).
376
-
377
289
  ## Limitations
378
290
 
379
291
  - Requires a DOM-like environment (it throws when `document` is missing).
@@ -26,6 +26,21 @@ const tar_1 = require("tar");
26
26
  const DEFAULT_BINDING_SPEC = process.env.WASM_BINDING_PACKAGE ?? '@oxc-parser/binding-wasm32-wasi@^0.99.0';
27
27
  const RUNTIME_DEPS = ['@napi-rs/wasm-runtime', '@emnapi/runtime', '@emnapi/core'];
28
28
  const SUPPORTED_PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
29
+ // Node emits a noisy ExperimentalWarning whenever the WASI shim loads; silence just that message.
30
+ const WASI_WARNING_SNIPPET = 'WASI is an experimental feature';
31
+ const LOADER_CONFIG_EXAMPLE = [
32
+ '// Example loader entry to drop into your bundler rules array:',
33
+ '{',
34
+ " loader: '@knighted/jsx/loader',",
35
+ ' options: {',
36
+ " tags: ['jsx', 'reactJsx'],",
37
+ ' tagModes: {',
38
+ " reactJsx: 'react',",
39
+ ' },',
40
+ ' },',
41
+ '}',
42
+ ].join('\n');
43
+ suppressExperimentalWasiWarning();
29
44
  function parseArgs(argv) {
30
45
  const options = {
31
46
  cwd: process.cwd(),
@@ -114,11 +129,11 @@ function ensurePackageJson(cwd) {
114
129
  throw new Error('No package.json found. Run this inside a project with package.json.');
115
130
  }
116
131
  }
117
- function runNpmPack(spec, cwd, dryRun, verbose) {
132
+ function runNpmPack(spec, cwd, dryRun, verbose, execFn = node_child_process_1.execFileSync) {
118
133
  logVerbose(`> npm pack ${spec}`, verbose);
119
134
  if (dryRun)
120
135
  return `${spec.replace(/\W+/g, '_')}.tgz`;
121
- const output = (0, node_child_process_1.execFileSync)('npm', ['pack', spec], {
136
+ const output = execFn('npm', ['pack', spec], {
122
137
  cwd,
123
138
  encoding: 'utf8',
124
139
  stdio: ['ignore', 'pipe', 'inherit'],
@@ -133,9 +148,9 @@ function parsePackageName(spec) {
133
148
  const [, name, version] = match;
134
149
  return { name, version };
135
150
  }
136
- async function installBinding(spec, cwd, dryRun, verbose) {
151
+ async function installBinding(spec, cwd, dryRun, verbose, pack = runNpmPack) {
137
152
  const { name, version } = parsePackageName(spec);
138
- const tarballName = runNpmPack(spec, cwd, dryRun, verbose);
153
+ const tarballName = pack(spec, cwd, dryRun, verbose);
139
154
  const tarballPath = node_path_1.default.resolve(cwd, tarballName);
140
155
  const targetDir = node_path_1.default.resolve(cwd, 'node_modules', ...name.split('/'));
141
156
  log(`> Installing ${spec} into ${targetDir}`);
@@ -147,7 +162,7 @@ async function installBinding(spec, cwd, dryRun, verbose) {
147
162
  }
148
163
  return { targetDir, tarballPath: dryRun ? undefined : tarballPath, name, version };
149
164
  }
150
- function installRuntimeDeps(pm, deps, cwd, dryRun, verbose) {
165
+ function installRuntimeDeps(pm, deps, cwd, dryRun, verbose, spawner = node_child_process_1.spawnSync) {
151
166
  const missing = deps.filter(dep => !isDependencyInstalled(dep, cwd));
152
167
  if (missing.length === 0) {
153
168
  log('> Runtime dependencies already present; skipping install');
@@ -162,7 +177,7 @@ function installRuntimeDeps(pm, deps, cwd, dryRun, verbose) {
162
177
  const [command, args] = commands[pm];
163
178
  logVerbose(`> ${command} ${args.join(' ')}`, verbose);
164
179
  if (!dryRun) {
165
- const result = (0, node_child_process_1.spawnSync)(command, args, { cwd, stdio: 'inherit' });
180
+ const result = spawner(command, args, { cwd, stdio: 'inherit' });
166
181
  if (result.status !== 0) {
167
182
  throw new Error(`Failed to install runtime dependencies with ${pm}`);
168
183
  }
@@ -194,11 +209,11 @@ function persistBindingSpec(cwd, name, version, dryRun, verbose) {
194
209
  node_fs_1.default.writeFileSync(pkgPath, `${JSON.stringify(pkgJson, null, 2)}\n`, 'utf8');
195
210
  }
196
211
  }
197
- async function verifyBinding(name, cwd, verbose) {
212
+ async function verifyBinding(name, cwd, verbose, importer = specifier => import(specifier)) {
198
213
  const requireFromCwd = (0, node_module_1.createRequire)(node_path_1.default.join(cwd, 'package.json'));
199
214
  const resolved = requireFromCwd.resolve(name);
200
215
  logVerbose(`> Resolved ${name} to ${resolved}`, verbose);
201
- const imported = await import((0, node_url_1.pathToFileURL)(resolved).href);
216
+ const imported = await importer((0, node_url_1.pathToFileURL)(resolved).href);
202
217
  if (!imported) {
203
218
  throw new Error(`Imported ${name} is empty; verification failed`);
204
219
  }
@@ -229,27 +244,30 @@ async function maybeHandleConfigPrompt(skipConfig, force) {
229
244
  return;
230
245
  }
231
246
  log('> Loader assistance is interactive and not applied automatically yet. See docs at docs/cli.md for next steps.');
247
+ log('> Example loader config (webpack / rspack):');
248
+ console.log(LOADER_CONFIG_EXAMPLE);
232
249
  }
233
- async function main() {
234
- const options = parseArgs(process.argv.slice(2));
235
- ensurePackageJson(options.cwd);
236
- const packageManager = detectPackageManager(options.cwd, options.packageManager);
237
- log(`> Using package manager: ${packageManager}`);
238
- const installedRuntimeDeps = installRuntimeDeps(packageManager, RUNTIME_DEPS, options.cwd, options.dryRun, options.verbose);
239
- const binding = await installBinding(options.wasmPackage, options.cwd, options.dryRun, options.verbose);
240
- persistBindingSpec(options.cwd, binding.name, binding.version, options.dryRun, options.verbose);
250
+ 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;
252
+ const options = parseArgsImpl(process.argv.slice(2));
253
+ ensurePackageJsonImpl(options.cwd);
254
+ const packageManager = detectPackageManagerImpl(options.cwd, options.packageManager);
255
+ logImpl(`> Using package manager: ${packageManager}`);
256
+ 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);
258
+ persistBindingSpecImpl(options.cwd, binding.name, binding.version, options.dryRun, options.verbose);
241
259
  let resolvedPath;
242
260
  if (!options.dryRun) {
243
- resolvedPath = await verifyBinding(binding.name, options.cwd, options.verbose);
244
- log(`> Verified ${binding.name} at ${resolvedPath}`);
261
+ resolvedPath = await verifyBindingImpl(binding.name, options.cwd, options.verbose);
262
+ logImpl(`> Verified ${binding.name} at ${resolvedPath}`);
245
263
  }
246
- await maybeHandleConfigPrompt(options.skipConfig, options.force);
247
- log('\nDone!');
248
- log(`- Binding: ${binding.name}${binding.version ? `@${binding.version}` : ''}`);
249
- log(`- Target: ${binding.targetDir}`);
250
- log(`- Runtime deps installed: ${installedRuntimeDeps.join(', ') || 'none (already present)'}`);
264
+ await maybeHandleConfigPromptImpl(options.skipConfig, options.force);
265
+ logImpl('\nDone!');
266
+ logImpl(`- Binding: ${binding.name}${binding.version ? `@${binding.version}` : ''}`);
267
+ logImpl(`- Target: ${binding.targetDir}`);
268
+ logImpl(`- Runtime deps installed: ${installedRuntimeDeps.join(', ') || 'none (already present)'}`);
251
269
  if (resolvedPath)
252
- log(`- Verified import: ${resolvedPath}`);
270
+ logImpl(`- Verified import: ${resolvedPath}`);
253
271
  }
254
272
  if (process.env.KNIGHTED_JSX_CLI_TEST !== '1') {
255
273
  main().catch(error => {
@@ -257,3 +275,17 @@ if (process.env.KNIGHTED_JSX_CLI_TEST !== '1') {
257
275
  process.exitCode = 1;
258
276
  });
259
277
  }
278
+ function suppressExperimentalWasiWarning() {
279
+ const originalEmitWarning = process.emitWarning.bind(process);
280
+ process.emitWarning = ((warning, ...args) => {
281
+ const [typeMaybe] = args;
282
+ const message = typeof warning === 'string' ? warning : warning.message;
283
+ const name = typeof warning === 'string' ? undefined : warning.name;
284
+ const type = typeof typeMaybe === 'string' ? typeMaybe : undefined;
285
+ if (message.includes(WASI_WARNING_SNIPPET) &&
286
+ (name === 'ExperimentalWarning' || type === 'ExperimentalWarning')) {
287
+ return;
288
+ }
289
+ return originalEmitWarning(warning, ...args);
290
+ });
291
+ }
@@ -1,3 +1,4 @@
1
+ import { execFileSync, spawnSync } from 'node:child_process';
1
2
  declare const SUPPORTED_PACKAGE_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
2
3
  type PackageManager = (typeof SUPPORTED_PACKAGE_MANAGERS)[number];
3
4
  type CliOptions = {
@@ -10,9 +11,10 @@ type CliOptions = {
10
11
  wasmPackage: string;
11
12
  };
12
13
  declare function parseArgs(argv: string[]): CliOptions;
14
+ declare function log(message: string): void;
13
15
  declare function detectPackageManager(cwd: string, explicit?: PackageManager): PackageManager;
14
16
  declare function ensurePackageJson(cwd: string): void;
15
- declare function runNpmPack(spec: string, cwd: string, dryRun: boolean, verbose: boolean): string;
17
+ declare function runNpmPack(spec: string, cwd: string, dryRun: boolean, verbose: boolean, execFn?: typeof execFileSync): string;
16
18
  declare function parsePackageName(spec: string): {
17
19
  name: string;
18
20
  version: undefined;
@@ -20,17 +22,30 @@ declare function parsePackageName(spec: string): {
20
22
  name: string;
21
23
  version: string;
22
24
  };
23
- declare function installBinding(spec: string, cwd: string, dryRun: boolean, verbose: boolean): Promise<{
25
+ type PackFunction = (spec: string, cwd: string, dryRun: boolean, verbose: boolean) => string;
26
+ declare function installBinding(spec: string, cwd: string, dryRun: boolean, verbose: boolean, pack?: PackFunction): Promise<{
24
27
  targetDir: string;
25
28
  tarballPath?: string;
26
29
  name: string;
27
30
  version?: string;
28
31
  }>;
29
- declare function installRuntimeDeps(pm: PackageManager, deps: string[], cwd: string, dryRun: boolean, verbose: boolean): string[];
32
+ declare function installRuntimeDeps(pm: PackageManager, deps: string[], cwd: string, dryRun: boolean, verbose: boolean, spawner?: typeof spawnSync): string[];
30
33
  declare function isDependencyInstalled(dep: string, cwd: string): boolean;
31
34
  declare function persistBindingSpec(cwd: string, name: string, version: string | undefined, dryRun: boolean, verbose: boolean): void;
32
- declare function verifyBinding(name: string, cwd: string, verbose: boolean): Promise<string>;
35
+ type BindingImporter = (specifier: string) => Promise<unknown>;
36
+ declare function verifyBinding(name: string, cwd: string, verbose: boolean, importer?: BindingImporter): Promise<string>;
33
37
  declare function promptYesNo(prompt: string, defaultValue: boolean, force: boolean): Promise<boolean>;
34
38
  declare function maybeHandleConfigPrompt(skipConfig: boolean, force: boolean): Promise<void>;
35
- declare function main(): Promise<void>;
39
+ type MainDeps = {
40
+ parseArgs: typeof parseArgs;
41
+ ensurePackageJson: typeof ensurePackageJson;
42
+ detectPackageManager: typeof detectPackageManager;
43
+ installRuntimeDeps: typeof installRuntimeDeps;
44
+ installBinding: typeof installBinding;
45
+ persistBindingSpec: typeof persistBindingSpec;
46
+ verifyBinding: typeof verifyBinding;
47
+ maybeHandleConfigPrompt: typeof maybeHandleConfigPrompt;
48
+ log: typeof log;
49
+ };
50
+ declare function main(overrides?: Partial<MainDeps>): Promise<void>;
36
51
  export { parseArgs, detectPackageManager, ensurePackageJson, runNpmPack, parsePackageName, installBinding, installRuntimeDeps, isDependencyInstalled, persistBindingSpec, verifyBinding, promptYesNo, maybeHandleConfigPrompt, main, };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Fragment = void 0;
4
+ exports.jsx = jsx;
5
+ exports.jsxs = jsxs;
6
+ exports.jsxDEV = jsxDEV;
7
+ const runtimeModuleId = '@knighted/jsx/jsx-runtime';
8
+ const fragmentSymbolDescription = `${runtimeModuleId}::Fragment`;
9
+ const runtimeNotAvailable = () => {
10
+ throw new Error(`The automatic JSX runtime is only published for TypeScript diagnostics. ` +
11
+ `Render DOM nodes through the jsx tagged template exported by @knighted/jsx instead.`);
12
+ };
13
+ exports.Fragment = Symbol.for(fragmentSymbolDescription);
14
+ function jsx(_, __, ___) {
15
+ return runtimeNotAvailable();
16
+ }
17
+ function jsxs(_, __, ___) {
18
+ return runtimeNotAvailable();
19
+ }
20
+ function jsxDEV(_, __, ___, ____, _____, ______) {
21
+ return runtimeNotAvailable();
22
+ }
@@ -0,0 +1,32 @@
1
+ import type { JsxRenderable } from './jsx.cjs';
2
+ export declare const Fragment: unique symbol;
3
+ export declare function jsx(_: unknown, __?: unknown, ___?: unknown): JsxRenderable;
4
+ export declare function jsxs(_: unknown, __?: unknown, ___?: unknown): JsxRenderable;
5
+ export declare function jsxDEV(_: unknown, __?: unknown, ___?: unknown, ____?: boolean, _____?: unknown, ______?: unknown): JsxRenderable;
6
+ type DataAttributes = {
7
+ [K in `data-${string}`]?: string | number | boolean | null | undefined;
8
+ };
9
+ type AriaAttributes = {
10
+ [K in `aria-${string}`]?: string | number | boolean | null | undefined;
11
+ };
12
+ type EventHandlers<T extends EventTarget> = {
13
+ [K in keyof GlobalEventHandlersEventMap as `on${Capitalize<string & K>}`]?: (event: GlobalEventHandlersEventMap[K]) => void;
14
+ };
15
+ type ElementProps<Tag extends keyof HTMLElementTagNameMap> = Partial<HTMLElementTagNameMap[Tag]> & EventHandlers<HTMLElementTagNameMap[Tag]> & DataAttributes & AriaAttributes & {
16
+ class?: string;
17
+ className?: string;
18
+ style?: string | Record<string, string | number>;
19
+ ref?: ((value: HTMLElementTagNameMap[Tag]) => void) | {
20
+ current: HTMLElementTagNameMap[Tag] | null;
21
+ };
22
+ children?: JsxRenderable | JsxRenderable[];
23
+ };
24
+ declare global {
25
+ namespace JSX {
26
+ type Element = JsxRenderable;
27
+ type IntrinsicElements = {
28
+ [Tag in keyof HTMLElementTagNameMap]: ElementProps<Tag>;
29
+ };
30
+ }
31
+ }
32
+ export {};
@@ -1,3 +1,4 @@
1
+ import { execFileSync, spawnSync } from 'node:child_process';
1
2
  declare const SUPPORTED_PACKAGE_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
2
3
  type PackageManager = (typeof SUPPORTED_PACKAGE_MANAGERS)[number];
3
4
  type CliOptions = {
@@ -10,9 +11,10 @@ type CliOptions = {
10
11
  wasmPackage: string;
11
12
  };
12
13
  declare function parseArgs(argv: string[]): CliOptions;
14
+ declare function log(message: string): void;
13
15
  declare function detectPackageManager(cwd: string, explicit?: PackageManager): PackageManager;
14
16
  declare function ensurePackageJson(cwd: string): void;
15
- declare function runNpmPack(spec: string, cwd: string, dryRun: boolean, verbose: boolean): string;
17
+ declare function runNpmPack(spec: string, cwd: string, dryRun: boolean, verbose: boolean, execFn?: typeof execFileSync): string;
16
18
  declare function parsePackageName(spec: string): {
17
19
  name: string;
18
20
  version: undefined;
@@ -20,17 +22,30 @@ declare function parsePackageName(spec: string): {
20
22
  name: string;
21
23
  version: string;
22
24
  };
23
- declare function installBinding(spec: string, cwd: string, dryRun: boolean, verbose: boolean): Promise<{
25
+ type PackFunction = (spec: string, cwd: string, dryRun: boolean, verbose: boolean) => string;
26
+ declare function installBinding(spec: string, cwd: string, dryRun: boolean, verbose: boolean, pack?: PackFunction): Promise<{
24
27
  targetDir: string;
25
28
  tarballPath?: string;
26
29
  name: string;
27
30
  version?: string;
28
31
  }>;
29
- declare function installRuntimeDeps(pm: PackageManager, deps: string[], cwd: string, dryRun: boolean, verbose: boolean): string[];
32
+ declare function installRuntimeDeps(pm: PackageManager, deps: string[], cwd: string, dryRun: boolean, verbose: boolean, spawner?: typeof spawnSync): string[];
30
33
  declare function isDependencyInstalled(dep: string, cwd: string): boolean;
31
34
  declare function persistBindingSpec(cwd: string, name: string, version: string | undefined, dryRun: boolean, verbose: boolean): void;
32
- declare function verifyBinding(name: string, cwd: string, verbose: boolean): Promise<string>;
35
+ type BindingImporter = (specifier: string) => Promise<unknown>;
36
+ declare function verifyBinding(name: string, cwd: string, verbose: boolean, importer?: BindingImporter): Promise<string>;
33
37
  declare function promptYesNo(prompt: string, defaultValue: boolean, force: boolean): Promise<boolean>;
34
38
  declare function maybeHandleConfigPrompt(skipConfig: boolean, force: boolean): Promise<void>;
35
- declare function main(): Promise<void>;
39
+ type MainDeps = {
40
+ parseArgs: typeof parseArgs;
41
+ ensurePackageJson: typeof ensurePackageJson;
42
+ detectPackageManager: typeof detectPackageManager;
43
+ installRuntimeDeps: typeof installRuntimeDeps;
44
+ installBinding: typeof installBinding;
45
+ persistBindingSpec: typeof persistBindingSpec;
46
+ verifyBinding: typeof verifyBinding;
47
+ maybeHandleConfigPrompt: typeof maybeHandleConfigPrompt;
48
+ log: typeof log;
49
+ };
50
+ declare function main(overrides?: Partial<MainDeps>): Promise<void>;
36
51
  export { parseArgs, detectPackageManager, ensurePackageJson, runNpmPack, parsePackageName, installBinding, installRuntimeDeps, isDependencyInstalled, persistBindingSpec, verifyBinding, promptYesNo, maybeHandleConfigPrompt, main, };
package/dist/cli/init.js CHANGED
@@ -10,6 +10,20 @@ import { extract } from 'tar';
10
10
  var DEFAULT_BINDING_SPEC = process.env.WASM_BINDING_PACKAGE ?? "@oxc-parser/binding-wasm32-wasi@^0.99.0";
11
11
  var RUNTIME_DEPS = ["@napi-rs/wasm-runtime", "@emnapi/runtime", "@emnapi/core"];
12
12
  var SUPPORTED_PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
13
+ var WASI_WARNING_SNIPPET = "WASI is an experimental feature";
14
+ var LOADER_CONFIG_EXAMPLE = [
15
+ "// Example loader entry to drop into your bundler rules array:",
16
+ "{",
17
+ " loader: '@knighted/jsx/loader',",
18
+ " options: {",
19
+ " tags: ['jsx', 'reactJsx'],",
20
+ " tagModes: {",
21
+ " reactJsx: 'react',",
22
+ " },",
23
+ " },",
24
+ "}"
25
+ ].join("\n");
26
+ suppressExperimentalWasiWarning();
13
27
  function parseArgs(argv) {
14
28
  const options = {
15
29
  cwd: process.cwd(),
@@ -97,10 +111,10 @@ function ensurePackageJson(cwd) {
97
111
  throw new Error("No package.json found. Run this inside a project with package.json.");
98
112
  }
99
113
  }
100
- function runNpmPack(spec, cwd, dryRun, verbose) {
114
+ function runNpmPack(spec, cwd, dryRun, verbose, execFn = execFileSync) {
101
115
  logVerbose(`> npm pack ${spec}`, verbose);
102
116
  if (dryRun) return `${spec.replace(/\W+/g, "_")}.tgz`;
103
- const output = execFileSync("npm", ["pack", spec], {
117
+ const output = execFn("npm", ["pack", spec], {
104
118
  cwd,
105
119
  encoding: "utf8",
106
120
  stdio: ["ignore", "pipe", "inherit"]
@@ -114,9 +128,9 @@ function parsePackageName(spec) {
114
128
  const [, name, version] = match;
115
129
  return { name, version };
116
130
  }
117
- async function installBinding(spec, cwd, dryRun, verbose) {
131
+ async function installBinding(spec, cwd, dryRun, verbose, pack = runNpmPack) {
118
132
  const { name, version } = parsePackageName(spec);
119
- const tarballName = runNpmPack(spec, cwd, dryRun, verbose);
133
+ const tarballName = pack(spec, cwd, dryRun, verbose);
120
134
  const tarballPath = path.resolve(cwd, tarballName);
121
135
  const targetDir = path.resolve(cwd, "node_modules", ...name.split("/"));
122
136
  log(`> Installing ${spec} into ${targetDir}`);
@@ -128,7 +142,7 @@ async function installBinding(spec, cwd, dryRun, verbose) {
128
142
  }
129
143
  return { targetDir, tarballPath: dryRun ? void 0 : tarballPath, name, version };
130
144
  }
131
- function installRuntimeDeps(pm, deps, cwd, dryRun, verbose) {
145
+ function installRuntimeDeps(pm, deps, cwd, dryRun, verbose, spawner = spawnSync) {
132
146
  const missing = deps.filter((dep) => !isDependencyInstalled(dep, cwd));
133
147
  if (missing.length === 0) {
134
148
  log("> Runtime dependencies already present; skipping install");
@@ -143,7 +157,7 @@ function installRuntimeDeps(pm, deps, cwd, dryRun, verbose) {
143
157
  const [command, args] = commands[pm];
144
158
  logVerbose(`> ${command} ${args.join(" ")}`, verbose);
145
159
  if (!dryRun) {
146
- const result = spawnSync(command, args, { cwd, stdio: "inherit" });
160
+ const result = spawner(command, args, { cwd, stdio: "inherit" });
147
161
  if (result.status !== 0) {
148
162
  throw new Error(`Failed to install runtime dependencies with ${pm}`);
149
163
  }
@@ -175,11 +189,11 @@ function persistBindingSpec(cwd, name, version, dryRun, verbose) {
175
189
  `, "utf8");
176
190
  }
177
191
  }
178
- async function verifyBinding(name, cwd, verbose) {
192
+ async function verifyBinding(name, cwd, verbose, importer = (specifier) => import(specifier)) {
179
193
  const requireFromCwd = createRequire(path.join(cwd, "package.json"));
180
194
  const resolved = requireFromCwd.resolve(name);
181
195
  logVerbose(`> Resolved ${name} to ${resolved}`, verbose);
182
- const imported = await import(pathToFileURL(resolved).href);
196
+ const imported = await importer(pathToFileURL(resolved).href);
183
197
  if (!imported) {
184
198
  throw new Error(`Imported ${name} is empty; verification failed`);
185
199
  }
@@ -213,26 +227,39 @@ async function maybeHandleConfigPrompt(skipConfig, force) {
213
227
  log(
214
228
  "> Loader assistance is interactive and not applied automatically yet. See docs at docs/cli.md for next steps."
215
229
  );
230
+ log("> Example loader config (webpack / rspack):");
231
+ console.log(LOADER_CONFIG_EXAMPLE);
216
232
  }
217
- async function main() {
218
- const options = parseArgs(process.argv.slice(2));
219
- ensurePackageJson(options.cwd);
220
- const packageManager = detectPackageManager(options.cwd, options.packageManager);
221
- log(`> Using package manager: ${packageManager}`);
222
- const installedRuntimeDeps = installRuntimeDeps(
233
+ async function main(overrides = {}) {
234
+ const {
235
+ parseArgs: parseArgsImpl = parseArgs,
236
+ ensurePackageJson: ensurePackageJsonImpl = ensurePackageJson,
237
+ detectPackageManager: detectPackageManagerImpl = detectPackageManager,
238
+ installRuntimeDeps: installRuntimeDepsImpl = installRuntimeDeps,
239
+ installBinding: installBindingImpl = installBinding,
240
+ persistBindingSpec: persistBindingSpecImpl = persistBindingSpec,
241
+ verifyBinding: verifyBindingImpl = verifyBinding,
242
+ maybeHandleConfigPrompt: maybeHandleConfigPromptImpl = maybeHandleConfigPrompt,
243
+ log: logImpl = log
244
+ } = overrides;
245
+ const options = parseArgsImpl(process.argv.slice(2));
246
+ ensurePackageJsonImpl(options.cwd);
247
+ const packageManager = detectPackageManagerImpl(options.cwd, options.packageManager);
248
+ logImpl(`> Using package manager: ${packageManager}`);
249
+ const installedRuntimeDeps = installRuntimeDepsImpl(
223
250
  packageManager,
224
251
  RUNTIME_DEPS,
225
252
  options.cwd,
226
253
  options.dryRun,
227
254
  options.verbose
228
255
  );
229
- const binding = await installBinding(
256
+ const binding = await installBindingImpl(
230
257
  options.wasmPackage,
231
258
  options.cwd,
232
259
  options.dryRun,
233
260
  options.verbose
234
261
  );
235
- persistBindingSpec(
262
+ persistBindingSpecImpl(
236
263
  options.cwd,
237
264
  binding.name,
238
265
  binding.version,
@@ -241,17 +268,17 @@ async function main() {
241
268
  );
242
269
  let resolvedPath;
243
270
  if (!options.dryRun) {
244
- resolvedPath = await verifyBinding(binding.name, options.cwd, options.verbose);
245
- log(`> Verified ${binding.name} at ${resolvedPath}`);
271
+ resolvedPath = await verifyBindingImpl(binding.name, options.cwd, options.verbose);
272
+ logImpl(`> Verified ${binding.name} at ${resolvedPath}`);
246
273
  }
247
- await maybeHandleConfigPrompt(options.skipConfig, options.force);
248
- log("\nDone!");
249
- log(`- Binding: ${binding.name}${binding.version ? `@${binding.version}` : ""}`);
250
- log(`- Target: ${binding.targetDir}`);
251
- log(
274
+ await maybeHandleConfigPromptImpl(options.skipConfig, options.force);
275
+ logImpl("\nDone!");
276
+ logImpl(`- Binding: ${binding.name}${binding.version ? `@${binding.version}` : ""}`);
277
+ logImpl(`- Target: ${binding.targetDir}`);
278
+ logImpl(
252
279
  `- Runtime deps installed: ${installedRuntimeDeps.join(", ") || "none (already present)"}`
253
280
  );
254
- if (resolvedPath) log(`- Verified import: ${resolvedPath}`);
281
+ if (resolvedPath) logImpl(`- Verified import: ${resolvedPath}`);
255
282
  }
256
283
  if (process.env.KNIGHTED_JSX_CLI_TEST !== "1") {
257
284
  main().catch((error) => {
@@ -262,5 +289,18 @@ if (process.env.KNIGHTED_JSX_CLI_TEST !== "1") {
262
289
  process.exitCode = 1;
263
290
  });
264
291
  }
292
+ function suppressExperimentalWasiWarning() {
293
+ const originalEmitWarning = process.emitWarning.bind(process);
294
+ process.emitWarning = ((warning, ...args) => {
295
+ const [typeMaybe] = args;
296
+ const message = typeof warning === "string" ? warning : warning.message;
297
+ const name = typeof warning === "string" ? void 0 : warning.name;
298
+ const type = typeof typeMaybe === "string" ? typeMaybe : void 0;
299
+ if (message.includes(WASI_WARNING_SNIPPET) && (name === "ExperimentalWarning" || type === "ExperimentalWarning")) {
300
+ return;
301
+ }
302
+ return originalEmitWarning(warning, ...args);
303
+ });
304
+ }
265
305
 
266
306
  export { detectPackageManager, ensurePackageJson, installBinding, installRuntimeDeps, isDependencyInstalled, main, maybeHandleConfigPrompt, parseArgs, parsePackageName, persistBindingSpec, promptYesNo, runNpmPack, verifyBinding };
@@ -0,0 +1,32 @@
1
+ import type { JsxRenderable } from './jsx.js';
2
+ export declare const Fragment: unique symbol;
3
+ export declare function jsx(_: unknown, __?: unknown, ___?: unknown): JsxRenderable;
4
+ export declare function jsxs(_: unknown, __?: unknown, ___?: unknown): JsxRenderable;
5
+ export declare function jsxDEV(_: unknown, __?: unknown, ___?: unknown, ____?: boolean, _____?: unknown, ______?: unknown): JsxRenderable;
6
+ type DataAttributes = {
7
+ [K in `data-${string}`]?: string | number | boolean | null | undefined;
8
+ };
9
+ type AriaAttributes = {
10
+ [K in `aria-${string}`]?: string | number | boolean | null | undefined;
11
+ };
12
+ type EventHandlers<T extends EventTarget> = {
13
+ [K in keyof GlobalEventHandlersEventMap as `on${Capitalize<string & K>}`]?: (event: GlobalEventHandlersEventMap[K]) => void;
14
+ };
15
+ type ElementProps<Tag extends keyof HTMLElementTagNameMap> = Partial<HTMLElementTagNameMap[Tag]> & EventHandlers<HTMLElementTagNameMap[Tag]> & DataAttributes & AriaAttributes & {
16
+ class?: string;
17
+ className?: string;
18
+ style?: string | Record<string, string | number>;
19
+ ref?: ((value: HTMLElementTagNameMap[Tag]) => void) | {
20
+ current: HTMLElementTagNameMap[Tag] | null;
21
+ };
22
+ children?: JsxRenderable | JsxRenderable[];
23
+ };
24
+ declare global {
25
+ namespace JSX {
26
+ type Element = JsxRenderable;
27
+ type IntrinsicElements = {
28
+ [Tag in keyof HTMLElementTagNameMap]: ElementProps<Tag>;
29
+ };
30
+ }
31
+ }
32
+ export {};
@@ -0,0 +1,16 @@
1
+ const runtimeModuleId = '@knighted/jsx/jsx-runtime';
2
+ const fragmentSymbolDescription = `${runtimeModuleId}::Fragment`;
3
+ const runtimeNotAvailable = () => {
4
+ throw new Error(`The automatic JSX runtime is only published for TypeScript diagnostics. ` +
5
+ `Render DOM nodes through the jsx tagged template exported by @knighted/jsx instead.`);
6
+ };
7
+ export const Fragment = Symbol.for(fragmentSymbolDescription);
8
+ export function jsx(_, __, ___) {
9
+ return runtimeNotAvailable();
10
+ }
11
+ export function jsxs(_, __, ___) {
12
+ return runtimeNotAvailable();
13
+ }
14
+ export function jsxDEV(_, __, ___, ____, _____, ______) {
15
+ return runtimeNotAvailable();
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/jsx",
3
- "version": "1.3.1",
3
+ "version": "1.4.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",
@@ -26,6 +26,16 @@
26
26
  "import": "./dist/index.js",
27
27
  "default": "./dist/index.js"
28
28
  },
29
+ "./jsx-runtime": {
30
+ "types": "./dist/jsx-runtime.d.ts",
31
+ "import": "./dist/jsx-runtime.js",
32
+ "default": "./dist/jsx-runtime.js"
33
+ },
34
+ "./jsx-dev-runtime": {
35
+ "types": "./dist/jsx-runtime.d.ts",
36
+ "import": "./dist/jsx-runtime.js",
37
+ "default": "./dist/jsx-runtime.js"
38
+ },
29
39
  "./lite": {
30
40
  "types": "./dist/index.d.ts",
31
41
  "import": "./dist/lite/index.js",