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 ( + + + ) +} 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 +}