diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 8ad3d90..43965f7 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -21,6 +21,8 @@ jobs:
node-version: '24.11.1'
- name: Install Dependencies
run: npm ci
+ - name: Setup JSX WASM binding
+ run: npm run setup:jsx-wasm
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium webkit
- name: Run Playwright
diff --git a/README.md b/README.md
index b28dbb7..ed3cb6e 100644
--- a/README.md
+++ b/README.md
@@ -132,7 +132,7 @@ module.exports = {
```ts
// lit wrapper
-import { reactJsx } from '@knighted/jsx'
+import { reactJsx } from '@knighted/jsx/react'
import { createRoot, type Root } from 'react-dom/client'
import { LitElement, html, unsafeCSS } from 'lit'
import { Button } from './button.tsx'
diff --git a/package-lock.json b/package-lock.json
index f3bb3c7..efe64bb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,10 @@
"packages/playwright"
],
"devDependencies": {
+ "@emnapi/core": "^1.2.0",
+ "@emnapi/runtime": "^1.2.0",
"@knighted/duel": "^2.1.6",
+ "@napi-rs/wasm-runtime": "^1.0.7",
"@playwright/test": "^1.48.2",
"@rspack/cli": "^1.0.0",
"@rspack/core": "^1.0.0",
@@ -356,9 +359,8 @@
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
"integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.1.0",
"tslib": "^2.4.0"
@@ -368,9 +370,8 @@
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
"integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
@@ -379,9 +380,8 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
@@ -1160,6 +1160,305 @@
}
}
},
+ "node_modules/@knighted/jsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@knighted/jsx/-/jsx-1.2.1.tgz",
+ "integrity": "sha512-zqkzK7wC1IZRdcEaAJGPqiy+YzqtJ5qOBCWiXMiNdipLAJaJgTXMxYTJtZLQCs4ux+HGSm5/OYYdq9tD7JZG0g==",
+ "license": "MIT",
+ "dependencies": {
+ "magic-string": "^0.30.21",
+ "oxc-parser": "^0.99.0"
+ },
+ "engines": {
+ "node": ">=22.3.0"
+ },
+ "optionalDependencies": {
+ "@oxc-parser/binding-darwin-arm64": "^0.99.0",
+ "@oxc-parser/binding-linux-x64-gnu": "^0.99.0",
+ "@oxc-parser/binding-wasm32-wasi": "^0.99.0"
+ },
+ "peerDependencies": {
+ "jsdom": "*",
+ "linkedom": "*",
+ "react": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "jsdom": {
+ "optional": true
+ },
+ "linkedom": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-android-arm64": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.99.0.tgz",
+ "integrity": "sha512-V4jhmKXgQQdRnm73F+r3ZY4pUEsijQeSraFeaCGng7abSNJGs76X6l82wHnmjLGFAeY00LWtjcELs7ZmbJ9+lA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-darwin-arm64": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.99.0.tgz",
+ "integrity": "sha512-Rp41nf9zD5FyLZciS9l1GfK8PhYqrD5kEGxyTOA2esTLeAy37rZxetG2E3xteEolAkeb2WDkVrlxPtibeAncMg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-darwin-x64": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.99.0.tgz",
+ "integrity": "sha512-WVonp40fPPxo5Gs0POTI57iEFv485TvNKOHMwZRhigwZRhZY2accEAkYIhei9eswF4HN5B44Wybkz7Gd1Qr/5Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-freebsd-x64": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.99.0.tgz",
+ "integrity": "sha512-H30bjOOttPmG54gAqu6+HzbLEzuNOYO2jZYrIq4At+NtLJwvNhXz28Hf5iEAFZIH/4hMpLkM4VN7uc+5UlNW3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-arm-gnueabihf": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.99.0.tgz",
+ "integrity": "sha512-0Z/Th0SYqzSRDPs6tk5lQdW0i73UCupnim3dgq2oW0//UdLonV/5wIZCArfKGC7w9y4h8TxgXpgtIyD1kKzzlQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-arm-musleabihf": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.99.0.tgz",
+ "integrity": "sha512-xo0wqNd5bpbzQVNpAIFbHk1xa+SaS/FGBABCd942SRTnrpxl6GeDj/s1BFaGcTl8MlwlKVMwOcyKrw/2Kdfquw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-arm64-gnu": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.99.0.tgz",
+ "integrity": "sha512-u26I6LKoLTPTd4Fcpr0aoAtjnGf5/ulMllo+QUiBhupgbVCAlaj4RyXH/mvcjcsl2bVBv9E/gYJZz2JjxQWXBA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-arm64-musl": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.99.0.tgz",
+ "integrity": "sha512-qhftDo2D37SqCEl3ZTa367NqWSZNb1Ddp34CTmShLKFrnKdNiUn55RdokLnHtf1AL5ssaQlYDwBECX7XiBWOhw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-riscv64-gnu": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.99.0.tgz",
+ "integrity": "sha512-zxn/xkf519f12FKkpL5XwJipsylfSSnm36h6c1zBDTz4fbIDMGyIhHfWfwM7uUmHo9Aqw1pLxFpY39Etv398+Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-s390x-gnu": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.99.0.tgz",
+ "integrity": "sha512-Y1eSDKDS5E4IVC7Oxw+NbYAKRmJPMJTIjW+9xOWwteDHkFqpocKe0USxog+Q1uhzalD9M0p9eXWEWdGQCMDBMQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-x64-gnu": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.99.0.tgz",
+ "integrity": "sha512-YVJMfk5cFWB8i2/nIrbk6n15bFkMHqWnMIWkVx7r2KwpTxHyFMfu2IpeVKo1ITDSmt5nBrGdLHD36QRlu2nDLg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-x64-musl": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.99.0.tgz",
+ "integrity": "sha512-2+SDPrie5f90A1b9EirtVggOgsqtsYU5raZwkDYKyS1uvJzjqHCDhG/f4TwQxHmIc5YkczdQfwvN91lwmjsKYQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-win32-arm64-msvc": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.99.0.tgz",
+ "integrity": "sha512-EaB3AvsxqdNUhh9FOoAxRZ2L4PCRwDlDb//QXItwyOJrX7XS+uGK9B1KEUV4FZ/7rDhHsWieLt5e07wl2Ti5AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-win32-x64-msvc": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.99.0.tgz",
+ "integrity": "sha512-sJN1Q8h7ggFOyDn0zsHaXbP/MklAVUvhrbq0LA46Qum686P3SZQHjbATqJn9yaVEvaSKXCshgl0vQ1gWkGgpcQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/@oxc-project/types": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.99.0.tgz",
+ "integrity": "sha512-LLDEhXB7g1m5J+woRSgfKsFPS3LhR9xRhTeIoEBm5WrkwMxn6eZ0Ld0c0K5eHB57ChZX6I3uSmmLjZ8pcjlRcw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@knighted/jsx/node_modules/oxc-parser": {
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.99.0.tgz",
+ "integrity": "sha512-MpS1lbd2vR0NZn1v0drpgu7RUFu3x9Rd0kxExObZc2+F+DIrV0BOMval/RO3BYGwssIOerII6iS8EbbpCCZQpQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "^0.99.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ },
+ "optionalDependencies": {
+ "@oxc-parser/binding-android-arm64": "0.99.0",
+ "@oxc-parser/binding-darwin-arm64": "0.99.0",
+ "@oxc-parser/binding-darwin-x64": "0.99.0",
+ "@oxc-parser/binding-freebsd-x64": "0.99.0",
+ "@oxc-parser/binding-linux-arm-gnueabihf": "0.99.0",
+ "@oxc-parser/binding-linux-arm-musleabihf": "0.99.0",
+ "@oxc-parser/binding-linux-arm64-gnu": "0.99.0",
+ "@oxc-parser/binding-linux-arm64-musl": "0.99.0",
+ "@oxc-parser/binding-linux-riscv64-gnu": "0.99.0",
+ "@oxc-parser/binding-linux-s390x-gnu": "0.99.0",
+ "@oxc-parser/binding-linux-x64-gnu": "0.99.0",
+ "@oxc-parser/binding-linux-x64-musl": "0.99.0",
+ "@oxc-parser/binding-wasm32-wasi": "0.99.0",
+ "@oxc-parser/binding-win32-arm64-msvc": "0.99.0",
+ "@oxc-parser/binding-win32-x64-msvc": "0.99.0"
+ }
+ },
"node_modules/@knighted/module": {
"version": "1.0.0-alpha.10",
"resolved": "https://registry.npmjs.org/@knighted/module/-/module-1.0.0-alpha.10.tgz",
@@ -1398,23 +1697,6 @@
"node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@knighted/specifier/node_modules/@oxc-parser/binding-wasm32-wasi": {
- "version": "0.99.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.99.0.tgz",
- "integrity": "sha512-DKA4j0QerUWSMADziLM5sAyM7V53Fj95CV9SjP77bPfEfT7MnvFKnneaRMqPK1cpzjAGiQF52OBUIKyk0dwOQA==",
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@napi-rs/wasm-runtime": "^1.0.7"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@knighted/specifier/node_modules/@oxc-parser/binding-win32-arm64-msvc": {
"version": "0.99.0",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.99.0.tgz",
@@ -1515,6 +1797,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@lit-labs/ssr-dom-shim": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz",
+ "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@lit/reactive-element": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz",
+ "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.4.0"
+ }
+ },
"node_modules/@module-federation/error-codes": {
"version": "0.21.6",
"resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.21.6.tgz",
@@ -1578,9 +1875,8 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
"integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"@emnapi/core": "^1.5.0",
"@emnapi/runtime": "^1.5.0",
@@ -1792,17 +2088,16 @@
}
},
"node_modules/@oxc-parser/binding-wasm32-wasi": {
- "version": "0.78.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.78.0.tgz",
- "integrity": "sha512-Pq0uT2CuN3J7Tv3KLuO7Sh4C7zTuqdJl0IDg3zB5keKx0BSbaEWewJL2CUNYUlG8txf+sMpUV+bkAIS5MEcKAw==",
+ "version": "0.99.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.99.0.tgz",
+ "integrity": "sha512-DKA4j0QerUWSMADziLM5sAyM7V53Fj95CV9SjP77bPfEfT7MnvFKnneaRMqPK1cpzjAGiQF52OBUIKyk0dwOQA==",
"cpu": [
"wasm32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@napi-rs/wasm-runtime": "^1.0.1"
+ "@napi-rs/wasm-runtime": "^1.0.7"
},
"engines": {
"node": ">=14.0.0"
@@ -2541,9 +2836,8 @@
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
@@ -2735,6 +3029,27 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/react": {
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
+ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
"node_modules/@types/retry": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
@@ -2795,6 +3110,12 @@
"@types/node": "*"
}
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT"
+ },
"node_modules/@types/webpack": {
"version": "5.28.5",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.5.tgz",
@@ -6639,6 +6960,37 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lit": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz",
+ "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@lit/reactive-element": "^2.1.0",
+ "lit-element": "^4.2.0",
+ "lit-html": "^3.3.0"
+ }
+ },
+ "node_modules/lit-element": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz",
+ "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.4.0",
+ "@lit/reactive-element": "^2.1.0",
+ "lit-html": "^3.3.0"
+ }
+ },
+ "node_modules/lit-html": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.1.tgz",
+ "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@types/trusted-types": "^2.0.2"
+ }
+ },
"node_modules/loader-runner": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
@@ -7324,7 +7676,6 @@
"integrity": "sha512-Kw6DlVJCG1HwArP3uF9kXc6nnAahpGaW7kZ7x1O7OugxbjSzkQqdKdA9loXCv7OeksFF/DfnLDupwqUjr1EOYQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@oxc-project/types": "^0.78.0"
},
@@ -7352,6 +7703,23 @@
"@oxc-parser/binding-win32-x64-msvc": "0.78.0"
}
},
+ "node_modules/oxc-parser/node_modules/@oxc-parser/binding-wasm32-wasi": {
+ "version": "0.78.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.78.0.tgz",
+ "integrity": "sha512-Pq0uT2CuN3J7Tv3KLuO7Sh4C7zTuqdJl0IDg3zB5keKx0BSbaEWewJL2CUNYUlG8txf+sMpUV+bkAIS5MEcKAw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/oxlint": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/oxlint/-/oxlint-0.4.4.tgz",
@@ -7879,6 +8247,28 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react": {
+ "version": "19.2.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz",
+ "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz",
+ "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.1"
+ }
+ },
"node_modules/read-package-up": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-12.0.0.tgz",
@@ -8192,6 +8582,12 @@
"license": "BlueOak-1.0.0",
"optional": true
},
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
"node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
@@ -10148,7 +10544,7 @@
},
"packages/css": {
"name": "@knighted/css",
- "version": "1.0.0-alpha.5",
+ "version": "1.0.0-rc.0",
"license": "MIT",
"dependencies": {
"dependency-tree": "^11.2.0",
@@ -10175,17 +10571,23 @@
"name": "@knighted/css-playwright-fixture",
"version": "0.0.0",
"dependencies": {
- "@knighted/css": "1.0.0-alpha.5"
+ "@knighted/css": "1.0.0-rc.0",
+ "@knighted/jsx": "^1.2.1",
+ "lit": "^3.2.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
},
"devDependencies": {
- "@types/knighted__css": "1.0.0-alpha.5"
+ "@types/knighted__css": "1.0.0-rc.0",
+ "@types/react": "^19.0.4",
+ "@types/react-dom": "^19.0.2"
}
},
"packages/types": {
"name": "@types/knighted__css",
- "version": "1.0.0-alpha.5",
+ "version": "1.0.0-rc.0",
"dependencies": {
- "@knighted/css": "1.0.0-alpha.5"
+ "@knighted/css": "1.0.0-rc.0"
}
}
}
diff --git a/package.json b/package.json
index 54014f0..36e39e1 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"test": "npm run test -w @knighted/css",
"pretest": "npm run build",
"test:e2e": "npm run test -w @knighted/css-playwright-fixture",
- "pretest:e2e": "npm run build",
+ "pretest:e2e": "npm run setup:jsx-wasm && npm run build",
"lint": "oxlint packages",
"prettier": "prettier --write .",
"prettier:check": "prettier --check .",
@@ -25,10 +25,13 @@
"check-types": "npm run check-types -w @knighted/css && npm run check-types -w @knighted/css-playwright-fixture",
"clean:deps": "find . -name node_modules -type d -prune -exec rm -rf {} +",
"clean:dist": "find . -name dist -type d -prune -exec rm -rf {} +",
- "clean": "npm run clean:deps && npm run clean:dist"
+ "clean": "npm run clean:deps && npm run clean:dist",
+ "setup:jsx-wasm": "node scripts/setup-jsx-wasm.mjs"
},
"devDependencies": {
"@knighted/duel": "^2.1.6",
+ "@emnapi/core": "^1.2.0",
+ "@emnapi/runtime": "^1.2.0",
"@playwright/test": "^1.48.2",
"@rspack/cli": "^1.0.0",
"@rspack/core": "^1.0.0",
@@ -42,6 +45,7 @@
"husky": "^9.1.7",
"less": "^4.2.0",
"lint-staged": "^16.2.7",
+ "@napi-rs/wasm-runtime": "^1.0.7",
"oxlint": "^0.4.1",
"prettier": "^3.7.4",
"sass": "^1.80.7",
diff --git a/packages/css/package.json b/packages/css/package.json
index e67b2c0..62b4379 100644
--- a/packages/css/package.json
+++ b/packages/css/package.json
@@ -1,6 +1,6 @@
{
"name": "@knighted/css",
- "version": "1.0.0-alpha.5",
+ "version": "1.0.0-rc.0",
"description": "A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.",
"type": "module",
"main": "./dist/css.js",
diff --git a/packages/playwright/package.json b/packages/playwright/package.json
index 8aa59a0..ce6b781 100644
--- a/packages/playwright/package.json
+++ b/packages/playwright/package.json
@@ -12,9 +12,15 @@
"pretest": "npm run build"
},
"dependencies": {
- "@knighted/css": "1.0.0-alpha.5"
+ "@knighted/css": "1.0.0-rc.0",
+ "@knighted/jsx": "^1.2.1",
+ "lit": "^3.2.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
},
"devDependencies": {
- "@types/knighted__css": "1.0.0-alpha.5"
+ "@types/knighted__css": "1.0.0-rc.0",
+ "@types/react": "^19.0.4",
+ "@types/react-dom": "^19.0.2"
}
}
diff --git a/packages/playwright/playwright.config.ts b/packages/playwright/playwright.config.ts
index d788c92..4dd67dc 100644
--- a/packages/playwright/playwright.config.ts
+++ b/packages/playwright/playwright.config.ts
@@ -18,7 +18,7 @@ if (isCI) {
export default defineConfig({
testDir: 'test',
- timeout: 60_000,
+ timeout: 10_000,
expect: {
timeout: 10_000,
},
diff --git a/packages/playwright/rspack.config.js b/packages/playwright/rspack.config.js
index efe9ac9..71a1c3e 100644
--- a/packages/playwright/rspack.config.js
+++ b/packages/playwright/rspack.config.js
@@ -1,5 +1,6 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
+import { ProvidePlugin } from '@rspack/core'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
@@ -17,9 +18,9 @@ export default {
},
},
resolve: {
- extensions: ['.ts', '.js'],
+ extensions: ['.tsx', '.ts', '.js'],
extensionAlias: {
- '.js': ['.js', '.ts'],
+ '.js': ['.js', '.ts', '.tsx'],
},
},
module: {
@@ -40,7 +41,7 @@ export default {
target: 'es2022',
parser: {
syntax: 'typescript',
- tsx: false,
+ tsx: true,
},
},
},
@@ -65,7 +66,7 @@ export default {
target: 'es2022',
parser: {
syntax: 'typescript',
- tsx: false,
+ tsx: true,
},
},
},
@@ -76,6 +77,9 @@ export default {
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
+ {
+ loader: '@knighted/jsx/loader',
+ },
{
loader: 'builtin:swc-loader',
options: {
@@ -104,4 +108,9 @@ export default {
},
],
},
+ plugins: [
+ new ProvidePlugin({
+ React: 'react',
+ }),
+ ],
}
diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts
index b8de712..8527c48 100644
--- a/packages/playwright/src/index.ts
+++ b/packages/playwright/src/index.ts
@@ -17,6 +17,7 @@ import VanillaComp, {
tokenClass,
} from './dialects/vanilla.js'
import { knightedCss as vanillaCss } from './dialects/vanilla.css.js?knighted-css'
+import { renderLitReactDemo } from './lit-react/index.js'
const dialects = [
{ label: Basic(), className: basicClass, css: basicCss, testId: 'dialect-basic' },
@@ -53,10 +54,12 @@ function render() {
el.textContent = dialect.label
root.appendChild(el)
}
+ return root
}
if (typeof document !== 'undefined') {
- render()
+ const root = render()
+ renderLitReactDemo(root)
}
export { render }
diff --git a/packages/playwright/src/lit-react/badge.css b/packages/playwright/src/lit-react/badge.css
new file mode 100644
index 0000000..78a58e2
--- /dev/null
+++ b/packages/playwright/src/lit-react/badge.css
@@ -0,0 +1,24 @@
+:host {
+ display: inline-flex;
+}
+
+.readme-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ padding: 0.25rem 0.8rem;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ background: #fcd34d;
+ color: #0f172a;
+}
+
+.readme-badge-dot {
+ width: 0.4rem;
+ height: 0.4rem;
+ border-radius: 999px;
+ background: currentColor;
+}
diff --git a/packages/playwright/src/lit-react/badge.tsx b/packages/playwright/src/lit-react/badge.tsx
new file mode 100644
index 0000000..0d36978
--- /dev/null
+++ b/packages/playwright/src/lit-react/badge.tsx
@@ -0,0 +1,14 @@
+import './badge.css'
+
+type BadgeProps = {
+ label: string
+}
+
+export function Badge({ label }: BadgeProps) {
+ return (
+
+
+ {label}
+
+ )
+}
diff --git a/packages/playwright/src/lit-react/button-wrapper.ts b/packages/playwright/src/lit-react/button-wrapper.ts
new file mode 100644
index 0000000..2aee1f5
--- /dev/null
+++ b/packages/playwright/src/lit-react/button-wrapper.ts
@@ -0,0 +1,65 @@
+import { reactJsx } from '@knighted/jsx/react'
+import { createRoot, type Root } from 'react-dom/client'
+import { LitElement, html, unsafeCSS, type PropertyValues } from 'lit'
+
+import { Button } from './button.js'
+import { knightedCss as reactStyles } from './button.js?knighted-css'
+import { BUTTON_WRAPPER_TAG } from './constants.js'
+
+export class ButtonWrapper extends LitElement {
+ static styles = [unsafeCSS(reactStyles)]
+ static properties = {
+ ctaLabel: { type: String, attribute: 'cta-label' },
+ }
+
+ /**
+ * Lit installs reactive accessors for declared properties, and TypeScript only knows about
+ * them if we declare the property manually. Avoid class field initializers here or they will
+ * shadow Lit's accessors and prevent attr -> prop updates (see Lit warning).
+ */
+ declare ctaLabel: string
+ #reactRoot?: Root
+
+ firstUpdated(): void {
+ this.#mountReact()
+ }
+
+ disconnectedCallback(): void {
+ this.#reactRoot?.unmount()
+ super.disconnectedCallback()
+ }
+
+ #mountReact(): void {
+ if (!this.#reactRoot) {
+ const outlet = this.renderRoot.querySelector(
+ '[data-react-root]',
+ ) as HTMLDivElement | null
+ if (!outlet) return
+ this.#reactRoot = createRoot(outlet)
+ }
+ this.#renderReactTree()
+ }
+
+ #renderReactTree(): void {
+ if (!this.#reactRoot) return
+ const label = this.ctaLabel ?? 'React Button'
+ this.#reactRoot.render(reactJsx`<${Button} label=${label} />`)
+ }
+
+ protected updated(changed: PropertyValues): void {
+ super.updated(changed)
+ if (changed.has('ctaLabel')) {
+ this.#renderReactTree()
+ }
+ }
+
+ render() {
+ return html``
+ }
+}
+
+export function ensureButtonWrapperDefined(): void {
+ if (!customElements.get(BUTTON_WRAPPER_TAG)) {
+ customElements.define(BUTTON_WRAPPER_TAG, ButtonWrapper)
+ }
+}
diff --git a/packages/playwright/src/lit-react/button.css b/packages/playwright/src/lit-react/button.css
new file mode 100644
index 0000000..f47bb9d
--- /dev/null
+++ b/packages/playwright/src/lit-react/button.css
@@ -0,0 +1,29 @@
+:host {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.readme-button {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.75rem;
+ font-size: 16px;
+ font-weight: 600;
+ border: none;
+ border-radius: 999px;
+ padding: 0.65rem 1.75rem;
+ cursor: pointer;
+ background: #111827;
+ color: #f8fafc;
+ transition: background 200ms ease;
+}
+
+.readme-button-label {
+ display: inline-flex;
+ align-items: center;
+}
+
+.readme-button:hover {
+ background: #1d4ed8;
+}
diff --git a/packages/playwright/src/lit-react/button.tsx b/packages/playwright/src/lit-react/button.tsx
new file mode 100644
index 0000000..acf6d8e
--- /dev/null
+++ b/packages/playwright/src/lit-react/button.tsx
@@ -0,0 +1,17 @@
+import './button.css'
+import { Badge } from './badge.js'
+
+type ButtonProps = {
+ label: string
+}
+
+export function Button({ label }: ButtonProps) {
+ return (
+
+ )
+}
diff --git a/packages/playwright/src/lit-react/constants.ts b/packages/playwright/src/lit-react/constants.ts
new file mode 100644
index 0000000..4f935b3
--- /dev/null
+++ b/packages/playwright/src/lit-react/constants.ts
@@ -0,0 +1,2 @@
+export const BUTTON_WRAPPER_TAG = 'pw-button-wrapper'
+export const LIT_REACT_TEST_ID = 'readme-button-wrapper'
diff --git a/packages/playwright/src/lit-react/index.ts b/packages/playwright/src/lit-react/index.ts
new file mode 100644
index 0000000..1f59e8f
--- /dev/null
+++ b/packages/playwright/src/lit-react/index.ts
@@ -0,0 +1,12 @@
+import { BUTTON_WRAPPER_TAG, LIT_REACT_TEST_ID } from './constants.js'
+import { ensureButtonWrapperDefined } from './button-wrapper.js'
+
+export function renderLitReactDemo(root: HTMLElement): void {
+ ensureButtonWrapperDefined()
+ const host = document.createElement(BUTTON_WRAPPER_TAG)
+ host.dataset.testid = LIT_REACT_TEST_ID
+ host.setAttribute('cta-label', 'Launch CSS Build')
+ root.appendChild(host)
+}
+
+export { LIT_REACT_TEST_ID }
diff --git a/packages/playwright/test/lit-react.spec.ts b/packages/playwright/test/lit-react.spec.ts
new file mode 100644
index 0000000..1cd6150
--- /dev/null
+++ b/packages/playwright/test/lit-react.spec.ts
@@ -0,0 +1,75 @@
+import { expect, test } from '@playwright/test'
+
+import { LIT_REACT_TEST_ID } from '../src/lit-react/constants.js'
+
+test.describe('Lit + React wrapper demo', () => {
+ test.beforeEach(async ({ page }) => {
+ page.on('console', msg => {
+ if (msg.type() === 'error') {
+ console.error(`[browser:${msg.type()}] ${msg.text()}`)
+ }
+ })
+ page.on('pageerror', error => {
+ console.error(`[pageerror] ${error.message}`)
+ })
+ await page.goto('/')
+ })
+
+ test('renders Lit wrapper hosting the React button', async ({ page }) => {
+ const buttonHandle = await page.waitForFunction(testId => {
+ const host = document.querySelector(`[data-testid="${testId}"]`)
+ return host?.shadowRoot?.querySelector('button.readme-button') ?? null
+ }, LIT_REACT_TEST_ID)
+
+ const button = buttonHandle.asElement()
+ if (!button) throw new Error('React button was not rendered inside the Lit wrapper')
+
+ const metrics = await button.evaluate(node => {
+ const el = node as HTMLButtonElement
+ const style = getComputedStyle(el)
+ const rect = el.getBoundingClientRect()
+ const badge = el.querySelector('[data-testid="readme-badge"]') as HTMLElement | null
+ const label = el.querySelector(
+ '[data-testid="react-button-label"]',
+ ) as HTMLElement | null
+ const badgeStyle = badge ? getComputedStyle(badge) : null
+ const labelStyle = label ? getComputedStyle(label) : null
+
+ return {
+ button: {
+ text: el.textContent?.replace(/\s+/g, ' ').trim() ?? '',
+ label: label?.textContent?.trim() ?? null,
+ visible: style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0,
+ background: style.getPropertyValue('background-color').trim(),
+ radius: style.getPropertyValue('border-radius').trim(),
+ labelColor: labelStyle?.getPropertyValue('color').trim() ?? null,
+ },
+ badge:
+ badge && badgeStyle
+ ? {
+ text: badge.textContent?.trim() ?? '',
+ background: badgeStyle.getPropertyValue('background-color').trim(),
+ color: badgeStyle.getPropertyValue('color').trim(),
+ }
+ : null,
+ }
+ })
+
+ await buttonHandle.dispose()
+
+ if (!metrics.badge) {
+ throw new Error('Nested badge component styles were not applied')
+ }
+
+ expect(metrics.button.visible).toBe(true)
+ expect(metrics.button.label).toBe('Launch CSS Build')
+ expect(metrics.button.text).toContain('Launch CSS Build')
+ expect(metrics.button.background).toBe('rgb(17, 24, 39)')
+ expect(metrics.button.radius).toBe('999px')
+ expect(metrics.button.labelColor).toBe('rgb(248, 250, 252)')
+
+ expect(metrics.badge.text).toBe('Synced Styles')
+ expect(metrics.badge.background).toBe('rgb(252, 211, 77)')
+ expect(metrics.badge.color).toBe('rgb(15, 23, 42)')
+ })
+})
diff --git a/packages/playwright/tsconfig.json b/packages/playwright/tsconfig.json
index db11192..b7e58a7 100644
--- a/packages/playwright/tsconfig.json
+++ b/packages/playwright/tsconfig.json
@@ -6,7 +6,8 @@
"strict": true,
"outDir": "dist",
"skipLibCheck": true,
- "declaration": true
+ "declaration": true,
+ "jsx": "react-jsx"
},
"include": ["src/**/*"]
}
diff --git a/packages/types/package.json b/packages/types/package.json
index 6c5edf2..3c42e92 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@types/knighted__css",
- "version": "1.0.0-alpha.5",
+ "version": "1.0.0-rc.0",
"private": false,
"type": "module",
"types": "index.d.ts",
@@ -9,6 +9,6 @@
"loader-queries.d.ts"
],
"dependencies": {
- "@knighted/css": "1.0.0-alpha.5"
+ "@knighted/css": "1.0.0-rc.0"
}
}
diff --git a/scripts/setup-jsx-wasm.mjs b/scripts/setup-jsx-wasm.mjs
new file mode 100644
index 0000000..4a1ad46
--- /dev/null
+++ b/scripts/setup-jsx-wasm.mjs
@@ -0,0 +1,44 @@
+#!/usr/bin/env node
+import { execSync } from 'node:child_process'
+import { existsSync, mkdirSync, rmSync } from 'node:fs'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+const ROOT = path.resolve(__dirname, '..')
+const BINDING_VERSION = process.env.KNIGHTED_JSX_WASM_VERSION ?? '0.99.0'
+const NODE_MODULES = path.join(ROOT, 'node_modules')
+const bindingDir = path.join(NODE_MODULES, '@oxc-parser', 'binding-wasm32-wasi')
+const sentinel = path.join(bindingDir, 'parser.wasi.cjs')
+
+if (existsSync(sentinel)) {
+ console.log('[setup:jsx-wasm] Binding already present, skipping install.')
+ process.exit(0)
+}
+
+const tarball = `oxc-parser-binding-wasm32-wasi-${BINDING_VERSION}.tgz`
+
+function run(command) {
+ execSync(command, { cwd: ROOT, stdio: 'inherit' })
+}
+
+try {
+ console.log(
+ `[setup:jsx-wasm] Packing @oxc-parser/binding-wasm32-wasi@${BINDING_VERSION}...`,
+ )
+ run(`npm pack @oxc-parser/binding-wasm32-wasi@${BINDING_VERSION}`)
+
+ mkdirSync(bindingDir, { recursive: true })
+
+ console.log('[setup:jsx-wasm] Extracting tarball into node_modules...')
+ run(`tar -xzf ${tarball} -C ${bindingDir} --strip-components=1`)
+
+ console.log('[setup:jsx-wasm] Cleaning up tarball...')
+ rmSync(path.join(ROOT, tarball))
+
+ console.log('[setup:jsx-wasm] Installed WASM parser binding successfully.')
+} catch (error) {
+ console.error('[setup:jsx-wasm] Failed to install WASM binding:', error)
+ process.exitCode = 1
+}