diff --git a/README.md b/README.md index e1f944f9..f8556dce 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Just-in-Time Typescript and ESM support for Node.js. - Node.js native require cache integration - Filesystem transpile with hard disk caches - Custom resolve aliases +- JSX support (opt-in) ## 🌟 Used by @@ -211,6 +212,16 @@ Parent module's [`import.meta`](https://developer.mozilla.org/en-US/docs/Web/Jav Try to use native require and import without jiti transformations first. +### `jsx` + +- Type: Boolean | {options} +- Default: `false` +- Environment Variable: `JITI_JSX` + +Enable JSX support using [`@babel/plugin-transform-react-jsx`](https://babeljs.io/docs/babel-plugin-transform-react-jsx). + +See [`test/fixtures/jsx`](./test/fixtures/jsx) for framework integration examples. + ## Development - Clone this repository diff --git a/lib/jiti-hooks.mjs b/lib/jiti-hooks.mjs index 30e9ca03..29263581 100644 --- a/lib/jiti-hooks.mjs +++ b/lib/jiti-hooks.mjs @@ -58,6 +58,7 @@ export async function load(url, context, nextLoad) { ts: url.endsWith("ts"), retainLines: true, async: true, + jsx: jiti.options.jsx, }); if (url.endsWith(".js") && !transpiledSource.includes("jitiImport")) { diff --git a/lib/types.d.ts b/lib/types.d.ts index 2b61bdc8..2b892228 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -8,6 +8,11 @@ export declare function createJiti(id: string, userOptions?: JitiOptions): Jiti; * **Note:**It is recommended to use `await jiti.import` instead */ export interface Jiti extends NodeRequire { + /** + * Resolved options + */ + options: JitiOptions; + /** * ESM import a module with additional Typescript and ESM compatibility. */ @@ -144,6 +149,15 @@ export interface JitiOptions { * Enabled if Bun is detected. */ tryNative?: boolean; + + /** + * Enable JSX support Enable JSX support using [`@babel/plugin-transform-react-jsx`](https://babeljs.io/docs/babel-plugin-transform-react-jsx). + * + * @default false + * + * You can also use `JITI_JSX=1` environment variable to enable JSX support. + */ + jsx?: boolean | JSXOptions; } export type ModuleCache = Record; @@ -163,6 +177,7 @@ export interface TransformOptions { retainLines?: boolean; interopDefault?: boolean; async: boolean; + jsx?: boolean | JSXOptions; [key: string]: any; } @@ -176,3 +191,14 @@ export interface JitiResolveOptions { parentURL?: string | URL; try?: boolean; } + +/** Reference: https://babeljs.io/docs/babel-plugin-transform-react-jsx#options */ +export interface JSXOptions { + throwIfNamespace?: boolean; + runtime?: "classic" | "automatic"; + importSource?: string; + pragma?: string; + pragmaFrag?: string; + useBuiltIns?: boolean; + useSpread?: boolean; +} diff --git a/package.json b/package.json index 4b229490..f854b87a 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "build": "pnpm clean && NODE_ENV=production pnpm webpack", "clean": "rm -rf dist", "dev": "pnpm clean && pnpm webpack --watch", - "jiti": "JITI_DEBUG=1 lib/jiti-cli.mjs", + "jiti": "JITI_DEBUG=1 JITI_JSX=1 lib/jiti-cli.mjs", "lint": "eslint . && prettier -c src lib test stubs", "lint:fix": "eslint --fix . && prettier -w src lib test stubs", "prepack": "pnpm build", @@ -52,7 +52,7 @@ "test:native:bun": "bun --bun test test/native/bun.test.ts", "test:native:deno": "deno test -A test/native/deno.test.ts", "test:native:node": "node --test --experimental-strip-types test/native/node.test.ts", - "test:node-register": "node --test test/node-register.test.mjs", + "test:node-register": "JITI_JSX=1 node --test test/node-register.test.mjs", "test:types": "tsc --noEmit" }, "devDependencies": { @@ -64,7 +64,9 @@ "@babel/plugin-proposal-decorators": "^7.24.7", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-import-assertions": "^7.25.6", + "@babel/plugin-syntax-jsx": "^7.24.7", "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/preset-typescript": "^7.24.7", "@babel/template": "^7.25.0", @@ -89,16 +91,23 @@ "estree-walker": "^3.0.3", "fast-glob": "^3.3.2", "mlly": "^1.7.1", + "nano-jsx": "^0.1.0", "pathe": "^1.1.2", "pkg-types": "^1.2.0", + "preact": "^10.24.1", + "preact-render-to-string": "^6.5.11", "prettier": "^3.3.3", + "react": "^18.3.1", + "react-dom": "^18.3.1", "reflect-metadata": "^0.2.2", + "solid-js": "^1.9.1", "std-env": "^3.7.0", "terser-webpack-plugin": "^5.3.10", "tinyexec": "^0.3.0", "ts-loader": "^9.5.1", "typescript": "^5.6.2", "vitest": "^2.1.1", + "vue": "^3.5.8", "webpack": "^5.94.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76d2ba8d..cced6a60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,9 +32,15 @@ importers: '@babel/plugin-syntax-import-assertions': specifier: ^7.25.6 version: 7.25.6(@babel/core@7.25.2) + '@babel/plugin-syntax-jsx': + specifier: ^7.24.7 + version: 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-export-namespace-from': specifier: ^7.24.7 version: 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-react-jsx': + specifier: ^7.25.2 + version: 7.25.2(@babel/core@7.25.2) '@babel/plugin-transform-typescript': specifier: ^7.25.2 version: 7.25.2(@babel/core@7.25.2) @@ -107,18 +113,36 @@ importers: mlly: specifier: ^1.7.1 version: 1.7.1 + nano-jsx: + specifier: ^0.1.0 + version: 0.1.0 pathe: specifier: ^1.1.2 version: 1.1.2 pkg-types: specifier: ^1.2.0 version: 1.2.0 + preact: + specifier: ^10.24.1 + version: 10.24.1 + preact-render-to-string: + specifier: ^6.5.11 + version: 6.5.11(preact@10.24.1) prettier: specifier: ^3.3.3 version: 3.3.3 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) reflect-metadata: specifier: ^0.2.2 version: 0.2.2 + solid-js: + specifier: ^1.9.1 + version: 1.9.1 std-env: specifier: ^3.7.0 version: 3.7.0 @@ -137,6 +161,9 @@ importers: vitest: specifier: ^2.1.1 version: 2.1.1(@types/node@22.5.5)(terser@5.33.0) + vue: + specifier: ^3.5.8 + version: 3.5.8(typescript@5.6.2) webpack: specifier: ^5.94.0 version: 5.94.0(webpack-cli@5.1.4) @@ -302,6 +329,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.25.2': + resolution: {integrity: sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-typescript@7.25.2': resolution: {integrity: sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==} engines: {node: '>=6.9.0'} @@ -777,6 +810,35 @@ packages: '@vitest/utils@2.1.1': resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} + '@vue/compiler-core@3.5.8': + resolution: {integrity: sha512-Uzlxp91EPjfbpeO5KtC0KnXPkuTfGsNDeaKQJxQN718uz+RqDYarEf7UhQJGK+ZYloD2taUbHTI2J4WrUaZQNA==} + + '@vue/compiler-dom@3.5.8': + resolution: {integrity: sha512-GUNHWvoDSbSa5ZSHT9SnV5WkStWfzJwwTd6NMGzilOE/HM5j+9EB9zGXdtu/fCNEmctBqMs6C9SvVPpVPuk1Eg==} + + '@vue/compiler-sfc@3.5.8': + resolution: {integrity: sha512-taYpngQtSysrvO9GULaOSwcG5q821zCoIQBtQQSx7Uf7DxpR6CIHR90toPr9QfDD2mqHQPCSgoWBvJu0yV9zjg==} + + '@vue/compiler-ssr@3.5.8': + resolution: {integrity: sha512-W96PtryNsNG9u0ZnN5Q5j27Z/feGrFV6zy9q5tzJVyJaLiwYxvC0ek4IXClZygyhjm+XKM7WD9pdKi/wIRVC/Q==} + + '@vue/reactivity@3.5.8': + resolution: {integrity: sha512-mlgUyFHLCUZcAYkqvzYnlBRCh0t5ZQfLYit7nukn1GR96gc48Bp4B7OIcSfVSvlG1k3BPfD+p22gi1t2n9tsXg==} + + '@vue/runtime-core@3.5.8': + resolution: {integrity: sha512-fJuPelh64agZ8vKkZgp5iCkPaEqFJsYzxLk9vSC0X3G8ppknclNDr61gDc45yBGTaN5Xqc1qZWU3/NoaBMHcjQ==} + + '@vue/runtime-dom@3.5.8': + resolution: {integrity: sha512-DpAUz+PKjTZPUOB6zJgkxVI3GuYc2iWZiNeeHQUw53kdrparSTG6HeXUrYDjaam8dVsCdvQxDz6ZWxnyjccUjQ==} + + '@vue/server-renderer@3.5.8': + resolution: {integrity: sha512-7AmC9/mEeV9mmXNVyUIm1a1AjUhyeeGNbkLh39J00E7iPeGks8OGRB5blJiMmvqSh8SkaS7jkLWSpXtxUCeagA==} + peerDependencies: + vue: 3.5.8 + + '@vue/shared@3.5.8': + resolution: {integrity: sha512-mJleSWbAGySd2RJdX1RBtcrUBX6snyOc0qHpgk3lGi4l9/P/3ny3ELqFWqYdkXIwwNN/kdm8nD9ky8o6l/Lx2A==} + '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -1093,6 +1155,9 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -1157,6 +1222,10 @@ packages: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + envinfo@7.14.0: resolution: {integrity: sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==} engines: {node: '>=4'} @@ -1253,6 +1322,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1611,6 +1683,10 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + loupe@3.1.1: resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} @@ -1708,6 +1784,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nano-jsx@0.1.0: + resolution: {integrity: sha512-S4qJM9ayruMdDnn3hiHNK6kq0ZvCaNrDL3RD5jc4AVhmsW1Ufk3xE64Q6xrjAzq1Gff+6VZ5+Au8For4FT/6LA==} + engines: {node: '>=16'} + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1857,6 +1937,14 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + preact-render-to-string@6.5.11: + resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} + peerDependencies: + preact: '>=10' + + preact@10.24.1: + resolution: {integrity: sha512-PnBAwFI3Yjxxcxw75n6VId/5TFxNW/81zexzWD9jn1+eSrOP84NdsS38H5IkF/UH3frqRPT+MvuCoVHjTDTnDw==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1879,6 +1967,15 @@ packages: rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -1947,6 +2044,9 @@ packages: sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + schema-utils@3.3.0: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} @@ -1970,6 +2070,16 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + seroval-plugins@1.1.1: + resolution: {integrity: sha512-qNSy1+nUj7hsCOon7AO4wdAIo9P0jrzAMp18XhiOzA6/uO5TKtP7ScozVJ8T293oRIvi5wyCHSM4TrJo/c/GJA==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.1.1: + resolution: {integrity: sha512-rqEO6FZk8mv7Hyv4UCj3FD3b6Waqft605TLfsCe/BiaylRpyyMC0b+uA5TJKawX3KzMrdi3wsLbCaLplrQmBvQ==} + engines: {node: '>=10'} + shallow-clone@3.0.1: resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} engines: {node: '>=8'} @@ -1997,6 +2107,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + solid-js@1.9.1: + resolution: {integrity: sha512-Gd6QWRFfO2XKKZqVK4YwbhWZkr0jWw1dYHOt+VYebomeyikGP0SuMflf42XcDuU9HAEYDArFJIYsBNjlE7iZsw==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2265,6 +2378,14 @@ packages: jsdom: optional: true + vue@3.5.8: + resolution: {integrity: sha512-hvuvuCy51nP/1fSRvrrIqTLSvrSyz2Pq+KQ8S8SXCxTWVE0nMaOnSDnSOxV1eYmGfvK7mqiwvd1C59CEEz7dAQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + watchpack@2.4.2: resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} @@ -2566,6 +2687,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2) + '@babel/types': 7.25.6 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-typescript@7.25.2(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -3013,6 +3145,60 @@ snapshots: loupe: 3.1.1 tinyrainbow: 1.2.0 + '@vue/compiler-core@3.5.8': + dependencies: + '@babel/parser': 7.25.6 + '@vue/shared': 3.5.8 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.8': + dependencies: + '@vue/compiler-core': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/compiler-sfc@3.5.8': + dependencies: + '@babel/parser': 7.25.6 + '@vue/compiler-core': 3.5.8 + '@vue/compiler-dom': 3.5.8 + '@vue/compiler-ssr': 3.5.8 + '@vue/shared': 3.5.8 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.47 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.8': + dependencies: + '@vue/compiler-dom': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/reactivity@3.5.8': + dependencies: + '@vue/shared': 3.5.8 + + '@vue/runtime-core@3.5.8': + dependencies: + '@vue/reactivity': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/runtime-dom@3.5.8': + dependencies: + '@vue/reactivity': 3.5.8 + '@vue/runtime-core': 3.5.8 + '@vue/shared': 3.5.8 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.8(vue@3.5.8(typescript@5.6.2))': + dependencies: + '@vue/compiler-ssr': 3.5.8 + '@vue/shared': 3.5.8 + vue: 3.5.8(typescript@5.6.2) + + '@vue/shared@3.5.8': {} + '@webassemblyjs/ast@1.12.1': dependencies: '@webassemblyjs/helper-numbers': 1.11.6 @@ -3346,6 +3532,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + csstype@3.1.3: {} + debounce@1.2.1: {} debug@4.3.7: @@ -3390,6 +3578,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 + entities@4.5.0: {} + envinfo@7.14.0: {} error-ex@1.3.2: @@ -3546,6 +3736,8 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.6 @@ -3863,6 +4055,10 @@ snapshots: lodash@4.17.21: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + loupe@3.1.1: dependencies: get-func-name: 2.0.2 @@ -3961,6 +4157,8 @@ snapshots: ms@2.1.3: {} + nano-jsx@0.1.0: {} + nanoid@3.3.7: {} natural-compare@1.4.0: {} @@ -4111,6 +4309,12 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.1 + preact-render-to-string@6.5.11(preact@10.24.1): + dependencies: + preact: 10.24.1 + + preact@10.24.1: {} + prelude-ls@1.2.1: {} prettier@3.3.3: {} @@ -4128,6 +4332,16 @@ snapshots: defu: 6.1.4 destr: 2.0.3 + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 @@ -4207,6 +4421,10 @@ snapshots: sax@1.4.1: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + schema-utils@3.3.0: dependencies: '@types/json-schema': 7.0.15 @@ -4225,6 +4443,12 @@ snapshots: dependencies: randombytes: 2.1.0 + seroval-plugins@1.1.1(seroval@1.1.1): + dependencies: + seroval: 1.1.1 + + seroval@1.1.1: {} + shallow-clone@3.0.1: dependencies: kind-of: 6.0.3 @@ -4247,6 +4471,12 @@ snapshots: slash@3.0.0: {} + solid-js@1.9.1: + dependencies: + csstype: 3.1.3 + seroval: 1.1.1 + seroval-plugins: 1.1.1(seroval@1.1.1) + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -4494,6 +4724,16 @@ snapshots: - supports-color - terser + vue@3.5.8(typescript@5.6.2): + dependencies: + '@vue/compiler-dom': 3.5.8 + '@vue/compiler-sfc': 3.5.8 + '@vue/runtime-dom': 3.5.8 + '@vue/server-renderer': 3.5.8(vue@3.5.8(typescript@5.6.2)) + '@vue/shared': 3.5.8 + optionalDependencies: + typescript: 5.6.2 + watchpack@2.4.2: dependencies: glob-to-regexp: 0.4.1 diff --git a/src/_types/babel-plugin-syntax-jsx.d.ts b/src/_types/babel-plugin-syntax-jsx.d.ts new file mode 100644 index 00000000..687e3265 --- /dev/null +++ b/src/_types/babel-plugin-syntax-jsx.d.ts @@ -0,0 +1,6 @@ +declare module "@babel/plugin-syntax-jsx" { + declare function jsx(): { + manipulateOptions(opts: any, parserOpts: { plugins: string[] }): void; + }; + export default jsx; +} diff --git a/src/_types/babel-plugin-transform-react-jsx.ts b/src/_types/babel-plugin-transform-react-jsx.ts new file mode 100644 index 00000000..dc5cd9cf --- /dev/null +++ b/src/_types/babel-plugin-transform-react-jsx.ts @@ -0,0 +1,17 @@ +declare module "@babel/plugin-transform-react-jsx" { + import type { declare } from "@babel/helper-plugin-utils"; + + /** Reference: https://babeljs.io/docs/babel-plugin-transform-react-jsx#options */ + export interface Options { + throwIfNamespace?: boolean; + runtime?: "classic" | "automatic"; + importSource?: string; + pragma?: string; + pragmaFrag?: string; + useBuiltIns?: boolean; + useSpread?: boolean; + } + + const transformReactJSXPlugin: ReturnType>; + export default transformReactJSXPlugin; +} diff --git a/src/babel.ts b/src/babel.ts index c2ca8ffc..d1937714 100644 --- a/src/babel.ts +++ b/src/babel.ts @@ -10,6 +10,8 @@ import transformExportNamespaceFromPlugin from "@babel/plugin-transform-export-n import transformTypeScriptPlugin from "@babel/plugin-transform-typescript"; import parameterDecoratorPlugin from "babel-plugin-parameter-decorator"; import transformTypeScriptMetaPlugin from "babel-plugin-transform-typescript-metadata"; +import syntaxJSXPlugin from "@babel/plugin-syntax-jsx"; +import transformReactJSX from "@babel/plugin-transform-react-jsx"; import { TransformImportMetaPlugin } from "./plugins/babel-plugin-transform-import-meta"; import { importMetaEnvPlugin } from "./plugins/import-meta-env"; import transformModulesPlugin from "./plugins/transform-module"; @@ -41,6 +43,13 @@ export default function transform(opts: TransformOptions): TransformResult { ], }; + if (opts.jsx) { + _opts.plugins.push( + [syntaxJSXPlugin], + [transformReactJSX, Object.assign({}, opts.jsx)], + ); + } + if (opts.ts) { _opts.plugins.push([ transformTypeScriptPlugin, diff --git a/src/cache.ts b/src/cache.ts index f65c0ce3..158e6056 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -20,11 +20,14 @@ export function getCache( const sourceHash = ` /* v${CACHE_VERSION}-${md5(topts.source, 16)} */\n`; // Compute cache file path - const cacheName = + let cacheName = `${basename(dirname(topts.filename))}-${filename(topts.filename)}` + (topts.interopDefault ? ".i" : "") + `.${md5(topts.filename)}` + (topts.async ? ".mjs" : ".cjs"); + if (topts.jsx && topts.filename.endsWith("x") /* jsx */) { + cacheName += "x"; + } const cacheDir = ctx.opts.fsCache as string; const cacheFilePath = join(cacheDir, cacheName); diff --git a/src/eval.ts b/src/eval.ts index 9bf90f39..498daee1 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -45,12 +45,13 @@ export function evalModule( ctx.isTransformRe.test(filename) || hasESMSyntax(source)); const start = performance.now(); - if (needsTranspile) { + if (needsTranspile || (ctx.opts.jsx && (ext === ".jsx" || ext === ".tsx"))) { source = transform(ctx, { filename, source, ts: isTypescript, async: evalOptions.async ?? false, + jsx: ctx.opts.jsx, }); const time = Math.round((performance.now() - start) * 1000) / 1000; debug( @@ -77,6 +78,7 @@ export function evalModule( source, ts: isTypescript, async: evalOptions.async ?? false, + jsx: ctx.opts.jsx, }); } } diff --git a/src/jiti.ts b/src/jiti.ts index c66a32cc..8c6ead33 100644 --- a/src/jiti.ts +++ b/src/jiti.ts @@ -126,6 +126,7 @@ export default function createJiti( cache: opts.moduleCache ? nativeRequire.cache : Object.create(null), extensions: nativeRequire.extensions, main: nativeRequire.main, + options: opts, resolve: Object.assign( function resolve(path: string) { return jitiResolve(ctx, path, { async: false }); diff --git a/src/options.ts b/src/options.ts index d77e881f..ac89880a 100644 --- a/src/options.ts +++ b/src/options.ts @@ -23,8 +23,13 @@ export function resolveJitiOptions(userOptions: JitiOptions): JitiOptions { nativeModules: _jsonEnv("JITI_NATIVE_MODULES", []), transformModules: _jsonEnv("JITI_TRANSFORM_MODULES", []), tryNative: _jsonEnv("JITI_TRY_NATIVE", "Bun" in globalThis), + jsx: _booleanEnv("JITI_JSX", false), }; + if (jitiDefaults.jsx) { + jitiDefaults.extensions!.push(".jsx", ".tsx"); + } + const deprecatOverrides: JitiOptions = {}; if (userOptions.cache !== undefined) { deprecatOverrides.fsCache = userOptions.cache; diff --git a/test/__snapshots__/fixtures.test.ts.snap b/test/__snapshots__/fixtures.test.ts.snap index f3436dbb..f4eb3ed6 100644 --- a/test/__snapshots__/fixtures.test.ts.snap +++ b/test/__snapshots__/fixtures.test.ts.snap @@ -4,6 +4,8 @@ exports[`fixtures > async > stdout 1`] = `"works"`; exports[`fixtures > circular > stdout 1`] = `"a b c"`; +exports[`fixtures > data-uri > stdout 1`] = `""`; + exports[`fixtures > env > stdout 1`] = ` "process.env true process.env.TEST true @@ -56,6 +58,13 @@ Required : { test: 123 } .default: { test: 123 } Dynamic Imported : { test: 123 } .default: { test: 123 }" `; +exports[`fixtures > jsx > stdout 1`] = ` +"

