From 36b64b76709fc9b0d59c40f69588c03fbad860a8 Mon Sep 17 00:00:00 2001 From: Kevin Deng Date: Fri, 26 Sep 2025 08:13:43 +0800 Subject: [PATCH] feat!: transform chunk --- README.md | 2 + package.json | 3 +- pnpm-lock.yaml | 174 ++++++++++++++++++++++++- src/index.ts | 99 ++++++++------ src/options.ts | 5 +- tests/__snapshots__/index.test.ts.snap | 68 +++++----- tests/fixtures/basic.js | 13 +- tests/fixtures/node-builtins.ts | 3 +- tests/fixtures/shebang.js | 2 + 9 files changed, 289 insertions(+), 80 deletions(-) create mode 100644 tests/fixtures/shebang.js diff --git a/README.md b/README.md index 5e93f85..8e9a411 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Transform ESM imports to CJS requires when the imported module is pure CJS. Some packages only provide CJS builds (e.g., [`typescript`](https://npmjs.com/package/typescript), [`@babel/parser`](https://npmjs.com/package/@babel/parser)), and importing them using ESM syntax increases Node's `cjs-module-lexer` overhead. This plugin converts ESM imports to CJS requires for such pure CJS packages, allowing Node to skip the cjs-module-lexer step and improve performance. +If performance is insignificant for your project, please do not use this plugin, as it introduces additional complexity and maintenance overhead. + See more: https://x.com/sanxiaozhizi/status/1968580207322808812 ## Install diff --git a/package.json b/package.json index 6bcd4e6..f2800a6 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "cjs-module-lexer": "^2.1.0", "empathic": "^2.0.0", "magic-string-ast": "^1.0.2", - "mlly": "^1.8.0" + "mlly": "^1.8.0", + "unplugin-utils": "^0.3.0" }, "devDependencies": { "@babel/parser": "^7.28.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca6026d..6d093b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: mlly: specifier: ^1.8.0 version: 1.8.0 + unplugin-utils: + specifier: ^0.3.0 + version: 0.3.0 devDependencies: '@babel/parser': specifier: ^7.28.4 @@ -464,6 +467,9 @@ packages: '@oxc-project/types@0.90.0': resolution: {integrity: sha512-fWvaufWUcLtm/OBKcNmxUkR0kQW5ZKAF0t03BXPqdzpxmnVCmSKzvUDRCOKnSagSfNzG/3ZdKpComH3GMy881g==} + '@oxc-project/types@0.92.0': + resolution: {integrity: sha512-PDLfCbwgXjGdTBxzcuDOUxJYNBl6P8dOp3eDKWw54dYvqONan9rwGDRQU0zrkdEMiItfXQQUOI17uOcMX5Zm7A==} + '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -481,86 +487,172 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.0.0-beta.40': + resolution: {integrity: sha512-9Ii9phC7QU6Lb+ncMfG1Xlosq0NBB1N/4sw+EGZ3y0BBWGy02TOb5ghWZalphAKv9rn1goqo5WkBjyd2YvsLmA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.0-beta.39': resolution: {integrity: sha512-tnuiLq9vd08KsZeFkFgzCXVKsTgSZGn+YBQjHSEiUvXJy5pfUf82X/YyLCG8P6I+WDd2cgrcLilMBQPZgaNwkg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-beta.40': + resolution: {integrity: sha512-5O6d0y2tBQTL+ecQY3qXIwSnF1/Zik8q7LZMKeyF+VJ9l194d0IdMhl2zUF0cqWbYHuF4Pnxplk4OhurPQ/Z9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-beta.39': resolution: {integrity: sha512-wLFoB3ZM4AoeBlsP0eVbPzWfkEgvmnibMQEKUgWRfJnKhUWiSxl0kGdSw1fNYdX3KAqIeA5gPJNvSJmf6g5S3Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-beta.40': + resolution: {integrity: sha512-izB9jygt3miPQbOTZfSu5K51isUplqa8ysByOKQqcJHgrBWmbTU8TM9eouv6tRmBR0kjcEcID9xhmA1CeZ1VIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-beta.39': resolution: {integrity: sha512-wzFZlixF9VMbyi++rHCU4Cy72SH11aBNnkadmvwTAbokwjYHi8NqxQ3/Lx00c700N6kwwuiTsbcGt5DEA9aROw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-beta.40': + resolution: {integrity: sha512-2fdpEpKT+wwP0vig9dqxu+toTeWmVSjo3psJQVDeLJ51rO+GXcCJ1IkCXjhMKVEevNtZS7B8T8Z2vvmRV9MAdA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.39': resolution: {integrity: sha512-eVnZcwGbje1uwdFjeQZQ6918RHgGIK7iTC+AoDsgetgAXQmQpnuWYQ9OWa5oTHNQyCkZbMfiHKgpkUPpceMecw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.40': + resolution: {integrity: sha512-HP2lo78OWULN+8TewpLbS9PS00jh0CaF04tA2u8z2I+6QgVgrYOYKvX+T0hlO5smgso4+qb3YchzumWJl3yCPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.39': resolution: {integrity: sha512-Td96iRQA0nmRZM6kJ3+LDDKWLh4bl0zqeR+IYxXwPZBw4iXSREzXrcZ3QqgFHqnXPgryIJEW1U1Ebh2xf+b2UA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.40': + resolution: {integrity: sha512-ng00gfr9BhA2NPAOU5RWAlTiL+JcwAD+L+4yUD1sbBy6tgHdLiNBOvKtHISIF9RM9/eQeS0tAiWOYZGIH9JMew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.39': resolution: {integrity: sha512-bcSIh1TFUoPcexJH+gO1sE6wpSR0j3UpWBnjAwyM1PRKfjtqN4R9Du90ofH5KsR/A35FT3eP4mdnhMDTd5Yt+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.40': + resolution: {integrity: sha512-mF0R1l9kLcaag/9cLEiYYdNZ4v1uuX4jklSDZ1s6vJE4RB3LirUney0FavdVRwCJ5sDvfvsPgXgtBXWYr2M2tQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.39': resolution: {integrity: sha512-tYEcZdVGovEemh7ELr+VUoezGkuBgRZYvDHHW/HVIw9LQW5HKLtBIGLzFlOfu/Lq5b9FlDKl+lrY6weviaNnKw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.40': + resolution: {integrity: sha512-+wi08S7wT5iLPHRZb0USrS6n+T6m+yY++dePYedE5uvKIpWCJJioFTaRtWjpm0V6dVNLcq2OukrvfdlGtH9Wgg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0-beta.39': resolution: {integrity: sha512-xf9QdMC+qwQxtFAty/9RxgCLFdp9pFl09g86hxGPzlzCtHUjd+BmeUnUTXvVC8CHJLWECLQbFP6/233XHG0blA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0-beta.40': + resolution: {integrity: sha512-W5qBGAemUocIBKCcOsDjlV9GUt28qhl/+M6etWBeLS5gQK0J6XDg0YVzfOQdvq57ZGjYNP0NvhYzqhOOnEx+4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-openharmony-arm64@1.0.0-beta.39': resolution: {integrity: sha512-QCvN02VpE6zFYry0zAU+29D5+O9tJELNt+OjuCubilZdD/S8xFdho7qBJaa3YhFYyA9cReOMVH8Z8b3yWb4hcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-beta.40': + resolution: {integrity: sha512-vJwoDehtt+yqj2zacq1AqNc2uE/oh7mnRGqAUbuldV6pgvU01OSQUJ7Zu+35hTopnjFoDNN6mIezkYlGAv5RFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0-beta.39': resolution: {integrity: sha512-LFgshxApyBNiBHFVpun7tPrIQ4TvxW0f/endC5C4RzEHu7mxexBCQEkO5XrZ42Cr5DUY+ERNbkfNTUv+vVCaxQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-beta.40': + resolution: {integrity: sha512-Oj3YyqVUPurr1FlMpEE/bJmMC+VWAWPM/SGUfklO5KUX97bk5Q/733nPg4RykK8q8/TluJoQYvRc05vL/B74dw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.39': resolution: {integrity: sha512-Mykirawg+s1e0uzVSEFhUBTShvXrOghPnyuLYkCfw8gzy8bMYiJuxsAfcopzZIIAVOHeSblJoiA/e7gYFjg8HA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.40': + resolution: {integrity: sha512-0ZtO6yN8XjVoFfN4HDWQj4nDu3ndMybr7jIM00DJqOmc+yFhly7rdOy7fNR9Sky3leCpBtsXfepVqRmVpYKPVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.39': resolution: {integrity: sha512-4PQJfWx7mdzXbAa4y+3OSSo911BZyJ/Is4pJKiwcGUqtvY66MX7BqlNWMr9QAozArAGE2knDubLqCQwZpK631w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.40': + resolution: {integrity: sha512-BPl1inoJXPpIe38Ja46E4y11vXlJyuleo+9Rmu//pYL5fIDYJkXUj/oAXqjSuwLcssrcwnuPgzvzvlz9++cr3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.39': resolution: {integrity: sha512-0zmmPOWbFfp1g9ofieimHwhuclZMcib0HL52Q+JTRpOHChI2f83TtH3duKWtAaxqhLUndTr/Z5sxzb+G2FNL9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.40': + resolution: {integrity: sha512-UguA4ltbAk+nbwHRxqaUP/etpTbR0HjyNlsu4Zjbh/ytNbFsbw8CA4tEBkwDyjgI5NIPea6xY11zpl7R2/ddVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-beta.39': resolution: {integrity: sha512-GkTtNCV8ObWbq3LrJStPBv9jkRPct8WlwotVjx3aU0RwfH3LyheixWK9Zhaj22C4EQj/TJxYyetoX+uOn/MWKw==} + '@rolldown/pluginutils@1.0.0-beta.40': + resolution: {integrity: sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==} + '@rollup/rollup-android-arm-eabi@4.52.0': resolution: {integrity: sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A==} cpu: [arm] @@ -2080,6 +2172,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.0.0-beta.40: + resolution: {integrity: sha512-VqEHbKpOgTPmQrZ4fVn4eshDQS/6g/fRpNE7cFSJY+eQLDZn4B9X61J6L+hnlt1u2uRI+pF7r1USs6S5fuWCvw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.52.0: resolution: {integrity: sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2714,6 +2811,8 @@ snapshots: '@oxc-project/types@0.90.0': {} + '@oxc-project/types@0.92.0': {} + '@pkgr/core@0.2.9': {} '@prettier/plugin-oxc@0.0.4': @@ -2727,49 +2826,95 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-beta.39': optional: true + '@rolldown/binding-android-arm64@1.0.0-beta.40': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-beta.39': optional: true + '@rolldown/binding-darwin-arm64@1.0.0-beta.40': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-beta.39': optional: true + '@rolldown/binding-darwin-x64@1.0.0-beta.40': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-beta.39': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-beta.40': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.39': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.40': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.39': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.40': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.39': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.40': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.39': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.40': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-beta.39': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-beta.40': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-beta.39': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-beta.40': + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-beta.39': dependencies: '@napi-rs/wasm-runtime': 1.0.5 optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-beta.40': + dependencies: + '@napi-rs/wasm-runtime': 1.0.5 + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.39': optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.40': + optional: true + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.39': optional: true + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.40': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.39': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.40': + optional: true + '@rolldown/pluginutils@1.0.0-beta.39': {} + '@rolldown/pluginutils@1.0.0-beta.40': {} + '@rollup/rollup-android-arm-eabi@4.52.0': optional: true @@ -4446,7 +4591,7 @@ snapshots: reusify@1.1.0: {} - rolldown-plugin-dts@0.16.7(rolldown@1.0.0-beta.39)(typescript@5.9.2): + rolldown-plugin-dts@0.16.7(rolldown@1.0.0-beta.40)(typescript@5.9.2): dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.4 @@ -4457,7 +4602,7 @@ snapshots: dts-resolver: 2.1.2 get-tsconfig: 4.10.1 magic-string: 0.30.19 - rolldown: 1.0.0-beta.39 + rolldown: 1.0.0-beta.40 optionalDependencies: typescript: 5.9.2 transitivePeerDependencies: @@ -4485,6 +4630,27 @@ snapshots: '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.39 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.39 + rolldown@1.0.0-beta.40: + dependencies: + '@oxc-project/types': 0.92.0 + '@rolldown/pluginutils': 1.0.0-beta.40 + ansis: 4.1.0 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.40 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.40 + '@rolldown/binding-darwin-x64': 1.0.0-beta.40 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.40 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.40 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.40 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.40 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.40 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.40 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.40 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.40 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.40 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.40 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.40 + rollup@4.52.0: dependencies: '@types/estree': 1.0.8 @@ -4612,8 +4778,8 @@ snapshots: diff: 8.0.2 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-beta.39 - rolldown-plugin-dts: 0.16.7(rolldown@1.0.0-beta.39)(typescript@5.9.2) + rolldown: 1.0.0-beta.40 + rolldown-plugin-dts: 0.16.7(rolldown@1.0.0-beta.40)(typescript@5.9.2) semver: 7.7.2 tinyexec: 1.0.1 tinyglobby: 0.2.15 diff --git a/src/index.ts b/src/index.ts index b24da76..c697c7f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { up } from 'empathic/package' import { generateTransform, MagicStringAST } from 'magic-string-ast' import { resolvePathSync } from 'mlly' import { parseAst } from 'rolldown/parseAst' +import { createFilter } from 'unplugin-utils' import { resolveOptions, type Options } from './options' import type { Plugin } from 'rolldown' @@ -13,9 +14,12 @@ export * from './options' let initted = false +const REQUIRE = `__cjs_require` + export function RequireCJS(userOptions: Options = {}): Plugin { - const { include, exclude, order, shouldTransform, builtinNodeModules } = + const { include, exclude, order, cwd, shouldTransform, builtinNodeModules } = resolveOptions(userOptions) + const filter = createFilter(include, exclude) return { name: 'rolldown-plugin-require-cjs', @@ -39,14 +43,14 @@ export function RequireCJS(userOptions: Options = {}): Plugin { ) } }, - transform: { - filter: { - id: { include, exclude }, - }, + renderChunk: { order, - async handler(code, id) { - const { body } = parseAst(code, { lang: undefined }, id) + async handler(code, { fileName }) { + if (!filter(fileName)) return + + const { body } = parseAst(code, { lang: undefined }, fileName) const s = new MagicStringAST(code) + let usingRequire = false for (const stmt of body) { if (stmt.type === 'ImportDeclaration') { @@ -58,19 +62,11 @@ export function RequireCJS(userOptions: Options = {}): Plugin { builtinNodeModules && (builtinModules.includes(source) || source.startsWith('node:')) - const resolution = isBuiltinModule - ? null // skip resolution for built-in modules - : await this.resolve(source, id) - if (resolution && resolution.external === false) { - // internal resolution, skip - // we only care about external CJS modules - continue - } - const shouldProcess = isBuiltinModule || - ((await shouldTransform?.(source, id)) ?? - (await isPureCJS(source, id))) + ((await shouldTransform?.(source, cwd)) ?? + (await isPureCJS(source, cwd))) + if (!shouldProcess) continue if (stmt.specifiers.length === 0) { @@ -80,56 +76,85 @@ export function RequireCJS(userOptions: Options = {}): Plugin { s.removeNode(stmt) } else { // require('cjs-module') - s.overwriteNode(stmt, `require(${s.sliceNode(stmt.source)});`) + s.overwriteNode(stmt, `${REQUIRE}(${JSON.stringify(source)});`) + usingRequire = true } continue } - const mapping: Record = {} + const mapping: [string, string][] = [] let namespaceId: string | undefined let defaultId: string | undefined for (const specifier of stmt.specifiers) { // namespace if (specifier.type === 'ImportNamespaceSpecifier') { // import * as name from 'cjs-module' - namespaceId = s.sliceNode(specifier.local) + namespaceId = specifier.local.name } else if (specifier.type === 'ImportSpecifier') { if (specifier.importKind === 'type') continue // named import - mapping[s.sliceNode(specifier.imported)] = s.sliceNode( - specifier.local, - ) + mapping.push([ + s.sliceNode(specifier.imported), + specifier.local.name, + ]) } else { // default import - defaultId = s.sliceNode(specifier.local) + defaultId = specifier.local.name } } - const requireCode = isBuiltinModule - ? `process.getBuiltinModule(${s.sliceNode(stmt.source)})` - : `require(${s.sliceNode(stmt.source)})` - let str = '' + let requireCode: string + if (isBuiltinModule) { + requireCode = + source === 'process' || source === 'node:process' + ? 'globalThis.process' + : `globalThis.process.getBuiltinModule(${JSON.stringify(source)})` + } else { + requireCode = `__cjs_require(${JSON.stringify(source)})` + usingRequire = true + } + + const codes: string[] = [] if (namespaceId) { defaultId ||= `_cjs_${namespaceId}_default` } if (defaultId) { // const name = require('cjs-module') - str += `const ${defaultId} = ${requireCode};` + codes.push(`const ${defaultId} = ${requireCode};`) } if (namespaceId) { // const ns = { ...default, default } - str += `const ${namespaceId} = { ...${defaultId}, default: ${defaultId} };` + codes.push( + `const ${namespaceId} = { ...${defaultId}, default: ${defaultId} };`, + ) } - if (Object.keys(mapping).length > 0) { - str += `const { ${Object.entries(mapping) - .map(([k, v]) => `${k}: ${v}`) - .join(', ')} } = ${defaultId || requireCode};` + if (mapping.length > 0) { + codes.push( + `const {\n${mapping + .map(([k, v]) => ` ${k === v ? v : `${k}: ${v}`}`) + .join(',\n')}\n} = ${defaultId || requireCode};`, + ) } - s.overwriteNode(stmt, str) + s.overwriteNode(stmt, codes.join('\n')) + } + } + + if (usingRequire) { + const preamble = builtinNodeModules + ? `const ${REQUIRE} = globalThis.process.getBuiltinModule("module").createRequire(import.meta.url);\n` + : `import { createRequire as __cjs_createRequire } from "node:module"; +const ${REQUIRE} = __cjs_createRequire(import.meta.url);\n` + + if (code[0] === '#') { + // skip shebang line + const firstNewLineIndex = code.indexOf('\n') + 1 + s.appendLeft(firstNewLineIndex, preamble) + } else { + s.prepend(preamble) } } - return generateTransform(s, id) + return generateTransform(s, fileName) }, }, } diff --git a/src/options.ts b/src/options.ts index 0307839..4a9b41d 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,4 +1,5 @@ -type FilterPattern = Array | string | RegExp +import process from 'node:process' +import type { FilterPattern } from 'unplugin-utils' type Awaitable = T | Promise @@ -21,6 +22,7 @@ export interface Options { include?: FilterPattern exclude?: FilterPattern order?: 'pre' | 'post' | undefined + cwd?: string /** * A function to determine whether a module should be transformed. * Return `true` to force transformation, `false` to skip transformation, @@ -53,6 +55,7 @@ export function resolveOptions(options: Options): OptionsResolved { include: options.include || [/\.[cm]?[jt]sx?$/], exclude: options.exclude || [/node_modules/, /\.d\.[cm]?ts$/], order: 'order' in options ? options.order : 'pre', + cwd: options.cwd || process.cwd(), shouldTransform: options.shouldTransform, builtinNodeModules: !!options.builtinNodeModules, } diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index 737201f..f771193 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -2,48 +2,46 @@ exports[`fixtures > fixtures/basic.js 1`] = ` "// basic.js -import { createRequire } from "node:module"; +import { createRequire as __cjs_createRequire } from "node:module"; +const __cjs_require = __cjs_createRequire(import.meta.url); +const _cjs_ns_default = __cjs_require("typescript"); +const ns = { ..._cjs_ns_default, default: _cjs_ns_default }; +const _default = __cjs_require("typescript"); +const { + imported: local, + named, + named: local2, + default: _default2, + default: _default3 +} = _default; +__cjs_require("force"); import "auto"; +import { resolve } from "path"; -//#region rolldown:runtime -var __require = /* @__PURE__ */ createRequire(import.meta.url); - -//#endregion -//#region tests/fixtures/basic.js -__require("typescript"); -const _cjs_ns_default = __require("typescript"); -const ns = { - ..._cjs_ns_default, - default: _cjs_ns_default -}; -const _default = __require("typescript"); -const { named, imported: local } = __require("typescript"); -const _default2 = __require("typescript"); -const { named: local2 } = _default2; -const _default3 = __require("typescript"); -const ns2 = { - ..._default3, - default: _default3 -}; -__require("force"); - -//#endregion -export { _default, _default2, _default3, local, local2, named, ns, ns2 };" +export { _default, _default2, _default3, local, local2, named, ns, ns as ns2, resolve };" `; exports[`fixtures > fixtures/node-builtins.ts 1`] = ` "// node-builtins.js -//#region tests/fixtures/node-builtins.ts -const path = process.getBuiltinModule("node:path"); -const _cjs_path2_default = process.getBuiltinModule("node:path"); -const path2 = { - ..._cjs_path2_default, - default: _cjs_path2_default -}; -const { resolve } = process.getBuiltinModule("path"); +const { + resolve +} = globalThis.process.getBuiltinModule("path"); +const _cjs_path2_default = globalThis.process.getBuiltinModule("node:path"); +const path2 = { ..._cjs_path2_default, default: _cjs_path2_default }; +const path = globalThis.process.getBuiltinModule("node:path"); +const process = globalThis.process; + +export { path, path2, process, resolve };" +`; -//#endregion -export { path, path2, resolve };" +exports[`fixtures > fixtures/shebang.js 1`] = ` +"// shebang.js +#!shebang +import { createRequire as __cjs_createRequire } from "node:module"; +const __cjs_require = __cjs_createRequire(import.meta.url); +__cjs_require("typescript"); + +export { };" `; exports[`fixtures > fixtures/type-only.ts 1`] = ` diff --git a/tests/fixtures/basic.js b/tests/fixtures/basic.js index 80293d0..c391deb 100644 --- a/tests/fixtures/basic.js +++ b/tests/fixtures/basic.js @@ -6,5 +6,16 @@ import _default2, { named as local2 } from 'typescript' import _default3, * as ns2 from 'typescript' import 'force' import 'auto' +import { resolve } from 'path' -export { ns, _default, named, local, _default2, local2, _default3, ns2 } +export { + ns, + _default, + named, + local, + _default2, + local2, + _default3, + ns2, + resolve, +} diff --git a/tests/fixtures/node-builtins.ts b/tests/fixtures/node-builtins.ts index 11a562b..a323d09 100644 --- a/tests/fixtures/node-builtins.ts +++ b/tests/fixtures/node-builtins.ts @@ -3,5 +3,6 @@ import 'node:path' import path from 'node:path' import * as path2 from 'node:path' import { resolve } from 'path' +import process from 'node:process' -export { path, path2, resolve } +export { path, path2, resolve, process } diff --git a/tests/fixtures/shebang.js b/tests/fixtures/shebang.js new file mode 100644 index 0000000..fba6b90 --- /dev/null +++ b/tests/fixtures/shebang.js @@ -0,0 +1,2 @@ +#!shebang +import typescript from 'typescript' -- 2.51.2