Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ on:
push:
branches:
- main
pull_request:
branches:
- main
paths:
- 'src/**'
- 'package-lock.json'
workflow_dispatch:

jobs:
Expand Down
3 changes: 2 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env sh
set -euo pipefail
set -eu
if (set -o pipefail) 2>/dev/null; then :; fi

npx lint-staged
3 changes: 2 additions & 1 deletion .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env sh
set -euo pipefail
set -eu
if (set -o pipefail) 2>/dev/null; then :; fi

npm run prettier:check
npm run lint
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ A runtime JSX template tag backed by the [`oxc-parser`](https://github.com/oxc-p
- [Next.js integration](#nextjs-integration)
- [Browser usage](#browser-usage)
- [Testing & demos](#testing)
- [CLI setup](docs/cli.md)

## Installation

Expand All @@ -34,12 +35,14 @@ npm install @knighted/jsx
> [!NOTE]
> 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.

The parser automatically uses native bindings when it runs in Node.js. To enable the WASM binding for browser builds you also need the `@oxc-parser/binding-wasm32-wasi` package. Because npm enforces the `cpu: ["wasm32"]` flag you must opt into the install explicitly:
The parser automatically uses native bindings when it runs in Node.js. To enable the WASM binding for browser builds you also need the `@oxc-parser/binding-wasm32-wasi` package. The quickest path is:

```sh
npm_config_ignore_platform=true npm install @oxc-parser/binding-wasm32-wasi
npx @knighted/jsx init
```

See [docs/cli.md](docs/cli.md) for flags, dry runs, and package-manager overrides. If you prefer manual install, run `npm_config_ignore_platform=true npm install @oxc-parser/binding-wasm32-wasi`.

> [!TIP]
> Public CDNs such as `esm.sh` or `jsdelivr` already publish bundles that include the WASM binding, so you can import this package directly from those endpoints in `<script type="module">` blocks without any extra setup.

Expand Down
53 changes: 53 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# CLI: init

The `init` command installs the WASM parser binding and helper runtimes for `@knighted/jsx`. It defaults to a safe path that only installs and verifies the binding—it will not change your bundler config unless you explicitly opt in.

## Quick start

```sh
npx @knighted/jsx init
```

What it does by default:

- Installs `@oxc-parser/binding-wasm32-wasi` plus runtime helpers (`@napi-rs/wasm-runtime`, `@emnapi/runtime`, `@emnapi/core`).
- Records the binding in `optionalDependencies` so the version is visible in your project.
- Verifies the binding can be imported and reports the resolved path.
- Skips loader config changes (prompted only when you opt in).

## Options

- `--package-manager`, `--pm <npm|pnpm|yarn|bun>`: override detection.
- `--wasm-package <spec>`: install a different binding spec (or set `WASM_BINDING_PACKAGE`).
- `--config`: prompt for loader help (no automatic edits yet; shows guidance only).
- `--skip-config`: skip loader help (default).
- `--dry-run`: print what would happen without executing.
- `--force`, `--yes`: assume "yes" for prompts.
- `--verbose`: show additional detail (commands, resolve paths).

Examples:

```sh
# Default install + verification
npx @knighted/jsx init

# Dry run with verbose logging
npx @knighted/jsx init --dry-run --verbose

# Force npm even if a different lockfile is present
npx @knighted/jsx init --pm npm

# Prompt for loader guidance after install
npx @knighted/jsx init --config

# Use a custom binding build
WASM_BINDING_PACKAGE=@oxc-parser/binding-wasm32-wasi@^0.100.0 npx @knighted/jsx init
```

## Notes

- The command uses `npm pack` internally to pull the WASM binding even when it is marked for `cpu: ["wasm32"]`.
- Loader configuration is opt-in and requires a prompt. No config files are modified unless you request help.
- If verification fails, rerun with `--verbose` to see the resolved binding path and error details.
- `@tybys/wasm-util` is not required for the standard flow. Consider it only as an advanced workaround if your environment blocks the normal WASM install/loading path.
- Bundlers may need a WASM-friendly setting to emit the parser asset: Vite handles `.wasm` out of the box; Webpack/Rspack typically need `experiments.asyncWebAssembly = true` or an asset rule so `parser.wasm32-wasi.wasm` is copied into the build output. Ensure your bundler is configured to serve that file.
6 changes: 6 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export default [
'n/no-unpublished-require': 'off',
},
},
{
files: ['src/cli/**/*', 'scripts/**/*'],
rules: {
'n/no-process-exit': 'off',
},
},
{
files: ['test/fixtures/**/*'],
rules: {
Expand Down
19 changes: 8 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 11 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@knighted/jsx",
"version": "1.2.1",
"version": "1.3.0",
"description": "Runtime JSX tagged template that renders DOM or React trees anywhere without a build step.",
"keywords": [
"jsx runtime",
Expand All @@ -17,6 +17,9 @@
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
"jsx": "./dist/cli/init.js"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
Expand Down Expand Up @@ -65,11 +68,11 @@
"./package.json": "./package.json"
},
"engines": {
"node": ">=22.3.0"
"node": ">=22.17.0"
},
"engineStrict": true,
"scripts": {
"build": "duel && npm run build:lite",
"build": "duel && npm run build:lite && npm run build:cli",
"prepare": "husky",
"precheck-types": "npm run build",
"check-types": "npm run check-types:lib && npm run check-types:demo",
Expand All @@ -78,15 +81,16 @@
"lint": "eslint src test",
"prettier": "prettier -w .",
"prettier:check": "prettier --check .",
"test": "vitest run --coverage",
"test:watch": "vitest",
"test": "KNIGHTED_JSX_CLI_TEST=1 vitest run --coverage",
"test:watch": "KNIGHTED_JSX_CLI_TEST=1 vitest",
"test:e2e": "npm run build && npm run setup:wasm && npm run build:fixture && playwright test",
"build:fixture": "node scripts/build-rspack-fixture.mjs",
"demo:node-ssr": "node test/fixtures/node-ssr/render.mjs",
"dev": "vite dev --config vite.config.ts",
"build:demo": "vite build --config vite.config.ts",
"preview": "vite preview --config vite.config.ts",
"build:lite": "tsup --config tsup.config.ts",
"build:cli": "tsup --config tsup.cli.config.ts",
"setup:wasm": "node scripts/setup-wasm.mjs",
"prepack": "npm run build"
},
Expand All @@ -113,7 +117,6 @@
"prettier": "^3.7.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tar": "^7.4.3",
"tsup": "^8.5.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.0",
Expand All @@ -122,7 +125,8 @@
},
"dependencies": {
"magic-string": "^0.30.21",
"oxc-parser": "^0.99.0"
"oxc-parser": "^0.99.0",
"tar": "^7.4.3"
},
"peerDependencies": {
"jsdom": "*",
Expand Down
12 changes: 11 additions & 1 deletion scripts/setup-wasm.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { execFileSync } from 'node:child_process'
import { execFileSync, spawnSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import { extract } from 'tar'

const PACKAGE_SPEC =
process.env.WASM_BINDING_PACKAGE ?? '@oxc-parser/binding-wasm32-wasi@^0.99.0'
const cwd = process.cwd()
const cliEntry = path.resolve(cwd, 'dist', 'cli', 'init.js')

if (fs.existsSync(cliEntry)) {
const result = spawnSync(process.execPath, [cliEntry, '--skip-config', '--force'], {
cwd,
stdio: 'inherit',
})

process.exit(result.status ?? 0)
}

function runNpmPack() {
const output = execFileSync('npm', ['pack', PACKAGE_SPEC], {
Expand Down
Loading