Hello, nano-jsx!

+

Hello, preact!

+

Hello, react!

+

Hello, vue!

" +`; + exports[`fixtures > mixed > stdout 1`] = `"Mixed works for: "`; exports[`fixtures > native > stdout 1`] = `"[Module: null prototype] { default: { hasRequire: false } }"`; diff --git a/test/fixtures.test.ts b/test/fixtures.test.ts index 5dc27579..cf6098b9 100644 --- a/test/fixtures.test.ts +++ b/test/fixtures.test.ts @@ -53,6 +53,7 @@ describe("fixtures", async () => { stdio: "pipe", env: { JITI_CACHE: "false", + JITI_JSX: "true", }, }, }); diff --git a/test/fixtures/jsx/index.ts b/test/fixtures/jsx/index.ts new file mode 100644 index 00000000..d27b89ba --- /dev/null +++ b/test/fixtures/jsx/index.ts @@ -0,0 +1,4 @@ +import "./nano-jsx.jsx"; +import "./preact.jsx"; +import "./react.jsx"; +import "./vue.jsx"; diff --git a/test/fixtures/jsx/nano-jsx.jsx b/test/fixtures/jsx/nano-jsx.jsx new file mode 100644 index 00000000..37a3edac --- /dev/null +++ b/test/fixtures/jsx/nano-jsx.jsx @@ -0,0 +1,4 @@ +/** @jsx h */ +import { h, renderSSR } from "nano-jsx"; + +console.log(renderSSR(() =>

Hello, nano-jsx!

)); diff --git a/test/fixtures/jsx/preact.jsx b/test/fixtures/jsx/preact.jsx new file mode 100644 index 00000000..7574f49b --- /dev/null +++ b/test/fixtures/jsx/preact.jsx @@ -0,0 +1,5 @@ +/** @jsx h */ +import { h } from "preact"; +import render from "preact-render-to-string"; + +console.log(render(

Hello, preact!

)); diff --git a/test/fixtures/jsx/react.jsx b/test/fixtures/jsx/react.jsx new file mode 100644 index 00000000..dfa5959b --- /dev/null +++ b/test/fixtures/jsx/react.jsx @@ -0,0 +1,4 @@ +import React from "react"; +import { renderToString } from "react-dom/server"; + +console.log(renderToString(

Hello, react!

)); diff --git a/test/fixtures/jsx/vue.jsx b/test/fixtures/jsx/vue.jsx new file mode 100644 index 00000000..56e92cc3 --- /dev/null +++ b/test/fixtures/jsx/vue.jsx @@ -0,0 +1,5 @@ +/** @jsx h */ +import { h } from "vue"; +import { renderToString } from "vue/server-renderer"; + +console.log(await renderToString(

Hello, vue!

)); diff --git a/test/native/bun.test.ts b/test/native/bun.test.ts index eb172e72..46257499 100644 --- a/test/native/bun.test.ts +++ b/test/native/bun.test.ts @@ -13,6 +13,7 @@ const ignore = new Set([ "error-parse", "typescript", "data-uri", + "jsx", ]); for (const fixture of fixtures) { diff --git a/test/native/node.test.ts b/test/native/node.test.ts index 73cc77e8..cbd254b0 100644 --- a/test/native/node.test.ts +++ b/test/native/node.test.ts @@ -22,6 +22,7 @@ const ignore = new Set( "exotic", "circular", "data-uri", + "jsx", ].filter(Boolean), ); diff --git a/vitest.config.ts b/vitest.config.ts index b433e306..0336c1af 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,6 +2,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { - include: ["test/fixturtes.test.ts", "test/utils.test.ts"], + include: ["test/fixtures.test.ts", "test/utils.test.ts"], }, });