├── .changeset ├── README.md └── config.json ├── .github └── workflows │ ├── publish.yml │ ├── release.yml │ ├── test-rust.yml │ └── test.yml ├── .gitignore ├── .prettierignore ├── .yarn └── releases │ └── yarn-4.4.0.cjs ├── .yarnrc.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── eslint.config.js ├── package.json ├── packages ├── babel-plugin-node-cjs-interop │ ├── .prettierignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── babel-cjs.config.cjs │ ├── babel.config.cjs │ ├── cjs │ │ └── package.json │ ├── configs │ │ ├── tsconfig.base.json │ │ └── tsconfig.main.json │ ├── package.json │ ├── src │ │ ├── babel-helper-validator-option.d.ts │ │ ├── index.ts │ │ ├── options.test.ts │ │ ├── options.ts │ │ ├── package-name.test.ts │ │ └── package-name.ts │ ├── test │ │ ├── fixtures │ │ │ ├── basic │ │ │ │ ├── braces-only-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── call-replacement │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── default-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── dynamic-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── multiple-import-decls │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── multiple-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── named-default-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── named-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── namespace-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── options.json │ │ │ │ ├── side-effect-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ └── todo-forbidden-rewriting │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ ├── loose │ │ │ │ ├── braces-only-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── call-replacement │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── default-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── multiple-import-decls │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── multiple-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── named-default-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── named-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── namespace-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── options.json │ │ │ │ ├── side-effect-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ └── todo-forbidden-rewriting │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ ├── package-filtering │ │ │ │ ├── options.json │ │ │ │ └── test │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ ├── ts-twisted │ │ │ │ ├── braces-only-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── call-replacement │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── default-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── multiple-import-decls │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── multiple-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── named-default-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── named-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── namespace-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── options.json │ │ │ │ ├── side-effect-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ └── todo-forbidden-rewriting │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ ├── use-runtime │ │ │ │ ├── braces-only-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── call-replacement │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── default-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── multiple-import-decls │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── multiple-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── named-default-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── named-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── namespace-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ ├── options.json │ │ │ │ ├── side-effect-imports │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ └── todo-forbidden-rewriting │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ ├── with-react │ │ │ │ ├── jsx │ │ │ │ │ ├── input.mjs │ │ │ │ │ └── output.mjs │ │ │ │ └── options.json │ │ │ └── with-typescript │ │ │ │ ├── basic │ │ │ │ ├── input.mts │ │ │ │ └── output.mjs │ │ │ │ ├── dual-use │ │ │ │ ├── input.mts │ │ │ │ └── output.mjs │ │ │ │ └── options.json │ │ ├── helper-plugin-test-runner.d.ts │ │ └── index.test.ts │ └── tsconfig.json ├── node-cjs-interop-finder │ ├── .prettierignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── babel.config.cjs │ ├── bin │ │ └── node-cjs-interop-finder.js │ ├── configs │ │ ├── tsconfig.base.json │ │ └── tsconfig.main.json │ ├── package.json │ ├── src │ │ ├── __fixtures__ │ │ │ └── package1 │ │ │ │ ├── .gitignore │ │ │ │ ├── node_modules │ │ │ │ ├── @types │ │ │ │ │ └── dep1 │ │ │ │ │ │ ├── index.d.ts │ │ │ │ │ │ └── package.json │ │ │ │ ├── dep1 │ │ │ │ │ ├── dist │ │ │ │ │ │ └── index.js │ │ │ │ │ └── package.json │ │ │ │ ├── dep2 │ │ │ │ │ ├── dist │ │ │ │ │ │ └── index.js │ │ │ │ │ └── package.json │ │ │ │ ├── dep3 │ │ │ │ │ ├── dist │ │ │ │ │ │ ├── esm │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── index.js │ │ │ │ │ └── package.json │ │ │ │ ├── dep4 │ │ │ │ │ ├── dist │ │ │ │ │ │ └── index.js │ │ │ │ │ └── package.json │ │ │ │ └── dep5 │ │ │ │ │ ├── dist │ │ │ │ │ ├── browser.js │ │ │ │ │ └── index.js │ │ │ │ │ └── package.json │ │ │ │ └── package.json │ │ ├── classify-module.test.ts │ │ ├── classify-module.ts │ │ ├── index.ts │ │ ├── list-packages.test.ts │ │ └── list-packages.ts │ └── tsconfig.json ├── node-cjs-interop │ ├── .prettierignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── babel-cjs.config.cjs │ ├── babel.config.cjs │ ├── cjs │ │ └── package.json │ ├── configs │ │ ├── tsconfig.base.json │ │ └── tsconfig.main.json │ ├── package.json │ ├── src │ │ ├── __fixtures__ │ │ │ ├── module1.cjs │ │ │ ├── module1.d.cts │ │ │ ├── module2.cjs │ │ │ ├── module2.d.cts │ │ │ ├── module3.d.mts │ │ │ ├── module3.mjs │ │ │ ├── module4.cjs │ │ │ └── module4.d.cts │ │ ├── index.test.ts │ │ └── index.ts │ └── tsconfig.json └── swc-plugin-node-cjs-interop │ ├── .gitignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── package.json │ ├── src │ ├── lib.rs │ ├── options.rs │ └── package_name.rs │ └── tests │ ├── fixtures │ ├── basic │ │ ├── braces-only-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── call-replacement │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── default-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── dynamic-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── multiple-import-decls │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── multiple-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── named-default-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── named-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── namespace-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── side-effect-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ └── todo-forbidden-rewriting │ │ │ ├── input.mjs │ │ │ └── output.mjs │ ├── loose │ │ ├── braces-only-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── call-replacement │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── default-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── multiple-import-decls │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── multiple-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── named-default-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── named-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── namespace-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── side-effect-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ └── todo-forbidden-rewriting │ │ │ ├── input.mjs │ │ │ └── output.mjs │ ├── package-filtering │ │ └── test │ │ │ ├── input.mjs │ │ │ └── output.mjs │ ├── ts-twisted │ │ ├── braces-only-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── call-replacement │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── default-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── multiple-import-decls │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── multiple-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── named-default-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── named-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── namespace-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── side-effect-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ └── todo-forbidden-rewriting │ │ │ ├── input.mjs │ │ │ └── output.mjs │ ├── use-runtime │ │ ├── braces-only-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── call-replacement │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── default-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── multiple-import-decls │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── multiple-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── named-default-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── named-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── namespace-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ ├── side-effect-imports │ │ │ ├── input.mjs │ │ │ └── output.mjs │ │ └── todo-forbidden-rewriting │ │ │ ├── input.mjs │ │ │ └── output.mjs │ ├── with-react │ │ └── jsx │ │ │ ├── input.mjs │ │ │ └── output.mjs │ └── with-typescript │ │ ├── basic │ │ ├── input.mts │ │ └── output.mjs │ │ └── dual-use │ │ ├── input.mts │ │ └── output.mjs │ └── index.rs ├── renovate.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch" 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Manually publish to npm 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | concurrency: ${{ github.workflow }} 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | runs-on: ubuntu-latest 12 | environment: publish 13 | steps: 14 | - name: Checkout Repo 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20.x 21 | 22 | - name: set up Rust 23 | run: | 24 | rustup set profile minimal 25 | rustup install stable 26 | rustup component add rustfmt clippy 27 | rustup target add wasm32-unknown-unknown 28 | 29 | - name: Install Dependencies 30 | run: yarn 31 | 32 | - name: Publish to npm 33 | run: yarn changeset publish 34 | env: 35 | NPM_TOKEN: ${{ secrets.PUBLISH_NPM_TOKEN }} 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | environment: publish 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 20.x 23 | 24 | - name: set up Rust 25 | run: | 26 | rustup set profile minimal 27 | rustup install stable 28 | rustup component add rustfmt clippy 29 | rustup target add wasm32-unknown-unknown 30 | 31 | - name: Install Dependencies 32 | run: yarn 33 | 34 | - name: Create Release Pull Request or prepare publish 35 | id: changesets 36 | uses: changesets/action@v1 37 | with: 38 | publish: yarn changeset publish 39 | env: 40 | NPM_TOKEN: ${{ secrets.PUBLISH_NPM_TOKEN }} 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | -------------------------------------------------------------------------------- /.github/workflows/test-rust.yml: -------------------------------------------------------------------------------- 1 | name: build and test (Rust) 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | # Only when Rust-related files are updated 8 | pull_request: 9 | paths: 10 | - "Cargo.toml" 11 | - "Cargo.lock" 12 | - "**/Cargo.toml" 13 | - "**.rs" 14 | - ".github/workflows/test-rust.yml" 15 | 16 | jobs: 17 | test: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: set up Rust 22 | run: | 23 | rustup set profile minimal 24 | rustup install stable 25 | rustup component add rustfmt clippy 26 | rustup target add wasm32-unknown-unknown 27 | - run: cargo fetch --locked 28 | - run: cargo build -p swc-plugin-node-cjs-interop --target wasm32-unknown-unknown 29 | - run: cargo test --all 30 | - run: cargo clippy --all 31 | - run: cargo fmt --all --check 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: build and test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [18.x, 20.x] 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | cache: "yarn" 18 | - run: yarn install --immutable 19 | - run: yarn build 20 | - run: yarn test 21 | - run: yarn lint 22 | - run: yarn fmt:check 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | 119 | # Stores VSCode versions used for testing VSCode extensions 120 | .vscode-test 121 | 122 | # yarn v2 123 | .yarn/cache 124 | .yarn/unplugged 125 | .yarn/build-state.yml 126 | .yarn/install-state.gz 127 | .pnp.* 128 | 129 | # Rust 130 | /target 131 | 132 | # SWC 133 | .swc 134 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.yarn/releases 2 | 3 | 4 | # See https://github.com/prettier/prettier/issues/4081 for below 5 | 6 | /packages/*/cjs/dist 7 | /packages/*/dist 8 | /packages/node-cjs-interop/src/__fixtures__/module2.cjs 9 | /packages/babel-plugin-node-cjs-interop/test/fixtures/**/output.* 10 | /packages/swc-plugin-node-cjs-interop/tests/fixtures/**/output.* 11 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.4.0.cjs 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "packages/swc-plugin-node-cjs-interop", 5 | ] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Masaki Hara 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `babel-plugin-node-cjs-interop`, `swc-plugin-node-cjs-interop` and `node-cjs-interop`: fix the default import interoperability issue in Node.js 2 | 3 | ## The problem to solve 4 | 5 | Consider the following modules: 6 | 7 | ```javascript 8 | // a.js 9 | 10 | export default function greet() { 11 | console.log("Hello, world!"); 12 | } 13 | ``` 14 | 15 | ```javascript 16 | // b.js 17 | 18 | import greet from "a.js"; 19 | 20 | greet(); 21 | ``` 22 | 23 | They usually work, unless the following conditions are met: 24 | 25 | - `a.js` (the module being imported) is a **simulated ESM**. That is, the module is transpiled as a CommonJS module (by Babel or TypeScript) before execution. And, 26 | - `b.js` (the importing module) is a **native ESM**, That is, the module is run on Node.js' native ES Module support. 27 | 28 | You can reproduce the above condition by placing the following files: 29 | 30 | ```javascript 31 | // a.cjs 32 | 33 | "use strict"; 34 | 35 | Object.defineProperty(exports, "__esModule", { 36 | value: true, 37 | }); 38 | exports.default = greet; 39 | 40 | function greet() { 41 | console.log("Hello, world!"); 42 | } 43 | ``` 44 | 45 | ```javascript 46 | // b.mjs 47 | 48 | import greet from "./a.cjs"; 49 | 50 | greet(); 51 | ``` 52 | 53 | ``` 54 | $ node ./b.mjs 55 | ./b.mjs:3 56 | greet(); 57 | ^ 58 | 59 | TypeError: greet is not a function 60 | at ./b.mjs:3:1 61 | at ModuleJob.run (node:internal/modules/esm/module_job:185:25) 62 | at async Promise.all (index 0) 63 | at async ESMLoader.import (node:internal/modules/esm/loader:281:24) 64 | at async loadESM (node:internal/process/esm_loader:88:5) 65 | at async handleMainPromise (node:internal/modules/run_main:65:12) 66 | ``` 67 | 68 | ## Solution 1: Babel/SWC plugin 69 | 70 | Install the babel plugin: 71 | 72 | ``` 73 | npm install -D babel-plugin-node-cjs-interop 74 | # or: 75 | yarn add -D babel-plugin-node-cjs-interop 76 | ``` 77 | 78 | Configure it in your Babel configuration: 79 | 80 | ```javascript 81 | // .babelrc.js or babel.config.js 82 | 83 | export default { 84 | presets: [ 85 | /* ... */ 86 | ], 87 | plugins: [ 88 | // ... 89 | [ 90 | "babel-plugin-node-cjs-interop", 91 | { 92 | packages: ["styled-components", "@babel/helper-plugin-test-runner"], 93 | }, 94 | ], 95 | ], 96 | }; 97 | ``` 98 | 99 | See [the package's README](./packages/babel-plugin-node-cjs-interop/README.md) for details. 100 | 101 | ### SWC version 102 | 103 | There is the plugin for SWC too. See [the package's README](./packages/swc-plugin-node-cjs-interop/README.md) for details. 104 | 105 | ## Solution 2: manually inserting the wrapper 106 | 107 | ``` 108 | npm install -D node-cjs-interop 109 | # or: 110 | yarn add -D node-cjs-interop 111 | ``` 112 | 113 | ```javascript 114 | import styledOrig from "styled-components"; 115 | import { interopImportCJSDefault } from "node-cjs-interop"; 116 | 117 | const styled = interopImportCJSDefault(styledOrig); 118 | ``` 119 | 120 | See [the package's README](./packages/node-cjs-interop/README.md) for details. 121 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import tseslint from "typescript-eslint"; 3 | import jest from "eslint-plugin-jest"; 4 | import babelDevelopment from "@babel/eslint-plugin-development"; 5 | import prettier from "eslint-config-prettier"; 6 | import globals from "globals"; 7 | 8 | export default tseslint.config( 9 | js.configs.recommended, 10 | ...tseslint.configs.recommended, 11 | jest.configs["flat/recommended"], 12 | prettier, 13 | { 14 | files: ["**/*.@(?([mc])[jt]s|[jt]sx)"], 15 | }, 16 | { 17 | ignores: [ 18 | "**/dist/**/*", 19 | "packages/babel-plugin-node-cjs-interop/test/fixtures/**/output.*", 20 | "packages/swc-plugin-node-cjs-interop/tests/fixtures/**/output.*", 21 | ], 22 | }, 23 | { 24 | languageOptions: { 25 | globals: { 26 | console: true, 27 | }, 28 | }, 29 | }, 30 | { 31 | files: ["**/*.c[jt]s"], 32 | languageOptions: { 33 | parserOptions: { 34 | sourceType: "script", 35 | }, 36 | globals: { 37 | ...globals.commonjs, 38 | __dirname: true, 39 | __filename: true, 40 | }, 41 | }, 42 | }, 43 | { 44 | files: ["**/eslint.config.js"], 45 | languageOptions: { 46 | globals: globals.node, 47 | }, 48 | }, 49 | { 50 | files: ["**/*.cjs"], 51 | rules: { 52 | "@typescript-eslint/no-var-requires": "off", 53 | }, 54 | }, 55 | { 56 | files: ["packages/*/@(src|test)/**/*.ts"], 57 | languageOptions: { 58 | parserOptions: { 59 | projectService: true, 60 | }, 61 | }, 62 | }, 63 | ...tseslint.configs.recommendedTypeChecked.map((c) => ({ 64 | ...c, 65 | files: ["packages/*/@(src|test)/**/*.ts"], 66 | })), 67 | { 68 | plugins: { 69 | "@babel/development": babelDevelopment, 70 | }, 71 | }, 72 | { 73 | rules: { 74 | "@babel/development/no-deprecated-clone": "error", 75 | "@babel/development/no-undefined-identifier": "error", 76 | }, 77 | }, 78 | { 79 | files: ["packages/babel-plugin-node-cjs-interop/src/index.ts"], 80 | rules: { 81 | "@babel/development/plugin-name": "error", 82 | }, 83 | }, 84 | { 85 | files: [ 86 | "packages/babel-plugin-node-cjs-interop/test/fixtures/**/input.*", 87 | "packages/swc-plugin-node-cjs-interop/tests/fixtures/**/input.*", 88 | ], 89 | rules: { 90 | "@typescript-eslint/ban-ts-comment": "off", 91 | "@typescript-eslint/no-unused-vars": "off", 92 | "no-import-assign": "off", 93 | }, 94 | }, 95 | ); 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "packageManager": "yarn@4.4.0", 4 | "workspaces": [ 5 | "packages/*" 6 | ], 7 | "type": "module", 8 | "scripts": { 9 | "build": "yarn workspaces foreach -Apt run build", 10 | "changeset": "changeset", 11 | "fmt": "prettier -w .", 12 | "fmt:check": "prettier -c .", 13 | "lint": "eslint .", 14 | "test": "yarn workspaces foreach -Ap run test" 15 | }, 16 | "devDependencies": { 17 | "@babel/eslint-plugin-development": "^7.25.1", 18 | "@changesets/cli": "^2.27.12", 19 | "@eslint/js": "^9.8.0", 20 | "@types/eslint": "^9.6.1", 21 | "@types/eslint-config-prettier": "^6.11.3", 22 | "@types/eslint__js": "^8.42.3", 23 | "eslint": "^9.8.0", 24 | "eslint-config-prettier": "^9.1.0", 25 | "eslint-plugin-jest": "^28.6.0", 26 | "globals": "^15.9.0", 27 | "prettier": "^3.3.3", 28 | "typescript": "^5.5.4", 29 | "typescript-eslint": "^8.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/.prettierignore: -------------------------------------------------------------------------------- 1 | /cjs/dist 2 | /dist 3 | /test/fixtures/**/output.* 4 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-node-cjs-interop 2 | 3 | ## 0.1.9 4 | 5 | ### Patch Changes 6 | 7 | - c75999a: chore(deps): update babel monorepo 8 | 9 | ## 0.1.8 10 | 11 | ### Patch Changes 12 | 13 | - 95bef4e: Update typescript 14 | - a6b57a9: Update Babel 15 | 16 | ## 0.1.7 17 | 18 | ### Patch Changes 19 | 20 | - 37a65b0: Support dynamic imports 21 | 22 | ## 0.1.6 23 | 24 | ### Patch Changes 25 | 26 | - fb98f16: Internally improve Babel config 27 | 28 | ## 0.1.5 29 | 30 | ### Patch Changes 31 | 32 | - 972e632: Add a variant helper that wraps default one more time. 33 | 34 | The helper is called `interopImportCJSNamespaceT` and you can use it just like `interopImportCJSNamespace` 35 | (except that it assumes `loose = true` by default). 36 | 37 | To use it from the Babel/SWC plugins, use the `packagesT` option instead of `packages`. 38 | 39 | ## 0.1.4 40 | 41 | ### Patch Changes 42 | 43 | - 54b33bb: Change how dual packages are configured 44 | 45 | ## 0.1.3 46 | 47 | - Update dependencies 48 | 49 | ## 0.1.2 50 | 51 | Add `loose` option to support more libraries. 52 | 53 | ## 0.1.1 54 | 55 | Fix `Property typeName of TSTypeReference expected node to be of a type ["TSEntityName"] but instead got "MemberExpression"` when used in conjunction with TypeScript 56 | 57 | ## 0.1.0 58 | 59 | Initial release. 60 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Masaki Hara 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/README.md: -------------------------------------------------------------------------------- 1 | # `babel-plugin-node-cjs-interop`: fix the default import interoperability issue in Node.js 2 | 3 | ## The problem to solve 4 | 5 | Consider the following modules: 6 | 7 | ```javascript 8 | // a.js 9 | 10 | export default function greet() { 11 | console.log("Hello, world!"); 12 | } 13 | ``` 14 | 15 | ```javascript 16 | // b.js 17 | 18 | import greet from "a.js"; 19 | 20 | greet(); 21 | ``` 22 | 23 | They usually work, unless the following conditions are met: 24 | 25 | - `a.js` (the module being imported) is a **simulated ESM**. That is, the module is transpiled as a CommonJS module (by Babel or TypeScript) before execution. And, 26 | - `b.js` (the importing module) is a **native ESM**, That is, the module is run on Node.js' native ES Module support. 27 | 28 | You can reproduce the above condition by placing the following files: 29 | 30 | ```javascript 31 | // a.cjs 32 | 33 | "use strict"; 34 | 35 | Object.defineProperty(exports, "__esModule", { 36 | value: true, 37 | }); 38 | exports.default = greet; 39 | 40 | function greet() { 41 | console.log("Hello, world!"); 42 | } 43 | ``` 44 | 45 | ```javascript 46 | // b.mjs 47 | 48 | import greet from "./a.cjs"; 49 | 50 | greet(); 51 | ``` 52 | 53 | ``` 54 | $ node ./b.mjs 55 | ./b.mjs:3 56 | greet(); 57 | ^ 58 | 59 | TypeError: greet is not a function 60 | at ./b.mjs:3:1 61 | at ModuleJob.run (node:internal/modules/esm/module_job:185:25) 62 | at async Promise.all (index 0) 63 | at async ESMLoader.import (node:internal/modules/esm/loader:281:24) 64 | at async loadESM (node:internal/process/esm_loader:88:5) 65 | at async handleMainPromise (node:internal/modules/run_main:65:12) 66 | ``` 67 | 68 | The following packages solve the problem: 69 | 70 | - `babel-plugin-node-cjs-interop` (this package) / [`swc-plugin-node-cjs-interop`](https://npmjs.com/package/swc-plugin-node-cjs-interop): automatically inserts the compatibility wrapper. 71 | - [`node-cjs-interop`](https://npmjs.com/package/node-cjs-interop): allows manually wrapping the exported value. 72 | 73 | ## Getting started 74 | 75 | Install the babel plugin: 76 | 77 | ``` 78 | npm install -D babel-plugin-node-cjs-interop 79 | # or: 80 | yarn add -D babel-plugin-node-cjs-interop 81 | ``` 82 | 83 | Configure it in your Babel configuration: 84 | 85 | ```javascript 86 | // .babelrc.js or babel.config.js 87 | 88 | export default { 89 | presets: [ 90 | /* ... */ 91 | ], 92 | plugins: [ 93 | // ... 94 | [ 95 | "babel-plugin-node-cjs-interop", 96 | { 97 | // List the packages you're experiencing problems 98 | // importing from Node.js' native ESM. 99 | // I.e. list the packages in the simulated ESM format. 100 | packages: ["styled-components", "@babel/helper-plugin-test-runner"], 101 | }, 102 | ], 103 | ], 104 | }; 105 | ``` 106 | 107 | If you're unsure what packages to specify in the configuration, [`node-cjs-interop-finder`](https://npmjs.com/package/node-cjs-interop-finder) might be useful: 108 | 109 | ```javascript 110 | npx node-cjs-interop-finder 111 | ``` 112 | 113 | ## Caveats 114 | 115 | ### re-exports 116 | 117 | `export ... from` is not detected yet. 118 | 119 | ### Effects to tree-shaking 120 | 121 | It may negatively affect tree-shaking because the wrapper function makes it difficult to analyze unused imports. 122 | 123 | ### Observing the export's newest value 124 | 125 | In ES Modules, a module can change its exports' values after it's been loaded, and the importing module can observe the update. 126 | 127 | ```javascript 128 | export let counter = 0; 129 | export function countUp() { 130 | counter++; 131 | } 132 | ``` 133 | 134 | ```javascript 135 | import { counter, countUp } from "./counter.js"; 136 | 137 | console.log(counter); // => 0 138 | countUp(); 139 | console.log(counter); // => 1 140 | ``` 141 | 142 | Here the semantics is also not preserved when importing a simulated ESM module from a native ESM; the importing module always observes the initial value. 143 | 144 | This plugin also restores the intended behavior of updating exported variables. Therefore, in rare cases, you may find your program behaving differently regarding named imports other than `default`. 145 | 146 | ### Accesses to missing named exports 147 | 148 | When using native ESM, it is an error to import a non-existent named export: 149 | 150 | ```javascript 151 | // Error 152 | import { nonExistent } from "./a.js"; 153 | ``` 154 | 155 | With `babel-plugin-node-cjs-interop`, it silently returns `undefined`. 156 | 157 | ### False positives 158 | 159 | This plugin uses the `__esModule` flag to detect Babel's ES Modules support. Technically speaking, it may lead to false positives in a rare situation. Consider the following code: 160 | 161 | ```javascript 162 | // a.js (simulated ESM) 163 | export const val = 42; 164 | ``` 165 | 166 | ```javascript 167 | // b.js (native ESM; this plugin is not applied) 168 | export * from "a.js"; 169 | export const foo = 100; 170 | ``` 171 | 172 | ```javascript 173 | // c.js (native ESM) 174 | import { foo } from "b.js"; 175 | ``` 176 | 177 | When this plugin is applied to the last import, the import will return an unintended value. 178 | 179 | ## Options 180 | 181 | ### packages 182 | 183 | - type: `string[]` 184 | - default: `[]` 185 | 186 | List of packages to apply the transformation. If empty, no transformation is applied. 187 | 188 | Currently there is no way to apply the transformation to all imports. 189 | 190 | You can use [`node-cjs-interop-finder`](https://npmjs.com/package/node-cjs-interop-finder) to figure out packages that might suit in the option: 191 | 192 | ```javascript 193 | npx node-cjs-interop-finder 194 | ``` 195 | 196 | ### packagesT 197 | 198 | - type: `string[]` 199 | - default: `[]` 200 | 201 | Similar to `packages`, but applies the "twisted" variant instead. In this mode, the imported `default` value 202 | would be the namespace object rather than the proper default export. 203 | 204 | This is useful when you have `"module"` or `"moduleResolution"` set to `"nodenext"` or `"node16"` 205 | in your `tsconfig.json` and you need to import `default` from a "dual package" in which the 206 | type definitions are recognized in the `.cts` mode. 207 | 208 | ### loose 209 | 210 | - type: `boolean` 211 | - default: `false` 212 | 213 | Skips check of the `ns.__esModule` export. Note that it still checks 214 | for `ns.default.__esModule`. 215 | 216 | This is useful if a transpiler or a bundler generates a module 217 | which cjs-module-lexer cannot correctly parse. 218 | 219 | ### useRuntime 220 | 221 | - type: `boolean` 222 | - default: `false` 223 | 224 | Imports helpers from the `node-cjs-interop` package. You need to add `node-cjs-interop` to your package's dependency. 225 | 226 | ## How it works 227 | 228 | ### How Node.js and Babel act differently with CommonJS interoperation 229 | 230 | ES Modules and CommonJS Modules use different models for module exports: 231 | 232 | - In **ES Modules** (ESM), a module exports **multiple values**, each having a name in string. 233 | - In **CommonJS Modules** (CJS), a module exports a **single unnamed value**. 234 | 235 | For this reason, exports are mapped differently in each direction: 236 | 237 | - Named imports in ESM are mapped to exports in CJS in the following ways: 238 | - The import named `default` is mapped to the single exported value. 239 | - Other named imports are mapped to each property of the single exported value. 240 | - Imports in CJS are mapped to the exported namespaces (the record containing all exported values) from ESM. 241 | 242 | And it has a big downside: **`default` exports from ESM don't round-trip** when shipped through CJS. This is problematic for transpilers like Babel, which needs to embed the semantics of ESM into CJS. Therefore Babel implements the additional rule to the above: 243 | 244 | - If a CJS module defines `exports.__esModule` as a truthy value, ESM's importing rule is modified so that the `default` import is treated uniformly with other named imports (i.e. mapped to `exports.default` rather than `exports`). 245 | 246 | And Babel defines `exports.__esModule` when transpiling modules in ESM to CJS format. 247 | 248 | However, Node.js didn't implement the rule for `__esModule`. It's [a result of careful consideration](https://github.com/nodejs/node/pull/40892#issuecomment-974654787), but is still problematic to gradual migration from CJS to ESM. 249 | 250 | ### node-cjs-interop 251 | 252 | node-cjs-interop works around the problem by **replacing the namespace object** appropriately. This is achieved by the following function: 253 | 254 | ```javascript 255 | function interopImportCJSNamespace(ns) { 256 | return ns.__esModule && ns.default && ns.default.__esModule 257 | ? // `ns.default` likely comes from Babel's ESM emulation. 258 | // In this case `ns.default` works as the namespace object. 259 | ns.default 260 | : // Original namespace object 261 | ns; 262 | } 263 | ``` 264 | 265 | babel-node-cjs-interop first transforms named imports: 266 | 267 | ```javascript 268 | import f, { a } from "mod"; 269 | console.log({ f, a }); 270 | ``` 271 | 272 | to namespace imports: 273 | 274 | ```javascript 275 | import * as ns from "mod"; 276 | console.log({ f: ns.default, a: ns.a }); 277 | ``` 278 | 279 | and then wraps the namespace object by the aforementioned function: 280 | 281 | ```javascript 282 | import * as nsOrig from "mod"; 283 | const ns = interopImportCJSNamespace(nsOrig); 284 | console.log({ f: ns.default, a: ns.a }); 285 | ``` 286 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/babel-cjs.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("@babel/core").TransformOptions} */ 2 | module.exports = { 3 | extends: "./babel.config.cjs", 4 | presets: [["@babel/preset-env", { modules: "commonjs" }]], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/babel.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("@babel/core").TransformOptions} */ 2 | module.exports = { 3 | targets: { node: "14" }, 4 | presets: [ 5 | ["@babel/preset-env", { modules: false }], 6 | ["@babel/preset-typescript", { allowDeclareFields: true }], 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/cjs/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/configs/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["ESNext"], 5 | "useDefineForClassFields": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | 9 | /* Interop Constraints */ 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | 14 | /* Type Checking */ 15 | "strict": true, 16 | "noImplicitOverride": true, 17 | "skipLibCheck": true, 18 | 19 | /* Linting */ 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noImplicitReturns": true, 23 | "noFallthroughCasesInSwitch": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/configs/tsconfig.main.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "noEmit": false, 6 | "emitDeclarationOnly": true, 7 | "rootDir": "../src", 8 | "outDir": "../dist" 9 | }, 10 | "include": ["../src/**/*.ts"], 11 | "exclude": ["../src/**/*.test.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-node-cjs-interop", 3 | "description": "A babel plugin to fix the default import interoperability issue in Node.js", 4 | "keywords": [ 5 | "babel", 6 | "babel-plugin", 7 | "commonjs", 8 | "node", 9 | "esm", 10 | "mjs", 11 | "cjs", 12 | "default import" 13 | ], 14 | "homepage": "https://github.com/qnighy/node-cjs-interop#readme", 15 | "bugs": { 16 | "url": "https://github.com/qnighy/node-cjs-interop/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/qnighy/node-cjs-interop.git", 21 | "directory": "packages/babel-plugin-node-cjs-interop" 22 | }, 23 | "license": "MIT", 24 | "author": "Masaki Hara ", 25 | "version": "0.1.9", 26 | "type": "module", 27 | "main": "./cjs/dist/index.js", 28 | "types": "./dist/index.d.ts", 29 | "exports": { 30 | "require": "./cjs/dist/index.js", 31 | "default": "./dist/index.js" 32 | }, 33 | "files": [ 34 | "cjs/**/*", 35 | "dist/**/*" 36 | ], 37 | "scripts": { 38 | "build": "$npm_execpath run build:tsc && $npm_execpath run build:babel:esm && $npm_execpath run build:babel:cjs", 39 | "build:babel:cjs": "babel -d cjs/dist src -x '.ts' --ignore 'src/**/*.test.ts' --ignore '**/*.d.ts' --config-file ./babel-cjs.config.cjs", 40 | "build:babel:esm": "babel -d dist src -x '.ts' --ignore 'src/**/*.test.ts' --ignore '**/*.d.ts'", 41 | "build:tsc": "tsc --build", 42 | "fmt": "prettier -w .", 43 | "fmt:check": "prettier -c .", 44 | "jest": "NODE_OPTIONS='--experimental-vm-modules' jest", 45 | "lint": "eslint .", 46 | "prepack": "$npm_execpath run build", 47 | "test": "yarn build:babel:cjs && yarn jest" 48 | }, 49 | "dependencies": { 50 | "@babel/helper-plugin-utils": "^7.22.5", 51 | "@babel/helper-validator-option": "^7.22.15" 52 | }, 53 | "devDependencies": { 54 | "@babel/cli": "^7.24.8", 55 | "@babel/core": "^7.25.2", 56 | "@babel/helper-plugin-test-runner": "^7.24.7", 57 | "@babel/preset-env": "^7.25.3", 58 | "@babel/preset-typescript": "^7.24.7", 59 | "@babel/types": "^7.25.2", 60 | "@jest/globals": "^29.7.0", 61 | "@types/babel__core": "^7.20.5", 62 | "@types/babel__helper-plugin-utils": "^7.10.3", 63 | "babel-jest": "^29.7.0", 64 | "eslint": "^9.8.0", 65 | "jest": "^29.7.0", 66 | "jest-resolve": "^29.7.0", 67 | "jest-ts-webcompat-resolver": "^1.0.0", 68 | "prettier": "^3.3.3", 69 | "typescript": "^5.5.4" 70 | }, 71 | "jest": { 72 | "extensionsToTreatAsEsm": [ 73 | ".ts", 74 | ".mts" 75 | ], 76 | "resolver": "jest-ts-webcompat-resolver" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/src/babel-helper-validator-option.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@babel/helper-validator-option" { 2 | export class OptionValidator { 3 | descriptor: string; 4 | constructor(descriptor: string); 5 | 6 | /** 7 | * Validate if the given `options` follow the name of keys defined in the `TopLevelOptionShape` 8 | * 9 | * @param options 10 | * @param TopLevelOptionShape 11 | * An object with all the valid key names that `options` should be allowed to have 12 | * The property values of `TopLevelOptionShape` can be arbitrary 13 | */ 14 | validateTopLevelOptions( 15 | options: T, 16 | TopLevelOptionShape: U, 17 | ): asserts options is T & 18 | Record & { 19 | [K in keyof T]?: K extends keyof U ? unknown : undefined; 20 | }; 21 | 22 | validateBooleanOption( 23 | name: string, 24 | value?: boolean, 25 | defaultValue?: T, 26 | ): boolean | T; 27 | 28 | validateStringOption( 29 | name: string, 30 | value?: string, 31 | defaultValue?: T, 32 | ): string | T; 33 | 34 | /** 35 | * A helper interface copied from the `invariant` npm package. 36 | * It throws given `message` when `condition` is not met 37 | */ 38 | invariant(condition: boolean, message: string): void; 39 | 40 | formatMessage(message: string): string; 41 | } 42 | 43 | /** 44 | * Given a string `str` and an array of candidates `arr`, 45 | * return the first of elements in candidates that has minimal 46 | * Levenshtein distance with `str`. 47 | */ 48 | export function findSuggestion(str: string, arr: readonly string[]): string; 49 | } 50 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/src/index.ts: -------------------------------------------------------------------------------- 1 | import type * as Babel from "@babel/core"; 2 | import type { 3 | CallExpression, 4 | Expression, 5 | Identifier, 6 | ImportExpression, 7 | JSXIdentifier, 8 | JSXMemberExpression, 9 | Node, 10 | Program, 11 | } from "@babel/types"; 12 | import { declare } from "@babel/helper-plugin-utils"; 13 | import { getPackageName } from "./package-name.js"; 14 | import { validateOptions } from "./options.js"; 15 | import type { Options } from "./options.js"; 16 | 17 | export type { Options } from "./options.js"; 18 | 19 | const statePrefix = "import-interop"; 20 | 21 | export default declare((api, options) => { 22 | api.assertVersion("^7.0.0 || ^8.0.0"); 23 | const { types: t } = api; 24 | 25 | validateOptions(options); 26 | 27 | return { 28 | name: "babel-plugin-node-cjs-interop", 29 | visitor: { 30 | ImportDeclaration(path, state) { 31 | const pkgType = applicableSourceType(path.node.source.value, options); 32 | if (!pkgType) return; 33 | // Should have been removed by transform-typescript. Just in case. 34 | if (path.node.importKind === "type") return; 35 | if (isCjsAnnotated(path.node)) return; 36 | if (path.node.specifiers.length === 0) return; 37 | 38 | const replaceMap = new Map(); 39 | 40 | const existingNsImport = path.node.specifiers.find( 41 | (specifier) => specifier.type === "ImportNamespaceSpecifier", 42 | )?.local; 43 | 44 | const nsImport = 45 | existingNsImport ?? path.scope.generateUidIdentifier("ns"); 46 | 47 | for (const specifier of path.node.specifiers) { 48 | let expr: Expression; 49 | if (specifier.type === "ImportDefaultSpecifier") { 50 | // ns.defalut 51 | expr = t.memberExpression( 52 | t.cloneNode(nsImport), 53 | t.identifier("default"), 54 | ); 55 | } else if (specifier.type === "ImportSpecifier") { 56 | if (specifier.imported.type === "StringLiteral") { 57 | // ns["named"] 58 | expr = t.memberExpression( 59 | t.cloneNode(nsImport), 60 | t.cloneNode(specifier.imported), 61 | true, 62 | ); 63 | } else { 64 | // ns.named 65 | expr = t.memberExpression( 66 | t.cloneNode(nsImport), 67 | t.cloneNode(specifier.imported), 68 | ); 69 | } 70 | } else if (specifier.type === "ImportNamespaceSpecifier") { 71 | // No need to replace 72 | continue; 73 | } else { 74 | const { type }: never = specifier; 75 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions 76 | throw new Error(`Unknown specifier type: ${type}`); 77 | } 78 | replaceMap.set(specifier.local.name, { scope: path.scope, expr }); 79 | } 80 | 81 | const importHelper = getImportHelper( 82 | t, 83 | path, 84 | state, 85 | pkgType, 86 | options.useRuntime ?? false, 87 | ); 88 | 89 | // import ... from "source"; 90 | // -> 91 | // import * as moduleOrig from "source"; 92 | // const module = _interopImportCJSNamespace(moduleOrig); 93 | const importOriginalName = path.scope.generateUidIdentifier("nsOrig"); 94 | const program = path.scope.getProgramParent() 95 | .path as Babel.NodePath; 96 | program.unshiftContainer( 97 | "body", 98 | t.variableDeclaration("const", [ 99 | t.variableDeclarator( 100 | nsImport, 101 | t.callExpression(t.cloneNode(importHelper), [ 102 | t.cloneNode(importOriginalName), 103 | ...(pkgType !== "ts-twisted" && options.loose 104 | ? [t.booleanLiteral(true)] 105 | : []), 106 | ]), 107 | ), 108 | ]), 109 | ); 110 | 111 | const newImport = t.cloneNode(path.node); 112 | newImport.specifiers = [t.importNamespaceSpecifier(importOriginalName)]; 113 | path.replaceWith(newImport); 114 | annotateAsCjs(t, path.node); 115 | 116 | if (replaceMap.size > 0) { 117 | path.parentPath.traverse({ 118 | Identifier(path) { 119 | const replacement = replaceMap.get(path.node.name); 120 | if (!replacement) return; 121 | 122 | if (!isReferencedValueIdentifier(path)) return; 123 | 124 | const binding = path.scope.getBinding(path.node.name); 125 | if (!binding || binding.scope === replacement.scope) { 126 | replaceIdentifier(t, path, t.cloneNode(replacement.expr)); 127 | } 128 | }, 129 | JSXIdentifier(path) { 130 | const replacement = replaceMap.get(path.node.name); 131 | if (!replacement) return; 132 | 133 | if (!isReferencedValueIdentifier(path)) return; 134 | 135 | const binding = path.scope.getBinding(path.node.name); 136 | if (!binding || binding.scope === replacement.scope) { 137 | replaceIdentifier(t, path, t.cloneNode(replacement.expr)); 138 | } 139 | }, 140 | }); 141 | } 142 | }, 143 | ExportNamedDeclaration(path) { 144 | if (!path.node.source) return; 145 | const pkgType = applicableSourceType(path.node.source.value, options); 146 | if (!pkgType) return; 147 | // Should have been removed by transform-typescript. Just in case. 148 | if (path.node.exportKind === "type") return; 149 | if (isCjsAnnotated(path.node)) return; 150 | if (path.node.specifiers.length === 0) return; 151 | 152 | throw path.buildCodeFrameError( 153 | "babel-plugin-node-cjs-interop: cannot transform export declarations", 154 | ); 155 | }, 156 | ExportAllDeclaration(path) { 157 | if (!path.node.source) return; 158 | const pkgType = applicableSourceType(path.node.source.value, options); 159 | if (!pkgType) return; 160 | // Should have been removed by transform-typescript. Just in case. 161 | if (path.node.exportKind === "type") return; 162 | if (isCjsAnnotated(path.node)) return; 163 | 164 | throw path.buildCodeFrameError( 165 | "babel-plugin-node-cjs-interop: cannot transform export declarations", 166 | ); 167 | }, 168 | // createImportExpressions === false; Babel 7 default 169 | CallExpression(path, state) { 170 | if (path.node.callee.type === "Import") { 171 | processImportExpression( 172 | path, 173 | path.get("arguments")[0] as Babel.NodePath, 174 | state, 175 | ); 176 | } 177 | }, 178 | // createImportExpressions === true; Babel 8 default 179 | ImportExpression(path, state) { 180 | processImportExpression(path, path.get("source"), state); 181 | }, 182 | }, 183 | }; 184 | 185 | function processImportExpression( 186 | path: Babel.NodePath, 187 | sourcePath: Babel.NodePath, 188 | state: Babel.PluginPass, 189 | ) { 190 | if (sourcePath.node.type !== "StringLiteral") return; 191 | const pkgType = applicableSourceType(sourcePath.node.value, options); 192 | if (!pkgType) return; 193 | if (isCjsAnnotated(path.node)) return; 194 | 195 | const awaitPath = path.parentPath; 196 | if ( 197 | awaitPath.isAwaitExpression() && 198 | awaitPath.node.argument === path.node 199 | ) { 200 | if (isCjsAnnotated(awaitPath.node)) return; 201 | 202 | const importHelper = getImportHelper( 203 | t, 204 | path, 205 | state, 206 | pkgType, 207 | options.useRuntime ?? false, 208 | ); 209 | 210 | const origNode = awaitPath.node; 211 | // await import("source") 212 | // -> _interopImportCJSNamespace(await import("source")) 213 | awaitPath.replaceWith( 214 | t.callExpression(t.cloneNode(importHelper), [ 215 | awaitPath.node, 216 | ...(pkgType !== "ts-twisted" && options.loose 217 | ? [t.booleanLiteral(true)] 218 | : []), 219 | ]), 220 | ); 221 | annotateAsCjs(t, origNode); 222 | return; 223 | } else { 224 | const importHelper = getImportHelper( 225 | t, 226 | path, 227 | state, 228 | pkgType, 229 | options.useRuntime ?? false, 230 | ); 231 | 232 | const origNode = path.node; 233 | // import("source") 234 | // -> import("source").then((ns) => _interopImportCJSNamespace(ns)) 235 | // NOTE: strictly speaking, it does not preserve scheduling semantics of the Promise; 236 | // the transform delays the execution by one cycle of microtask queue. 237 | // We could add other heuristics for cases like import("source").then(...) 238 | // but ultimately we cannot do so when we are not sure where the Promise goes to. 239 | // As the effect is fairly minor (do not depend on microtask cycle count!), 240 | // we just leave it as is for now. 241 | path.replaceWith( 242 | t.callExpression( 243 | t.memberExpression( 244 | // Explicit parentheses to get room for the #__CJS__ comment 245 | t.parenthesizedExpression(path.node), 246 | t.identifier("then"), 247 | false, 248 | false, 249 | ), 250 | [ 251 | t.arrowFunctionExpression( 252 | [t.identifier("ns")], 253 | t.callExpression(t.cloneNode(importHelper), [ 254 | t.identifier("ns"), 255 | ...(pkgType !== "ts-twisted" && options.loose 256 | ? [t.booleanLiteral(true)] 257 | : []), 258 | ]), 259 | ), 260 | ], 261 | ), 262 | ); 263 | annotateAsCjs(t, origNode); 264 | return; 265 | } 266 | } 267 | }); 268 | 269 | type Replacement = { 270 | scope: Babel.NodePath["scope"]; 271 | expr: Expression; 272 | }; 273 | 274 | function getImportHelper( 275 | t: typeof Babel.types, 276 | path: Babel.NodePath, 277 | state: Babel.PluginPass, 278 | pkgType: PackageType, 279 | useRuntime: boolean, 280 | ): Identifier { 281 | const key = 282 | pkgType === "ts-twisted" 283 | ? `${statePrefix}/importHelperT` 284 | : `${statePrefix}/importHelper`; 285 | let helper = state.get(key) as Identifier | undefined; 286 | if (helper) return helper; 287 | 288 | const helperName = 289 | pkgType === "ts-twisted" 290 | ? "interopImportCJSNamespaceT" 291 | : "interopImportCJSNamespace"; 292 | const scope = path.scope.getProgramParent(); 293 | helper = scope.generateUidIdentifier(helperName); 294 | 295 | if (useRuntime) { 296 | // import { 297 | // interopImportCJSNamespace as _interopImportCJSNamespace, 298 | // } from "node-cjs-interop"; 299 | (scope.path as Babel.NodePath).unshiftContainer( 300 | "body", 301 | t.importDeclaration( 302 | [t.importSpecifier(t.cloneNode(helper), t.identifier(helperName))], 303 | t.stringLiteral("node-cjs-interop"), 304 | ), 305 | ); 306 | state.set(key, helper); 307 | return helper; 308 | } 309 | 310 | const ns = t.identifier("ns"); 311 | const loose = t.identifier("loose"); 312 | const nsDefault = t.memberExpression( 313 | t.cloneNode(ns), 314 | t.identifier("default"), 315 | ); 316 | 317 | if (pkgType === "ts-twisted") { 318 | // function interopImportCJSNamespaceT(ns) { 319 | // return ns.default && ns.default.__esModule ? ns : { 320 | // ...ns, 321 | // default: ns 322 | // }; 323 | // } 324 | (scope.path as Babel.NodePath).unshiftContainer( 325 | "body", 326 | t.functionDeclaration( 327 | t.cloneNode(helper), 328 | [t.cloneNode(ns)], 329 | t.blockStatement([ 330 | t.returnStatement( 331 | t.conditionalExpression( 332 | t.logicalExpression( 333 | "&&", 334 | t.cloneNode(nsDefault), 335 | t.memberExpression( 336 | t.cloneNode(nsDefault), 337 | t.identifier("__esModule"), 338 | ), 339 | ), 340 | t.cloneNode(ns), 341 | t.objectExpression([ 342 | t.spreadElement(t.cloneNode(ns)), 343 | t.objectProperty( 344 | t.identifier("default"), 345 | t.cloneNode(ns), 346 | false, 347 | false, 348 | ), 349 | ]), 350 | ), 351 | ), 352 | ]), 353 | ), 354 | ); 355 | } else { 356 | // function interopImportCJSNamespace(ns, loose) { 357 | // return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 358 | // } 359 | (scope.path as Babel.NodePath).unshiftContainer( 360 | "body", 361 | t.functionDeclaration( 362 | t.cloneNode(helper), 363 | [t.cloneNode(ns), t.cloneNode(loose)], 364 | t.blockStatement([ 365 | t.returnStatement( 366 | t.conditionalExpression( 367 | t.logicalExpression( 368 | "&&", 369 | t.logicalExpression( 370 | "&&", 371 | t.logicalExpression( 372 | "||", 373 | t.cloneNode(loose), 374 | t.memberExpression( 375 | t.cloneNode(ns), 376 | t.identifier("__esModule"), 377 | ), 378 | ), 379 | t.cloneNode(nsDefault), 380 | ), 381 | t.memberExpression( 382 | t.cloneNode(nsDefault), 383 | t.identifier("__esModule"), 384 | ), 385 | ), 386 | t.cloneNode(nsDefault), 387 | t.cloneNode(ns), 388 | ), 389 | ), 390 | ]), 391 | ), 392 | ); 393 | } 394 | state.set(key, helper); 395 | return helper; 396 | } 397 | 398 | const CJS_ANNOTATION = "#__CJS__"; 399 | 400 | const isCjsAnnotated = ({ leadingComments }: Node): boolean => 401 | !!leadingComments && 402 | leadingComments.some((comment) => /#__CJS__/.test(comment.value)); 403 | 404 | function annotateAsCjs(t: typeof Babel.types, node: Node): void { 405 | if (isCjsAnnotated(node)) { 406 | return; 407 | } 408 | t.addComment(node, "leading", CJS_ANNOTATION); 409 | } 410 | 411 | type PackageType = "normal" | "ts-twisted"; 412 | function applicableSourceType( 413 | source: string, 414 | options: Options, 415 | ): PackageType | undefined { 416 | const { packages = [], packagesT = [] } = options; 417 | 418 | const sourcePackage = getPackageName(source); 419 | if (sourcePackage === undefined) return undefined; 420 | 421 | if (packages.includes(sourcePackage)) { 422 | return "normal"; 423 | } else if (packagesT.includes(sourcePackage)) { 424 | return "ts-twisted"; 425 | } 426 | return undefined; 427 | } 428 | 429 | function replaceIdentifier( 430 | t: typeof Babel.types, 431 | path: Babel.NodePath | Babel.NodePath, 432 | replacement: Expression, 433 | ) { 434 | if (path.isJSXIdentifier()) { 435 | path.replaceWith(toJSXReference(t, replacement)); 436 | return; 437 | } 438 | 439 | if ( 440 | (path.parentPath.isCallExpression({ callee: path.node }) || 441 | path.parentPath.isOptionalCallExpression({ callee: path.node }) || 442 | path.parentPath.isTaggedTemplateExpression({ tag: path.node })) && 443 | t.isMemberExpression(replacement) 444 | ) { 445 | path.replaceWith(t.sequenceExpression([t.numericLiteral(0), replacement])); 446 | } else { 447 | path.replaceWith(replacement); 448 | } 449 | } 450 | 451 | function toJSXReference( 452 | t: typeof Babel.types, 453 | expr: Expression, 454 | ): JSXIdentifier | JSXMemberExpression { 455 | if (t.isIdentifier(expr)) { 456 | return t.inherits(t.jsxIdentifier(expr.name), expr); 457 | } else if (t.isMemberExpression(expr)) { 458 | if (!t.isIdentifier(expr.property) || expr.computed) 459 | throw new Error("Not an identifier reference"); 460 | const property = t.inherits( 461 | t.jsxIdentifier(expr.property.name), 462 | expr.property, 463 | ); 464 | return t.inherits( 465 | t.jsxMemberExpression(toJSXReference(t, expr.object), property), 466 | expr, 467 | ); 468 | } else { 469 | throw new Error("Not a chain of identifiers"); 470 | } 471 | } 472 | 473 | function isReferencedValueIdentifier( 474 | path: Babel.NodePath, 475 | ): path is Babel.NodePath & Babel.NodePath { 476 | if (!path.isReferencedIdentifier()) return false; 477 | 478 | const { node, parent } = path; 479 | 480 | switch (parent.type) { 481 | // Most usages in types fall into this category. 482 | case "TSTypeReference": 483 | return false; 484 | 485 | // no: type T = typeof NODE; 486 | case "TSTypeQuery": 487 | return false; 488 | 489 | // no: type T = NODE.T; 490 | // no: type T = T.NODE; 491 | case "TSQualifiedName": 492 | return false; 493 | 494 | // no: type T = (NODE) => void 495 | // no: type T = new (NODE) => void 496 | // no: type T = { (NODE): void; } 497 | // no: type T = { new (NODE): void; } 498 | case "TSFunctionType": 499 | case "TSConstructorType": 500 | case "TSCallSignatureDeclaration": 501 | case "TSConstructSignatureDeclaration": 502 | return false; 503 | 504 | // no: type T = { NODE(): void; }; 505 | // perhaps: type T = { [NODE](): void; }; 506 | // perhaps: type T = { foo(NODE): void; }; 507 | case "TSMethodSignature": 508 | return false; 509 | 510 | // no: type T = { NODE }; 511 | // perhaps: type T = { [NODE] }; 512 | case "TSPropertySignature": 513 | return false; 514 | 515 | // Equivalent to FunctionDeclaration 516 | // no: declare function NODE(); 517 | // no: declare function foo(NODE); 518 | case "TSDeclareFunction": 519 | return false; 520 | 521 | // Equivalent to ClassMethod 522 | // no: class C { NODE(); } 523 | // perhaps: class C { [NODE](); } 524 | // no: class C { foo(NODE); } 525 | case "TSDeclareMethod": 526 | return false; 527 | 528 | // no: function foo(x): NODE is T; 529 | case "TSTypePredicate": 530 | return false; 531 | 532 | // no: class C { constructor(readonly NODE) {} } 533 | case "TSParameterProperty": 534 | return false; 535 | 536 | // no: type T = [NODE: T]; 537 | case "TSNamedTupleMember": 538 | return false; 539 | 540 | // no: interface I extends NODE {} 541 | case "TSExpressionWithTypeArguments": 542 | return false; 543 | 544 | // no: import NODE = require("foo"); 545 | case "TSImportEqualsDeclaration": 546 | return false; 547 | 548 | // no: export as namespace NODE; 549 | case "TSNamespaceExportDeclaration": 550 | return false; 551 | 552 | // no: type T = { [NODE: string]: T }; 553 | case "TSIndexSignature": 554 | return false; 555 | 556 | // no: type NODE = {}; 557 | case "TSTypeAliasDeclaration": 558 | return false; 559 | 560 | // no: interface NODE {} 561 | case "TSInterfaceDeclaration": 562 | return false; 563 | 564 | // no: namespace NODE {} 565 | case "TSModuleDeclaration": 566 | return false; 567 | 568 | // no: enum NODE {} 569 | case "TSEnumDeclaration": 570 | return false; 571 | 572 | // no: enum E { NODE } 573 | // yes: enum E { A = NODE } 574 | case "TSEnumMember": 575 | return parent.initializer === node; 576 | } 577 | return true; 578 | } 579 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/src/options.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "@jest/globals"; 2 | import { validateOptions } from "./options.js"; 3 | 4 | describe("validateOptions", () => { 5 | it("allows empty options", () => { 6 | const options = {}; 7 | expect(() => validateOptions(options)).not.toThrow(); 8 | }); 9 | 10 | it("forbids unknown option keys", () => { 11 | const options = { foo: 42 }; 12 | expect(() => validateOptions(options)).toThrow( 13 | "babel-plugin-node-cjs-interop: 'foo' is not a valid top-level option.\n- Did you mean 'loose'?", 14 | ); 15 | }); 16 | 17 | it("allows packages as an empty array", () => { 18 | const options = { packages: [] }; 19 | expect(() => validateOptions(options)).not.toThrow(); 20 | }); 21 | 22 | it("allows packages as an array of strings", () => { 23 | const options = { packages: ["foo", "bar"] }; 24 | expect(() => validateOptions(options)).not.toThrow(); 25 | }); 26 | 27 | it("forbids packages with non-string elements", () => { 28 | const options = { packages: ["foo", 42] }; 29 | expect(() => validateOptions(options)).toThrow( 30 | "babel-plugin-node-cjs-interop: packages should be an array of strings", 31 | ); 32 | }); 33 | 34 | it("forbids non-package names in packages option", () => { 35 | const options = { packages: ["foo/bar", "foo"] }; 36 | expect(() => validateOptions(options)).toThrow( 37 | "babel-plugin-node-cjs-interop: not a package name: foo/bar", 38 | ); 39 | }); 40 | 41 | it("allows loose as false", () => { 42 | const options = { loose: false }; 43 | expect(() => validateOptions(options)).not.toThrow(); 44 | }); 45 | 46 | it("allows loose as true", () => { 47 | const options = { loose: true }; 48 | expect(() => validateOptions(options)).not.toThrow(); 49 | }); 50 | 51 | it("forbids invalid loose value", () => { 52 | const options = { loose: 10 }; 53 | expect(() => validateOptions(options)).toThrow( 54 | "babel-plugin-node-cjs-interop: 'loose' option must be a boolean.", 55 | ); 56 | }); 57 | 58 | it("allows useRuntime as false", () => { 59 | const options = { useRuntime: false }; 60 | expect(() => validateOptions(options)).not.toThrow(); 61 | }); 62 | 63 | it("allows useRuntime as true", () => { 64 | const options = { useRuntime: true }; 65 | expect(() => validateOptions(options)).not.toThrow(); 66 | }); 67 | 68 | it("forbids invalid useRuntime value", () => { 69 | const options = { useRuntime: 10 }; 70 | expect(() => validateOptions(options)).toThrow( 71 | "babel-plugin-node-cjs-interop: 'useRuntime' option must be a boolean.", 72 | ); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/src/options.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { OptionValidator } from "@babel/helper-validator-option"; 3 | import { isPackageName } from "./package-name.js"; 4 | 5 | const v: OptionValidator = new OptionValidator("babel-plugin-node-cjs-interop"); 6 | 7 | export type Options = { 8 | packages?: string[] | undefined; 9 | packagesT?: string[] | undefined; 10 | loose?: boolean; 11 | useRuntime?: boolean; 12 | }; 13 | 14 | const optionShape = { 15 | packages: "string[]", 16 | packagesT: "string[]", 17 | loose: "boolean", 18 | useRuntime: "boolean", 19 | } as const; 20 | 21 | export function validateOptions(options: object): asserts options is Options { 22 | v.validateTopLevelOptions(options, optionShape); 23 | validatePackages(options.packages); 24 | validatePackages(options.packagesT); 25 | v.validateBooleanOption("loose", options.loose as boolean | undefined); 26 | v.validateBooleanOption( 27 | "useRuntime", 28 | options.useRuntime as boolean | undefined, 29 | ); 30 | validatePackagesSemantics(options.packages ?? []); 31 | validatePackagesSemantics(options.packagesT ?? []); 32 | } 33 | 34 | function validatePackages( 35 | packages: unknown, 36 | ): asserts packages is string[] | undefined { 37 | if (packages === undefined) return; 38 | if (!Array.isArray(packages)) { 39 | throw new Error( 40 | "babel-plugin-node-cjs-interop: packages should be an array", 41 | ); 42 | } 43 | 44 | if (!packages.every((p): p is string => typeof p === "string")) { 45 | throw new Error( 46 | "babel-plugin-node-cjs-interop: packages should be an array of strings", 47 | ); 48 | } 49 | } 50 | 51 | function validatePackagesSemantics(packages: string[]) { 52 | for (const name of packages) { 53 | if (!isPackageName(name)) { 54 | throw new Error( 55 | `babel-plugin-node-cjs-interop: not a package name: ${name}`, 56 | ); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/src/package-name.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "@jest/globals"; 2 | import { getPackageName } from "./package-name.js"; 3 | 4 | describe("getPackageName", () => { 5 | it("returns undefined for empty string", () => { 6 | expect(getPackageName("")).toBe(undefined); 7 | }); 8 | 9 | it("returns undefined for absolute paths", () => { 10 | expect(getPackageName("/")).toBe(undefined); 11 | expect(getPackageName("/foo")).toBe(undefined); 12 | expect(getPackageName("/foo/bar")).toBe(undefined); 13 | }); 14 | 15 | it("returns undefined for relative paths", () => { 16 | expect(getPackageName(".")).toBe(undefined); 17 | expect(getPackageName("./")).toBe(undefined); 18 | expect(getPackageName("./foo")).toBe(undefined); 19 | expect(getPackageName("./foo/bar")).toBe(undefined); 20 | expect(getPackageName("../foo")).toBe(undefined); 21 | expect(getPackageName("../foo/bar")).toBe(undefined); 22 | }); 23 | 24 | it("returns first segment for unscoped packages", () => { 25 | expect(getPackageName("foo")).toBe("foo"); 26 | expect(getPackageName("foo-bar")).toBe("foo-bar"); 27 | expect(getPackageName("foo.bar")).toBe("foo.bar"); 28 | }); 29 | 30 | it("returns first segment for modules from unscoped packages", () => { 31 | expect(getPackageName("foo/index")).toBe("foo"); 32 | expect(getPackageName("foo-bar/server")).toBe("foo-bar"); 33 | expect(getPackageName("foo.bar/dist/index.js")).toBe("foo.bar"); 34 | }); 35 | 36 | it("returns first two segments for scoped packages", () => { 37 | expect(getPackageName("@test/foo")).toBe("@test/foo"); 38 | expect(getPackageName("@test/foo-bar")).toBe("@test/foo-bar"); 39 | expect(getPackageName("@test/foo.bar")).toBe("@test/foo.bar"); 40 | }); 41 | 42 | it("returns first two segments for modules from scoped packages", () => { 43 | expect(getPackageName("@test/foo/index")).toBe("@test/foo"); 44 | expect(getPackageName("@test/foo-bar/server")).toBe("@test/foo-bar"); 45 | expect(getPackageName("@test/foo.bar/dist/index.js")).toBe("@test/foo.bar"); 46 | }); 47 | 48 | it("returns undefined for incomplete scoped packages", () => { 49 | expect(getPackageName("@test")).toBe(undefined); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/src/package-name.ts: -------------------------------------------------------------------------------- 1 | export function getPackageName(modulePath: string): string | undefined { 2 | if (modulePath.length === 0) return undefined; 3 | 4 | const first = modulePath.charAt(0); 5 | if (first === "/" || first === ".") { 6 | return undefined; 7 | } 8 | 9 | if (first === "@") { 10 | const index1 = modulePath.indexOf("/"); 11 | if (index1 === -1) return undefined; 12 | 13 | const index2 = modulePath.indexOf("/", index1 + 1); 14 | if (index2 === -1) { 15 | return modulePath; 16 | } else { 17 | return modulePath.substring(0, index2); 18 | } 19 | } else { 20 | const index = modulePath.indexOf("/"); 21 | if (index === -1) { 22 | return modulePath; 23 | } else { 24 | return modulePath.substring(0, index); 25 | } 26 | } 27 | } 28 | 29 | export function isPackageName(name: string): boolean { 30 | return name === getPackageName(name); 31 | } 32 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/braces-only-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import {} from "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/braces-only-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/call-replacement/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | 3 | console.log(f()); 4 | console.log(f.g()); 5 | console.log(f?.()); 6 | console.log(f`foo`); 7 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/call-replacement/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log((0, _ns.f)()); 8 | console.log(_ns.f.g()); 9 | console.log((0, _ns.f)?.()); 10 | console.log((0, _ns.f)`foo`); 11 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log(_ns.default); 8 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/dynamic-imports/input.mjs: -------------------------------------------------------------------------------- 1 | const M = await import("mod"); 2 | console.log(M); 3 | 4 | import("mod").then((M2) => { 5 | console.log(M2); 6 | }); 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/dynamic-imports/output.mjs: -------------------------------------------------------------------------------- 1 | function _interopImportCJSNamespace(ns, loose) { 2 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 3 | } 4 | const M = _interopImportCJSNamespace( /*#__CJS__*/await import("mod")); 5 | console.log(M); 6 | ( /*#__CJS__*/import("mod")).then(ns => _interopImportCJSNamespace(ns)).then(M2 => { 7 | console.log(M2); 8 | }); 9 | export {}; 10 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/multiple-import-decls/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod1"; 2 | import { a as x, b } from "mod2"; 3 | import "mod1"; 4 | import {} from "mod1"; 5 | import { default as f2 } from "mod1"; 6 | import * as ns2 from "mod2"; 7 | import * as ns2b from "mod2"; 8 | 9 | console.log({ f, x, b, f2, ns2, ns2b }); 10 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/multiple-import-decls/output.mjs: -------------------------------------------------------------------------------- 1 | const ns2b = _interopImportCJSNamespace(_nsOrig5); 2 | const ns2 = _interopImportCJSNamespace(_nsOrig4); 3 | const _ns3 = _interopImportCJSNamespace(_nsOrig3); 4 | const _ns2 = _interopImportCJSNamespace(_nsOrig2); 5 | const _ns = _interopImportCJSNamespace(_nsOrig); 6 | function _interopImportCJSNamespace(ns, loose) { 7 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 8 | } 9 | /*#__CJS__*/ 10 | import * as _nsOrig from "mod1"; 11 | /*#__CJS__*/ 12 | import * as _nsOrig2 from "mod2"; 13 | import "mod1"; 14 | import "mod1"; 15 | /*#__CJS__*/ 16 | import * as _nsOrig3 from "mod1"; 17 | /*#__CJS__*/ 18 | import * as _nsOrig4 from "mod2"; 19 | /*#__CJS__*/ 20 | import * as _nsOrig5 from "mod2"; 21 | console.log({ 22 | f: _ns.default, 23 | x: _ns2.a, 24 | b: _ns2.b, 25 | f2: _ns3.default, 26 | ns2, 27 | ns2b 28 | }); 29 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/multiple-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f, { a as x, b } from "mod"; 2 | console.log({ f, x, b }); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/multiple-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log({ 8 | f: _ns.default, 9 | x: _ns.a, 10 | b: _ns.b 11 | }); 12 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/named-default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { default as f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/named-default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log(_ns.default); 8 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/named-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/named-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log(_ns.f); 8 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/namespace-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import * as M from "mod"; 2 | console.log(M); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/namespace-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const M = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log(M); 8 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../cjs/dist/index.js", { "packages": ["mod", "mod1", "mod2"] }] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/side-effect-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/side-effect-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/todo-forbidden-rewriting/input.mjs: -------------------------------------------------------------------------------- 1 | import { v } from "mod"; 2 | 3 | v = 42; 4 | [v] = [42]; 5 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/basic/todo-forbidden-rewriting/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | v = 42; 8 | [v] = [42]; 9 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/braces-only-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import {} from "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/braces-only-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/call-replacement/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | 3 | console.log(f()); 4 | console.log(f.g()); 5 | console.log(f?.()); 6 | console.log(f`foo`); 7 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/call-replacement/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log((0, _ns.f)()); 8 | console.log(_ns.f.g()); 9 | console.log((0, _ns.f)?.()); 10 | console.log((0, _ns.f)`foo`); 11 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log(_ns.default); 8 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/multiple-import-decls/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod1"; 2 | import { a as x, b } from "mod2"; 3 | import "mod1"; 4 | import {} from "mod1"; 5 | import { default as f2 } from "mod1"; 6 | import * as ns2 from "mod2"; 7 | import * as ns2b from "mod2"; 8 | 9 | console.log({ f, x, b, f2, ns2, ns2b }); 10 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/multiple-import-decls/output.mjs: -------------------------------------------------------------------------------- 1 | const ns2b = _interopImportCJSNamespace(_nsOrig5, true); 2 | const ns2 = _interopImportCJSNamespace(_nsOrig4, true); 3 | const _ns3 = _interopImportCJSNamespace(_nsOrig3, true); 4 | const _ns2 = _interopImportCJSNamespace(_nsOrig2, true); 5 | const _ns = _interopImportCJSNamespace(_nsOrig, true); 6 | function _interopImportCJSNamespace(ns, loose) { 7 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 8 | } 9 | /*#__CJS__*/ 10 | import * as _nsOrig from "mod1"; 11 | /*#__CJS__*/ 12 | import * as _nsOrig2 from "mod2"; 13 | import "mod1"; 14 | import "mod1"; 15 | /*#__CJS__*/ 16 | import * as _nsOrig3 from "mod1"; 17 | /*#__CJS__*/ 18 | import * as _nsOrig4 from "mod2"; 19 | /*#__CJS__*/ 20 | import * as _nsOrig5 from "mod2"; 21 | console.log({ 22 | f: _ns.default, 23 | x: _ns2.a, 24 | b: _ns2.b, 25 | f2: _ns3.default, 26 | ns2, 27 | ns2b 28 | }); 29 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/multiple-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f, { a as x, b } from "mod"; 2 | console.log({ f, x, b }); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/multiple-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log({ 8 | f: _ns.default, 9 | x: _ns.a, 10 | b: _ns.b 11 | }); 12 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/named-default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { default as f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/named-default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log(_ns.default); 8 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/named-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/named-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log(_ns.f); 8 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/namespace-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import * as M from "mod"; 2 | console.log(M); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/namespace-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const M = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | console.log(M); 8 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "../../../cjs/dist/index.js", 5 | { "packages": ["mod", "mod1", "mod2"], "loose": true } 6 | ] 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/side-effect-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/side-effect-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/todo-forbidden-rewriting/input.mjs: -------------------------------------------------------------------------------- 1 | import { v } from "mod"; 2 | 3 | v = 42; 4 | [v] = [42]; 5 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/loose/todo-forbidden-rewriting/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | import * as _nsOrig from "mod"; 7 | v = 42; 8 | [v] = [42]; 9 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/package-filtering/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "../../../cjs/dist/index.js", 5 | { 6 | "packages": ["foo", "bar", "@scoped/foo", "@scoped/bar"] 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/package-filtering/test/input.mjs: -------------------------------------------------------------------------------- 1 | import foo from "foo"; 2 | import bar from "bar"; 3 | import baz from "baz"; 4 | import foobar from "foobar"; 5 | import scopedFoo from "@scoped/foo"; 6 | import scopedBar from "@scoped/bar"; 7 | import scopedBaz from "@scoped/baz"; 8 | import scopedFoobar from "@scoped/foobar"; 9 | import scoopedFoo from "@scooped/foo"; 10 | import scoopedBar from "@scooped/bar"; 11 | import scoopedBaz from "@scooped/baz"; 12 | import scoopedFoobar from "@scooped/foobar"; 13 | import fooSub from "foo/sub.js"; 14 | import barSub from "bar/sub.js"; 15 | import bazSub from "baz/sub"; 16 | import relative from "./relative.js"; 17 | import absolute from "/path/to/absolute.js"; 18 | 19 | console.log({ 20 | foo, 21 | bar, 22 | baz, 23 | foobar, 24 | scopedFoo, 25 | scopedBar, 26 | scopedBaz, 27 | scopedFoobar, 28 | scoopedFoo, 29 | scoopedBar, 30 | scoopedBaz, 31 | scoopedFoobar, 32 | fooSub, 33 | barSub, 34 | bazSub, 35 | relative, 36 | absolute, 37 | }); 38 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/package-filtering/test/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns6 = _interopImportCJSNamespace(_nsOrig6); 2 | const _ns5 = _interopImportCJSNamespace(_nsOrig5); 3 | const _ns4 = _interopImportCJSNamespace(_nsOrig4); 4 | const _ns3 = _interopImportCJSNamespace(_nsOrig3); 5 | const _ns2 = _interopImportCJSNamespace(_nsOrig2); 6 | const _ns = _interopImportCJSNamespace(_nsOrig); 7 | function _interopImportCJSNamespace(ns, loose) { 8 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 9 | } 10 | /*#__CJS__*/ 11 | import * as _nsOrig from "foo"; 12 | /*#__CJS__*/ 13 | import * as _nsOrig2 from "bar"; 14 | import baz from "baz"; 15 | import foobar from "foobar"; 16 | /*#__CJS__*/ 17 | import * as _nsOrig3 from "@scoped/foo"; 18 | /*#__CJS__*/ 19 | import * as _nsOrig4 from "@scoped/bar"; 20 | import scopedBaz from "@scoped/baz"; 21 | import scopedFoobar from "@scoped/foobar"; 22 | import scoopedFoo from "@scooped/foo"; 23 | import scoopedBar from "@scooped/bar"; 24 | import scoopedBaz from "@scooped/baz"; 25 | import scoopedFoobar from "@scooped/foobar"; 26 | /*#__CJS__*/ 27 | import * as _nsOrig5 from "foo/sub.js"; 28 | /*#__CJS__*/ 29 | import * as _nsOrig6 from "bar/sub.js"; 30 | import bazSub from "baz/sub"; 31 | import relative from "./relative.js"; 32 | import absolute from "/path/to/absolute.js"; 33 | console.log({ 34 | foo: _ns.default, 35 | bar: _ns2.default, 36 | baz, 37 | foobar, 38 | scopedFoo: _ns3.default, 39 | scopedBar: _ns4.default, 40 | scopedBaz, 41 | scopedFoobar, 42 | scoopedFoo, 43 | scoopedBar, 44 | scoopedBaz, 45 | scoopedFoobar, 46 | fooSub: _ns5.default, 47 | barSub: _ns6.default, 48 | bazSub, 49 | relative, 50 | absolute 51 | }); 52 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/braces-only-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import {} from "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/braces-only-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/call-replacement/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | 3 | console.log(f()); 4 | console.log(f.g()); 5 | console.log(f?.()); 6 | console.log(f`foo`); 7 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/call-replacement/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | /*#__CJS__*/ 9 | import * as _nsOrig from "mod"; 10 | console.log((0, _ns.f)()); 11 | console.log(_ns.f.g()); 12 | console.log((0, _ns.f)?.()); 13 | console.log((0, _ns.f)`foo`); 14 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | /*#__CJS__*/ 9 | import * as _nsOrig from "mod"; 10 | console.log(_ns.default); 11 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/multiple-import-decls/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod1"; 2 | import { a as x, b } from "mod2"; 3 | import "mod1"; 4 | import {} from "mod1"; 5 | import { default as f2 } from "mod1"; 6 | import * as ns2 from "mod2"; 7 | import * as ns2b from "mod2"; 8 | 9 | console.log({ f, x, b, f2, ns2, ns2b }); 10 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/multiple-import-decls/output.mjs: -------------------------------------------------------------------------------- 1 | const ns2b = _interopImportCJSNamespaceT(_nsOrig5); 2 | const ns2 = _interopImportCJSNamespaceT(_nsOrig4); 3 | const _ns3 = _interopImportCJSNamespaceT(_nsOrig3); 4 | const _ns2 = _interopImportCJSNamespaceT(_nsOrig2); 5 | const _ns = _interopImportCJSNamespaceT(_nsOrig); 6 | function _interopImportCJSNamespaceT(ns) { 7 | return ns.default && ns.default.__esModule ? ns : { 8 | ...ns, 9 | default: ns 10 | }; 11 | } 12 | /*#__CJS__*/ 13 | import * as _nsOrig from "mod1"; 14 | /*#__CJS__*/ 15 | import * as _nsOrig2 from "mod2"; 16 | import "mod1"; 17 | import "mod1"; 18 | /*#__CJS__*/ 19 | import * as _nsOrig3 from "mod1"; 20 | /*#__CJS__*/ 21 | import * as _nsOrig4 from "mod2"; 22 | /*#__CJS__*/ 23 | import * as _nsOrig5 from "mod2"; 24 | console.log({ 25 | f: _ns.default, 26 | x: _ns2.a, 27 | b: _ns2.b, 28 | f2: _ns3.default, 29 | ns2, 30 | ns2b 31 | }); 32 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/multiple-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f, { a as x, b } from "mod"; 2 | console.log({ f, x, b }); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/multiple-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | /*#__CJS__*/ 9 | import * as _nsOrig from "mod"; 10 | console.log({ 11 | f: _ns.default, 12 | x: _ns.a, 13 | b: _ns.b 14 | }); 15 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/named-default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { default as f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/named-default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | /*#__CJS__*/ 9 | import * as _nsOrig from "mod"; 10 | console.log(_ns.default); 11 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/named-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/named-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | /*#__CJS__*/ 9 | import * as _nsOrig from "mod"; 10 | console.log(_ns.f); 11 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/namespace-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import * as M from "mod"; 2 | console.log(M); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/namespace-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const M = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | /*#__CJS__*/ 9 | import * as _nsOrig from "mod"; 10 | console.log(M); 11 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../cjs/dist/index.js", { "packagesT": ["mod", "mod1", "mod2"] }] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/side-effect-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/side-effect-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/todo-forbidden-rewriting/input.mjs: -------------------------------------------------------------------------------- 1 | import { v } from "mod"; 2 | 3 | v = 42; 4 | [v] = [42]; 5 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/ts-twisted/todo-forbidden-rewriting/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | /*#__CJS__*/ 9 | import * as _nsOrig from "mod"; 10 | v = 42; 11 | [v] = [42]; 12 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/braces-only-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import {} from "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/braces-only-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/call-replacement/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | 3 | console.log(f()); 4 | console.log(f.g()); 5 | console.log(f?.()); 6 | console.log(f`foo`); 7 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/call-replacement/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | /*#__CJS__*/ 4 | import * as _nsOrig from "mod"; 5 | console.log((0, _ns.f)()); 6 | console.log(_ns.f.g()); 7 | console.log((0, _ns.f)?.()); 8 | console.log((0, _ns.f)`foo`); 9 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | /*#__CJS__*/ 4 | import * as _nsOrig from "mod"; 5 | console.log(_ns.default); 6 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/multiple-import-decls/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod1"; 2 | import { a as x, b } from "mod2"; 3 | import "mod1"; 4 | import {} from "mod1"; 5 | import { default as f2 } from "mod1"; 6 | import * as ns2 from "mod2"; 7 | import * as ns2b from "mod2"; 8 | 9 | console.log({ f, x, b, f2, ns2, ns2b }); 10 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/multiple-import-decls/output.mjs: -------------------------------------------------------------------------------- 1 | const ns2b = _interopImportCJSNamespace(_nsOrig5); 2 | const ns2 = _interopImportCJSNamespace(_nsOrig4); 3 | const _ns3 = _interopImportCJSNamespace(_nsOrig3); 4 | const _ns2 = _interopImportCJSNamespace(_nsOrig2); 5 | const _ns = _interopImportCJSNamespace(_nsOrig); 6 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 7 | /*#__CJS__*/ 8 | import * as _nsOrig from "mod1"; 9 | /*#__CJS__*/ 10 | import * as _nsOrig2 from "mod2"; 11 | import "mod1"; 12 | import "mod1"; 13 | /*#__CJS__*/ 14 | import * as _nsOrig3 from "mod1"; 15 | /*#__CJS__*/ 16 | import * as _nsOrig4 from "mod2"; 17 | /*#__CJS__*/ 18 | import * as _nsOrig5 from "mod2"; 19 | console.log({ 20 | f: _ns.default, 21 | x: _ns2.a, 22 | b: _ns2.b, 23 | f2: _ns3.default, 24 | ns2, 25 | ns2b 26 | }); 27 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/multiple-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f, { a as x, b } from "mod"; 2 | console.log({ f, x, b }); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/multiple-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | /*#__CJS__*/ 4 | import * as _nsOrig from "mod"; 5 | console.log({ 6 | f: _ns.default, 7 | x: _ns.a, 8 | b: _ns.b 9 | }); 10 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/named-default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { default as f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/named-default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | /*#__CJS__*/ 4 | import * as _nsOrig from "mod"; 5 | console.log(_ns.default); 6 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/named-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/named-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | /*#__CJS__*/ 4 | import * as _nsOrig from "mod"; 5 | console.log(_ns.f); 6 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/namespace-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import * as M from "mod"; 2 | console.log(M); 3 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/namespace-imports/output.mjs: -------------------------------------------------------------------------------- 1 | const M = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | /*#__CJS__*/ 4 | import * as _nsOrig from "mod"; 5 | console.log(M); 6 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "../../../cjs/dist/index.js", 5 | { 6 | "packages": ["mod", "mod1", "mod2"], 7 | "useRuntime": true 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/side-effect-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/side-effect-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/todo-forbidden-rewriting/input.mjs: -------------------------------------------------------------------------------- 1 | import { v } from "mod"; 2 | 3 | v = 42; 4 | [v] = [42]; 5 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/use-runtime/todo-forbidden-rewriting/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | /*#__CJS__*/ 4 | import * as _nsOrig from "mod"; 5 | v = 42; 6 | [v] = [42]; 7 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/with-react/jsx/input.mjs: -------------------------------------------------------------------------------- 1 | import { Component, Foo } from "mod"; 2 | import * as ns from "mod2"; 3 | 4 | console.log(); 5 | console.log(); 6 | console.log(); 7 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/with-react/jsx/output.mjs: -------------------------------------------------------------------------------- 1 | const ns = _interopImportCJSNamespace(_nsOrig2); 2 | const _ns = _interopImportCJSNamespace(_nsOrig); 3 | function _interopImportCJSNamespace(ns, loose) { 4 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 5 | } 6 | /*#__CJS__*/ 7 | import * as _nsOrig from "mod"; 8 | /*#__CJS__*/ 9 | import * as _nsOrig2 from "mod2"; 10 | console.log(<_ns.Component />); 11 | console.log(<_ns.Foo.Component />); 12 | console.log(); 13 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/with-react/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOpts": { 3 | "plugins": ["jsx"] 4 | }, 5 | "plugins": [["../../../cjs/dist/index.js", { "packages": ["mod", "mod2"] }]] 6 | } 7 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/with-typescript/basic/input.mts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import type { T } from "mod"; 3 | import { x, type T2 } from "mod"; 4 | import * as ns from "mod"; 5 | import * as ns2 from "mod"; 6 | 7 | export type Foo = T & T2 & ns2.T; 8 | 9 | console.log({ x, ns }); 10 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/with-typescript/basic/output.mjs: -------------------------------------------------------------------------------- 1 | const ns = _interopImportCJSNamespace(_nsOrig2); 2 | const _ns = _interopImportCJSNamespace(_nsOrig); 3 | function _interopImportCJSNamespace(ns, loose) { 4 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 5 | } 6 | /*#__CJS__*/ 7 | // @ts-nocheck 8 | // @ts-nocheck 9 | import * as _nsOrig from "mod"; 10 | /*#__CJS__*/ 11 | import * as _nsOrig2 from "mod"; 12 | console.log({ 13 | x: _ns.x, 14 | ns 15 | }); 16 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/with-typescript/dual-use/input.mts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { T } from "mod"; 3 | 4 | export type Foo = T; 5 | 6 | console.log({ T }); 7 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/with-typescript/dual-use/output.mjs: -------------------------------------------------------------------------------- 1 | const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | /*#__CJS__*/ 6 | // @ts-nocheck 7 | // @ts-nocheck 8 | import * as _nsOrig from "mod"; 9 | console.log({ 10 | T: _ns.T 11 | }); 12 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/fixtures/with-typescript/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "presets": ["@babel/preset-typescript"], 4 | "plugins": [["../../../cjs/dist/index.js", { "packages": ["mod"] }]] 5 | } 6 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/helper-plugin-test-runner.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@babel/helper-plugin-test-runner" { 2 | function runner(loc: string): void; 3 | export default runner; 4 | } 5 | declare module "@babel/helper-plugin-test-runner/esm.mjs" { 6 | function runner(loc: string): void; 7 | export default runner; 8 | } 9 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/test/index.test.ts: -------------------------------------------------------------------------------- 1 | import runner from "@babel/helper-plugin-test-runner/esm.mjs"; 2 | 3 | const __dirname = new URL(".", import.meta.url).pathname; 4 | 5 | runner(__dirname); 6 | -------------------------------------------------------------------------------- /packages/babel-plugin-node-cjs-interop/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./configs/tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["src/**/*.test.ts", "test/**/*.ts"], 7 | "references": [{ "path": "./configs/tsconfig.main.json" }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/.prettierignore: -------------------------------------------------------------------------------- 1 | /cjs/dist 2 | /dist 3 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # node-cjs-interop-finder 2 | 3 | ## 0.1.5 4 | 5 | ### Patch Changes 6 | 7 | - c75999a: chore(deps): update babel monorepo 8 | 9 | ## 0.1.4 10 | 11 | ### Patch Changes 12 | 13 | - 95bef4e: Update typescript 14 | - a6b57a9: Update Babel 15 | 16 | ## 0.1.3 17 | 18 | ### Patch Changes 19 | 20 | - fb98f16: Internally improve Babel config 21 | 22 | ## 0.1.2 23 | 24 | - Update dependencies 25 | 26 | ## 0.1.1 27 | 28 | - Add executable entrypoint. Fixes https://github.com/qnighy/node-cjs-interop/issues/1 29 | - Skip known type-only package names to reduce noise. 30 | - Correctly process package.json with object-typed `browser` field. 31 | - Show more user-friendly message when encountered a package with CSS as its main file. 32 | It used to show a different error that the Babel parser cannot process decorators by default. 33 | 34 | ## 0.1.0 35 | 36 | Initial release. 37 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Masaki Hara 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/README.md: -------------------------------------------------------------------------------- 1 | # `node-cjs-interop-finder`: list packages eligible for `babel-plugin-node-cjs-interop` 2 | 3 | Helps configuring [`babel-plugin-node-cjs-interop`](https://npmjs.com/package/babel-plugin-node-cjs-interop). 4 | 5 | ## Usage 6 | 7 | ``` 8 | npx node-cjs-interop-finder 9 | ``` 10 | 11 | It searches packages in `dependencies` and `devDependencies`, and estimates their module types. It prints the list of packages in Babel's simulated ESM format. 12 | 13 | It prints two package lists: for Node.js and for module bundlers like Webpack. 14 | 15 | You don't need to specify all packages in the list; in most cases you only need the plugin when importing default exports. 16 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/babel.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("@babel/core").TransformOptions} */ 2 | module.exports = { 3 | targets: { node: "14" }, 4 | presets: [ 5 | ["@babel/preset-env", { modules: false }], 6 | ["@babel/preset-typescript", { allowDeclareFields: true }], 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/bin/node-cjs-interop-finder.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import "../dist/index.js"; 4 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/configs/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["ESNext"], 5 | "useDefineForClassFields": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | 9 | /* Interop Constraints */ 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | 14 | /* Type Checking */ 15 | "strict": true, 16 | "noImplicitOverride": true, 17 | "skipLibCheck": true, 18 | 19 | /* Linting */ 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noImplicitReturns": true, 23 | "noFallthroughCasesInSwitch": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/configs/tsconfig.main.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "noEmit": false, 6 | "emitDeclarationOnly": true, 7 | "rootDir": "../src", 8 | "outDir": "../dist" 9 | }, 10 | "include": ["../src/**/*.ts"], 11 | "exclude": ["../src/**/*.test.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-cjs-interop-finder", 3 | "description": "A helper command for babel-plugin-node-cjs-interop", 4 | "keywords": [ 5 | "cli", 6 | "commonjs", 7 | "node", 8 | "esm", 9 | "mjs", 10 | "cjs", 11 | "default import" 12 | ], 13 | "homepage": "https://github.com/qnighy/node-cjs-interop#readme", 14 | "bugs": { 15 | "url": "https://github.com/qnighy/node-cjs-interop/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/qnighy/node-cjs-interop.git", 20 | "directory": "packages/node-cjs-interop-finder" 21 | }, 22 | "license": "MIT", 23 | "author": "Masaki Hara ", 24 | "version": "0.1.5", 25 | "type": "module", 26 | "bin": "./bin/node-cjs-interop-finder.js", 27 | "main": "./dist/index.js", 28 | "types": "./dist/index.d.ts", 29 | "exports": "./dist/index.js", 30 | "files": [ 31 | "dist/**/*" 32 | ], 33 | "scripts": { 34 | "build": "$npm_execpath run build:tsc && $npm_execpath run build:babel", 35 | "build:babel": "babel -d dist src -x '.ts' --ignore 'src/**/*.test.ts,src/__fixtures__/**/*.ts'", 36 | "build:tsc": "tsc --build", 37 | "fmt": "prettier -w .", 38 | "fmt:check": "prettier -c .", 39 | "prepack": "$npm_execpath run build", 40 | "lint": "eslint .", 41 | "test": "NODE_OPTIONS=--experimental-vm-modules jest" 42 | }, 43 | "dependencies": { 44 | "@babel/parser": "^7.23.3", 45 | "@babel/types": "^7.23.3", 46 | "resolve": "^1.22.8" 47 | }, 48 | "devDependencies": { 49 | "@babel/cli": "^7.24.8", 50 | "@babel/core": "^7.25.2", 51 | "@babel/preset-env": "^7.25.3", 52 | "@babel/preset-typescript": "^7.24.7", 53 | "@jest/globals": "^29.7.0", 54 | "@types/node": "^20.14.14", 55 | "@types/resolve": "^1.20.6", 56 | "babel-jest": "^29.7.0", 57 | "eslint": "^9.8.0", 58 | "jest": "^29.7.0", 59 | "jest-resolve": "^29.7.0", 60 | "jest-ts-webcompat-resolver": "^1.0.0", 61 | "prettier": "^3.3.3", 62 | "typescript": "^5.5.4" 63 | }, 64 | "jest": { 65 | "extensionsToTreatAsEsm": [ 66 | ".ts", 67 | ".mts" 68 | ], 69 | "resolver": "jest-ts-webcompat-resolver" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/.gitignore: -------------------------------------------------------------------------------- 1 | !/node_modules 2 | !dist 3 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/@types/dep1/index.d.ts: -------------------------------------------------------------------------------- 1 | declare function greet(): void; 2 | 3 | export = greet; 4 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/@types/dep1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@types/dep1", 4 | "types": "./index.d.ts" 5 | } 6 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep1/dist/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function greet() { 2 | console.log("Hello, world!"); 3 | }; 4 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "dep1", 4 | "main": "./dist/index.js" 5 | } 6 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep2/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = greet; 7 | 8 | function greet() { 9 | console.log("Hello, world!"); 10 | } 11 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "dep2", 4 | "main": "./dist/index.js" 5 | } 6 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep3/dist/esm/index.js: -------------------------------------------------------------------------------- 1 | export default function greet() { 2 | console.log("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep3/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = greet; 7 | 8 | function greet() { 9 | console.log("Hello, world!"); 10 | } 11 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "dep3", 4 | "main": "./dist/index.js", 5 | "module": "./dist/esm/index.js" 6 | } 7 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep4/dist/index.js: -------------------------------------------------------------------------------- 1 | export default function greet() { 2 | console.log("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "dep4", 4 | "type": "module", 5 | "main": "./dist/index.js" 6 | } 7 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep5/dist/browser.js: -------------------------------------------------------------------------------- 1 | export default function greet() { 2 | console.log("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep5/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = greet; 7 | 8 | function greet() { 9 | console.log("Hello, world!"); 10 | } 11 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/node_modules/dep5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "dep5", 4 | "main": "./dist/index.js", 5 | "browser": { 6 | "./dist/index.js": "./dist/browser.js" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/__fixtures__/package1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "test-package1", 4 | "dependencies": { 5 | "@types/dep1": "0.0.0", 6 | "dep1": "0.0.0", 7 | "dep2": "0.0.0", 8 | "dep3": "0.0.0", 9 | "dep4": "0.0.0", 10 | "dep5": "0.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/classify-module.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "@jest/globals"; 2 | import { classifyModule } from "./classify-module"; 3 | 4 | describe("classifyModule", () => { 5 | it("Detects pure CJS", () => { 6 | expect( 7 | classifyModule(` 8 | module.exports = function() { 9 | console.log("Hello, world!"); 10 | }; 11 | `), 12 | ).toBe("commonjs"); 13 | }); 14 | 15 | it("Detects ESM", () => { 16 | expect( 17 | classifyModule(` 18 | export default function() { 19 | console.log("Hello, world!"); 20 | } 21 | `), 22 | ).toBe("module"); 23 | }); 24 | 25 | it("Detects exports.__esModule with Object.defineProperty", () => { 26 | expect( 27 | classifyModule(` 28 | "use strict"; 29 | 30 | Object.defineProperty(exports, "__esModule", { 31 | value: true 32 | }); 33 | exports.default = _default; 34 | 35 | function _default() { 36 | console.log("Hello, world!"); 37 | } 38 | `), 39 | ).toBe("commonjs-babel"); 40 | }); 41 | 42 | it("Detects module.exports.__esModule with Object.defineProperty", () => { 43 | expect( 44 | classifyModule(` 45 | "use strict"; 46 | 47 | Object.defineProperty(module.exports, "__esModule", { 48 | value: true 49 | }); 50 | exports.default = _default; 51 | 52 | function _default() { 53 | console.log("Hello, world!"); 54 | } 55 | `), 56 | ).toBe("commonjs-babel"); 57 | }); 58 | 59 | it("Detects exports.__esModule with assignment", () => { 60 | expect( 61 | classifyModule(` 62 | "use strict"; 63 | 64 | exports.__esModule = true; 65 | exports.default = _default; 66 | 67 | function _default() { 68 | console.log("Hello, world!"); 69 | } 70 | `), 71 | ).toBe("commonjs-babel"); 72 | }); 73 | 74 | it("Detects module.exports.__esModule with assignment", () => { 75 | expect( 76 | classifyModule(` 77 | "use strict"; 78 | 79 | module.exports.__esModule = true; 80 | exports.default = _default; 81 | 82 | function _default() { 83 | console.log("Hello, world!"); 84 | } 85 | `), 86 | ).toBe("commonjs-babel"); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/classify-module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isAssignmentExpression, 3 | isCallExpression, 4 | isExpressionStatement, 5 | isIdentifier, 6 | isMemberExpression, 7 | CallExpression, 8 | Expression, 9 | Node, 10 | isStringLiteral, 11 | isObjectExpression, 12 | isObjectProperty, 13 | isExpression, 14 | isBooleanLiteral, 15 | } from "@babel/types"; 16 | import { parse } from "@babel/parser"; 17 | 18 | export type ModuleType = "commonjs" | "commonjs-babel" | "module"; 19 | 20 | export function classifyModule(code: string): ModuleType { 21 | const ast = parse(code, { sourceType: "unambiguous" }); 22 | if (ast.program.sourceType === "module") return "module"; 23 | 24 | for (const stmt of ast.program.body) { 25 | if (isExpressionStatement(stmt)) { 26 | const expr = stmt.expression; 27 | if (isCallToDefineProperty(expr) && expr.arguments.length === 3) { 28 | // Object.defineProperty(...) 29 | if ( 30 | isExportsObject(expr.arguments[0]) && 31 | isStringLiteral(expr.arguments[1], { value: "__esModule" }) 32 | ) { 33 | const value = getValueExpression(expr.arguments[2]); 34 | // Object.defineProperty(exports, "__esModule", { value: true }); 35 | if (isBooleanLiteral(value, { value: true })) return "commonjs-babel"; 36 | } 37 | } else if (isAssignmentExpression(expr)) { 38 | if ( 39 | isMemberExpression(expr.left, { computed: false }) && 40 | isExportsObject(expr.left.object) && 41 | isIdentifier(expr.left.property, { name: "__esModule" }) && 42 | isBooleanLiteral(expr.right, { value: true }) 43 | ) { 44 | // exports.__esModule = true; 45 | return "commonjs-babel"; 46 | } 47 | } 48 | } 49 | } 50 | 51 | return "commonjs"; 52 | } 53 | 54 | /** `exports` or `module.exports` */ 55 | function isExportsObject(expr: Node): boolean { 56 | return ( 57 | isIdentifier(expr, { name: "exports" }) || 58 | (isMemberExpression(expr, { computed: false }) && 59 | isIdentifier(expr.object, { name: "module" }) && 60 | isIdentifier(expr.property, { name: "exports" })) 61 | ); 62 | } 63 | 64 | /** `Object.defineProperty(...)` */ 65 | function isCallToDefineProperty(expr: Expression): expr is CallExpression { 66 | return isCallExpression(expr) && isDefineProperty(expr.callee); 67 | } 68 | 69 | /** `Object.defineProperty` */ 70 | function isDefineProperty(expr: Node): boolean { 71 | return ( 72 | isMemberExpression(expr, { computed: false }) && 73 | isIdentifier(expr.object, { name: "Object" }) && 74 | isIdentifier(expr.property, { name: "defineProperty" }) 75 | ); 76 | } 77 | 78 | /** Extract `{ value: ... }` */ 79 | function getValueExpression(expr: Node): Expression | undefined { 80 | if (isObjectExpression(expr)) { 81 | for (const property of expr.properties) { 82 | if ( 83 | isObjectProperty(property, { computed: false }) && 84 | isIdentifier(property.key, { name: "value" }) && 85 | isExpression(property.value) 86 | ) { 87 | return property.value; 88 | } 89 | } 90 | } 91 | return undefined; 92 | } 93 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/index.ts: -------------------------------------------------------------------------------- 1 | import { listTargetPackages } from "./list-packages.js"; 2 | 3 | const packages = await listTargetPackages({ 4 | basePath: process.cwd(), 5 | mainFields: [], 6 | console, 7 | }); 8 | console.log(`packages (Node.js): [`); 9 | for (const packageName of packages) { 10 | console.log(` ${JSON.stringify(packageName)},`); 11 | } 12 | console.log("]"); 13 | 14 | const packagesWeb = await listTargetPackages({ 15 | basePath: process.cwd(), 16 | mainFields: ["browser", "module"], 17 | console, 18 | }); 19 | console.log(`packages (bundler): [`); 20 | for (const packageName of packagesWeb) { 21 | console.log(` ${JSON.stringify(packageName)},`); 22 | } 23 | console.log("]"); 24 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/list-packages.test.ts: -------------------------------------------------------------------------------- 1 | import { URL } from "node:url"; 2 | import path from "node:path"; 3 | import { describe, expect, it } from "@jest/globals"; 4 | import { listTargetPackages } from "./list-packages"; 5 | 6 | function filePath(url: string) { 7 | const urlObj = new URL(url); 8 | if (urlObj.protocol !== "file:") 9 | throw new Error(`Not a file protocol: ${url}`); 10 | return urlObj.pathname; 11 | } 12 | 13 | const fixtures = path.resolve( 14 | path.dirname(filePath(import.meta.url)), 15 | "./__fixtures__", 16 | ); 17 | 18 | const consoleError: typeof console.error = (message) => { 19 | if (message instanceof Error) { 20 | throw message; 21 | } 22 | throw new Error(`console.error: ${message as string}`); 23 | }; 24 | 25 | describe("listTargetPackges", () => { 26 | it("Lists simulated ESM packages for Node.js", async () => { 27 | const result = await listTargetPackages({ 28 | basePath: path.resolve(fixtures, "package1"), 29 | mainFields: [], 30 | console: { 31 | error: consoleError, 32 | // log: console.log, 33 | } as typeof console, 34 | }); 35 | expect(result).toEqual(["dep2", "dep3", "dep5"]); 36 | }); 37 | 38 | it("Lists simulated ESM packages for module bundlers", async () => { 39 | const result = await listTargetPackages({ 40 | basePath: path.resolve(fixtures, "package1"), 41 | mainFields: ["browser", "module"], 42 | console: { 43 | error: consoleError, 44 | // log: console.log, 45 | } as typeof console, 46 | }); 47 | expect(result).toEqual(["dep2"]); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/src/list-packages.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import resolve from "resolve"; 4 | import { classifyModule, ModuleType } from "./classify-module.js"; 5 | 6 | const NON_JS_PACKAGES = [/^@types\//, "type-fest"]; 7 | 8 | type Options = { 9 | basePath: string; 10 | mainFields: string[]; 11 | console: typeof console; 12 | }; 13 | 14 | export async function listTargetPackages(options: Options): Promise { 15 | const pjson = (await findPackageJson(options.basePath)) as { 16 | dependencies?: string[]; 17 | devDependencies?: string[]; 18 | }; 19 | const packageNames: string[] = []; 20 | if (pjson.dependencies) { 21 | packageNames.push(...Object.keys(pjson.dependencies)); 22 | } 23 | if (pjson.devDependencies) { 24 | packageNames.push(...Object.keys(pjson.devDependencies)); 25 | } 26 | return await listTargetPackagesFrom(options, packageNames); 27 | } 28 | 29 | async function findPackageJson(basePath: string): Promise { 30 | let currentPath = basePath; 31 | while (true) { 32 | const pjsonPath = path.resolve(currentPath, "package.json"); 33 | if (fs.existsSync(pjsonPath)) { 34 | return JSON.parse( 35 | await fs.promises.readFile(pjsonPath, { encoding: "utf-8" }), 36 | ) as unknown; 37 | } 38 | 39 | const nextPath = path.resolve(currentPath, ".."); 40 | if (nextPath === currentPath) 41 | throw new Error(`Cannot find package.json in ${basePath}`); 42 | currentPath = nextPath; 43 | } 44 | } 45 | 46 | export async function listTargetPackagesFrom( 47 | options: Options, 48 | packageNames: string[], 49 | ): Promise { 50 | const ret: string[] = []; 51 | for (const packageName of packageNames) { 52 | if (isNonJSPackage(packageName)) continue; 53 | 54 | let moduleType: ModuleType | undefined = undefined; 55 | try { 56 | moduleType = await classifyPackage(options, packageName); 57 | } catch (e) { 58 | options.console.error(e); 59 | } 60 | if (moduleType === "commonjs-babel") { 61 | ret.push(packageName); 62 | } 63 | } 64 | return ret; 65 | } 66 | 67 | function isNonJSPackage(packageName: string) { 68 | return NON_JS_PACKAGES.some((condition) => 69 | typeof condition === "string" 70 | ? packageName === condition 71 | : condition.test(packageName), 72 | ); 73 | } 74 | 75 | async function classifyPackage( 76 | options: Options, 77 | packageName: string, 78 | ): Promise { 79 | const [spec] = await new Promise< 80 | [string | undefined, PackageMeta | undefined] 81 | >((resolvePromise, reject) => { 82 | resolve( 83 | packageName, 84 | { 85 | basedir: options.basePath, 86 | packageFilter(pkg) { 87 | pkg = { ...pkg }; 88 | for (const mainField of [...options.mainFields].reverse()) { 89 | if (typeof pkg[mainField] === "string") { 90 | pkg["main"] = pkg[mainField]; 91 | } else if (typeof pkg[mainField] === "object") { 92 | const map = pkg[mainField] as Record; 93 | const relativePath = pkg["main"] as string; 94 | if (typeof map[relativePath] === "string") 95 | pkg["main"] = map[relativePath]; 96 | } 97 | } 98 | return pkg; 99 | }, 100 | }, 101 | (err, spec, meta) => { 102 | if (err) reject(err); 103 | else resolvePromise([spec, meta]); 104 | }, 105 | ); 106 | }); 107 | if (!spec) 108 | throw new Error(`Cannot find ${packageName}: resolve returned nothing`); 109 | if (/\.css$/.test(spec)) throw new Error(`${packageName} resolved to CSS`); 110 | const code = await fs.promises.readFile(spec, { encoding: "utf-8" }); 111 | return classifyModule(code); 112 | } 113 | 114 | type PackageMeta = { 115 | name: string; 116 | version: string; 117 | [key: string]: unknown; 118 | }; 119 | -------------------------------------------------------------------------------- /packages/node-cjs-interop-finder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./configs/tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["src/**/*.test.ts"], 7 | "references": [{ "path": "./configs/tsconfig.main.json" }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/.prettierignore: -------------------------------------------------------------------------------- 1 | /cjs/dist 2 | /dist 3 | /src/__fixtures__/module2.cjs 4 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # node-cjs-interop 2 | 3 | ## 0.1.7 4 | 5 | ### Patch Changes 6 | 7 | - c75999a: chore(deps): update babel monorepo 8 | 9 | ## 0.1.6 10 | 11 | ### Patch Changes 12 | 13 | - 95bef4e: Update typescript 14 | - a6b57a9: Update Babel 15 | 16 | ## 0.1.5 17 | 18 | ### Patch Changes 19 | 20 | - fb98f16: Internally improve Babel config 21 | 22 | ## 0.1.4 23 | 24 | ### Patch Changes 25 | 26 | - 972e632: Add a variant helper that wraps default one more time. 27 | 28 | The helper is called `interopImportCJSNamespaceT` and you can use it just like `interopImportCJSNamespace` 29 | (except that it assumes `loose = true` by default). 30 | 31 | To use it from the Babel/SWC plugins, use the `packagesT` option instead of `packages`. 32 | 33 | ## 0.1.3 34 | 35 | ### Patch Changes 36 | 37 | - 54b33bb: Change how dual packages are configured 38 | 39 | ## 0.1.2 40 | 41 | - Update dependencies 42 | 43 | ## 0.1.1 44 | 45 | Add `loose` parameter to `interopImportCJSNamespace` to support more libraries. 46 | 47 | ## 0.1.0 48 | 49 | Initial release. 50 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Masaki Hara 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/README.md: -------------------------------------------------------------------------------- 1 | # `node-cjs-interop`: Import helpers for Babel ESM from Node.js native ESM 2 | 3 | ## The problem to solve 4 | 5 | Consider the following modules: 6 | 7 | ```javascript 8 | // a.js 9 | 10 | export default function greet() { 11 | console.log("Hello, world!"); 12 | } 13 | ``` 14 | 15 | ```javascript 16 | // b.js 17 | 18 | import greet from "a.js"; 19 | 20 | greet(); 21 | ``` 22 | 23 | They usually work, unless the following conditions are met: 24 | 25 | - `a.js` (the module being imported) is a **simulated ESM**. That is, the module is transpiled as a CommonJS module (by Babel or TypeScript) before execution. And, 26 | - `b.js` (the importing module) is a **native ESM**, That is, the module is run on Node.js' native ES Module support. 27 | 28 | You can reproduce the above condition by placing the following files: 29 | 30 | ```javascript 31 | // a.cjs 32 | 33 | "use strict"; 34 | 35 | Object.defineProperty(exports, "__esModule", { 36 | value: true, 37 | }); 38 | exports.default = greet; 39 | 40 | function greet() { 41 | console.log("Hello, world!"); 42 | } 43 | ``` 44 | 45 | ```javascript 46 | // b.mjs 47 | 48 | import greet from "./a.cjs"; 49 | 50 | greet(); 51 | ``` 52 | 53 | ``` 54 | $ node ./b.mjs 55 | ./b.mjs:3 56 | greet(); 57 | ^ 58 | 59 | TypeError: greet is not a function 60 | at ./b.mjs:3:1 61 | at ModuleJob.run (node:internal/modules/esm/module_job:185:25) 62 | at async Promise.all (index 0) 63 | at async ESMLoader.import (node:internal/modules/esm/loader:281:24) 64 | at async loadESM (node:internal/process/esm_loader:88:5) 65 | at async handleMainPromise (node:internal/modules/run_main:65:12) 66 | ``` 67 | 68 | The following packages solve the problem: 69 | 70 | - [`babel-plugin-node-cjs-interop`](https://npmjs.com/package/babel-plugin-node-cjs-interop) / [`swc-plugin-node-cjs-interop`](https://npmjs.com/package/swc-plugin-node-cjs-interop): automatically inserts the compatibility wrapper. 71 | - `node-cjs-interop` (this package): allows manually wrapping the exported value. 72 | 73 | ## Getting started 74 | 75 | Install the package: 76 | 77 | ``` 78 | npm install node-cjs-interop 79 | # or: 80 | yarn add node-cjs-interop 81 | ``` 82 | 83 | Wrap a default import: 84 | 85 | ```javascript 86 | import { interopImportCJSDefault } from "node-cjs-interop"; 87 | import styledOrig from "styled-components"; 88 | 89 | const styled = interopImportCJSDefault(styledOrig); 90 | 91 | const CustomDiv = styled.div` 92 | ... 93 | `; 94 | ``` 95 | 96 | Wrap a namespace import: 97 | 98 | ```javascript 99 | import { interopImportCJSNamespace } from "node-cjs-interop"; 100 | import * as nsOrig from "your-package"; 101 | const ns = interopImportCJSNamespace(nsOrig); 102 | ``` 103 | 104 | ## Difference between `interopImportCJSNamespace` and `interopCJSDefault` 105 | 106 | `interopImportCJSNamespace` allows more accurate simulation of ESM semantics: 107 | 108 | ```javascript 109 | import { interopImportCJSNamespace } from "node-cjs-interop"; 110 | // import styled from "styled-components"; 111 | import * as nsOrig from "styled-components"; 112 | 113 | const ns = interopImportCJSNamespace(nsOrig); 114 | 115 | // const CustomDiv = styled.div`...`; 116 | const CustomDiv = ns.default.div`...`; 117 | ``` 118 | 119 | However, using [`babel-plugin-node-cjs-interop`](https://npmjs.com/package/babel-plugin-node-cjs-interop) is recommend over manual wrapping. 120 | 121 | ## The "twisted" variant 122 | 123 | You can also use the "twisted" variant of the functions: 124 | 125 | ```javascript 126 | import { interopImportCJSNamespaceT } from "node-cjs-interop"; 127 | import * as TextareaAutosizeOrig from "react-textarea-autosize"; 128 | 129 | const { 130 | default: { default: TextareaAutosize }, 131 | } = interopImportCJSNamespaceT(TextareaAutosizeOrig); 132 | ``` 133 | 134 | This is useful when you have `"module"` or `"moduleResolution"` set to `"nodenext"` or `"node16"` 135 | in your `tsconfig.json` and you need to import `default` from a "dual package" in which the 136 | type definitions are recognized in the `.cts` mode. 137 | 138 | There is no such thing as `interopImportCJSDefaultT` because in this mode, the imported `default` value 139 | would be the namespace object, and if it was originally the proper default export 140 | (i.e. in case it was imported like ESM), there is no way to reconstruct the whole namespace object from it. 141 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/babel-cjs.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("@babel/core").TransformOptions} */ 2 | module.exports = { 3 | extends: "./babel.config.cjs", 4 | presets: [["@babel/preset-env", { modules: "commonjs" }]], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/babel.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("@babel/core").TransformOptions} */ 2 | module.exports = { 3 | targets: { node: "14" }, 4 | presets: [ 5 | ["@babel/preset-env", { modules: false }], 6 | ["@babel/preset-typescript", { allowDeclareFields: true }], 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/cjs/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/configs/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["ESNext"], 5 | "useDefineForClassFields": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | 9 | /* Interop Constraints */ 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | 14 | /* Type Checking */ 15 | "strict": true, 16 | "noImplicitOverride": true, 17 | "skipLibCheck": true, 18 | 19 | /* Linting */ 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noImplicitReturns": true, 23 | "noFallthroughCasesInSwitch": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/configs/tsconfig.main.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "noEmit": false, 6 | "emitDeclarationOnly": true, 7 | "rootDir": "../src", 8 | "outDir": "../dist" 9 | }, 10 | "include": ["../src/**/*.ts"], 11 | "exclude": ["../src/**/*.test.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-cjs-interop", 3 | "description": "A library to fix the default import interoperability issue in Node.js", 4 | "keywords": [ 5 | "runtime", 6 | "commonjs", 7 | "node", 8 | "esm", 9 | "mjs", 10 | "cjs", 11 | "default import" 12 | ], 13 | "homepage": "https://github.com/qnighy/node-cjs-interop#readme", 14 | "bugs": { 15 | "url": "https://github.com/qnighy/node-cjs-interop/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/qnighy/node-cjs-interop.git", 20 | "directory": "packages/node-cjs-interop" 21 | }, 22 | "license": "MIT", 23 | "author": "Masaki Hara ", 24 | "version": "0.1.7", 25 | "type": "module", 26 | "main": "./dist/index.js", 27 | "types": "./dist/index.d.ts", 28 | "exports": { 29 | "require": "./cjs/dist/index.js", 30 | "default": "./dist/index.js" 31 | }, 32 | "files": [ 33 | "cjs/**/*", 34 | "dist/**/*" 35 | ], 36 | "scripts": { 37 | "build": "$npm_execpath run build:tsc && $npm_execpath run build:babel:cjs && $npm_execpath run build:babel:esm", 38 | "build:babel:cjs": "babel -d cjs/dist src -x '.ts' --ignore 'src/**/*.test.ts' --config-file ./babel-cjs.config.cjs", 39 | "build:babel:esm": "babel -d dist src -x '.ts' --ignore 'src/**/*.test.ts'", 40 | "build:tsc": "tsc --build", 41 | "fmt": "prettier -w .", 42 | "fmt:check": "prettier -c .", 43 | "lint": "eslint .", 44 | "prepack": "$npm_execpath run build", 45 | "test": "NODE_OPTIONS=--experimental-vm-modules jest" 46 | }, 47 | "devDependencies": { 48 | "@babel/cli": "^7.24.8", 49 | "@babel/core": "^7.25.2", 50 | "@babel/preset-env": "^7.25.3", 51 | "@babel/preset-typescript": "^7.24.7", 52 | "@jest/globals": "^29.7.0", 53 | "babel-jest": "^29.7.0", 54 | "eslint": "^9.8.0", 55 | "jest": "^29.7.0", 56 | "prettier": "^3.3.3", 57 | "typescript": "^5.5.4" 58 | }, 59 | "jest": { 60 | "extensionsToTreatAsEsm": [ 61 | ".ts", 62 | ".mts" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/__fixtures__/module1.cjs: -------------------------------------------------------------------------------- 1 | module.exports = (x) => x * x; 2 | module.exports.version = "0.1.2"; 3 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/__fixtures__/module1.d.cts: -------------------------------------------------------------------------------- 1 | declare function square(x: number): number; 2 | declare namespace square { 3 | const version: string; 4 | } 5 | 6 | export = square; 7 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/__fixtures__/module2.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.version = exports.default = void 0; 7 | 8 | var _default = x => x * x; 9 | 10 | exports.default = _default; 11 | const version = "0.1.2"; 12 | exports.version = version; 13 | 14 | // export default (x) => x * x; 15 | // export const version = "0.1.2"; 16 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/__fixtures__/module2.d.cts: -------------------------------------------------------------------------------- 1 | declare function square(x: number): number; 2 | export default square; 3 | 4 | export declare const version: string; 5 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/__fixtures__/module3.d.mts: -------------------------------------------------------------------------------- 1 | declare function square(x: number): number; 2 | export default square; 3 | 4 | export declare const version: string; 5 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/__fixtures__/module3.mjs: -------------------------------------------------------------------------------- 1 | export default (x) => x * x; 2 | export const version = "0.1.2"; 3 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/__fixtures__/module4.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function __define(obj, name, value) { 4 | Object.defineProperty(obj, name, { 5 | value: value, 6 | }); 7 | } 8 | 9 | __define(exports, "__esModule", true); 10 | exports.version = exports.default = void 0; 11 | 12 | var _default = (x) => x * x; 13 | 14 | exports.default = _default; 15 | const version = "0.1.2"; 16 | exports.version = version; 17 | 18 | // export default (x) => x * x; 19 | // export const version = "0.1.2"; 20 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/__fixtures__/module4.d.cts: -------------------------------------------------------------------------------- 1 | declare function square(x: number): number; 2 | export default square; 3 | 4 | export declare const version: string; 5 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "@jest/globals"; 2 | import { 3 | interopImportCJSNamespace, 4 | interopImportCJSNamespaceT, 5 | interopImportCJSDefault, 6 | } from "."; 7 | import * as module1 from "./__fixtures__/module1.cjs"; 8 | import * as module2 from "./__fixtures__/module2.cjs"; 9 | import * as module3 from "./__fixtures__/module3.mjs"; 10 | import * as module4 from "./__fixtures__/module4.cjs"; 11 | 12 | describe("interopImportCJSNamespace", () => { 13 | it("Returns the same value for pure CJS", () => { 14 | const wrapped = interopImportCJSNamespace(module1); 15 | expect(wrapped).toBe(module1); 16 | expect(wrapped.default(42)).toBe(1764); 17 | expect(wrapped.version).toBe("0.1.2"); 18 | }); 19 | 20 | it("Returns the default value for transpiled CJS", () => { 21 | const wrapped = interopImportCJSNamespace(module2); 22 | expect(wrapped).toBe(module2.default); 23 | expect(wrapped.default(42)).toBe(1764); 24 | expect(wrapped.version).toBe("0.1.2"); 25 | }); 26 | 27 | it("Returns the same value for native ESM", () => { 28 | const wrapped = interopImportCJSNamespace(module3); 29 | expect(wrapped).toBe(module3); 30 | expect(wrapped.default(42)).toBe(1764); 31 | expect(wrapped.version).toBe("0.1.2"); 32 | }); 33 | 34 | it("Returns the same value for poorly-written transpiled CJS", () => { 35 | const wrapped = interopImportCJSNamespace(module4); 36 | expect(wrapped).toBe(module4); 37 | }); 38 | }); 39 | 40 | describe("interopImportCJSNamespace with loose = true", () => { 41 | it("Returns the same value for pure CJS", () => { 42 | const wrapped = interopImportCJSNamespace(module1, true); 43 | expect(wrapped).toBe(module1); 44 | expect(wrapped.default(42)).toBe(1764); 45 | expect(wrapped.version).toBe("0.1.2"); 46 | }); 47 | 48 | it("Returns the default value for transpiled CJS", () => { 49 | const wrapped = interopImportCJSNamespace(module2, true); 50 | expect(wrapped).toBe(module2.default); 51 | expect(wrapped.default(42)).toBe(1764); 52 | expect(wrapped.version).toBe("0.1.2"); 53 | }); 54 | 55 | it("Returns the same value for native ESM", () => { 56 | const wrapped = interopImportCJSNamespace(module3, true); 57 | expect(wrapped).toBe(module3); 58 | expect(wrapped.default(42)).toBe(1764); 59 | expect(wrapped.version).toBe("0.1.2"); 60 | }); 61 | 62 | it("Returns the default value for poorly-written transpiled CJS", () => { 63 | const wrapped = interopImportCJSNamespace(module4, true); 64 | expect(wrapped).toBe(module4.default); 65 | expect(wrapped.default(42)).toBe(1764); 66 | expect(wrapped.version).toBe("0.1.2"); 67 | }); 68 | }); 69 | 70 | describe("interopImportCJSNamespaceT", () => { 71 | it("Returns a wrapped value for transpiled CJS", () => { 72 | const wrapped = interopImportCJSNamespaceT( 73 | // Reinterpret type to simulate "module": "nodenext" behavior 74 | module2 as typeof module2 & { default: typeof module2 }, 75 | ); 76 | expect(wrapped.default).toBe(module2.default); 77 | expect(wrapped.default.default(42)).toBe(1764); 78 | expect(wrapped.version).toBe("0.1.2"); 79 | }); 80 | 81 | it("Returns the default value for poorly-written transpiled CJS", () => { 82 | const wrapped = interopImportCJSNamespaceT( 83 | // Reinterpret type to simulate "module": "nodenext" behavior 84 | module4 as typeof module4 & { default: typeof module4 }, 85 | ); 86 | expect(wrapped.default).toBe(module4.default); 87 | expect(wrapped.default.default(42)).toBe(1764); 88 | expect(wrapped.version).toBe("0.1.2"); 89 | }); 90 | }); 91 | 92 | describe("interopImportCJSDefault", () => { 93 | it("Returns the same value for pure CJS", () => { 94 | const wrapped = interopImportCJSDefault(module1.default); 95 | expect(wrapped).toBe(module1.default); 96 | expect(wrapped(42)).toBe(1764); 97 | }); 98 | 99 | it("Returns the default value for transpiled CJS", () => { 100 | const wrapped = interopImportCJSDefault(module2.default); 101 | expect(wrapped).toBe( 102 | (module2 as unknown as { default: typeof module2 }).default.default, 103 | ); 104 | expect(wrapped(42)).toBe(1764); 105 | }); 106 | 107 | it("Returns the same value for native ESM", () => { 108 | const wrapped = interopImportCJSDefault(module3.default); 109 | expect(wrapped).toBe(module3.default); 110 | expect(wrapped(42)).toBe(1764); 111 | }); 112 | 113 | it("Returns the default value for poorly-written transpiled CJS", () => { 114 | const wrapped = interopImportCJSDefault(module4.default); 115 | expect(wrapped).toBe( 116 | (module4 as unknown as { default: typeof module4 }).default.default, 117 | ); 118 | expect(wrapped(42)).toBe(1764); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Adjusts a namespace object to allow interoperation between 3 | * Node.js native ESM and Babel's ESM transpilation. 4 | * 5 | * @param ns the namespace object provided by Node.js 6 | * @param loose skip checking the existence of `ns.__esModule`. 7 | * This is useful when cjs-module-lexer is unable to detect the 8 | * definition of `__esModule`. 9 | * @returns the adjusted namespace object 10 | * @example 11 | * ```ts 12 | * import * as nsOrig from "mod"; 13 | * const ns = interopImportCJSNamespace(nsOrig); 14 | * console.log([ns.foo, ns.default]); 15 | * ``` 16 | */ 17 | export function interopImportCJSNamespace(ns: T, loose?: boolean): T { 18 | type TT = NamespaceWrapper; 19 | return (loose || (ns as TT).__esModule) && 20 | (ns as TT).default && 21 | (ns as TT).default.__esModule 22 | ? (ns as TT).default 23 | : ns; 24 | } 25 | 26 | /** 27 | * Adjusts a namespace object to allow interoperation between 28 | * Node.js native ESM and Babel's ESM transpilation. 29 | * 30 | * This is an alternative to {@link interopImportCJSNamespace} that 31 | * aligns with skew default-import mode. This is useful when all of 32 | * the following conditions are met: 33 | * 34 | * - You have "module" or "moduleResolution" set to "node16" or "nodenext" 35 | * - Your source code is in ESM mode. That is: 36 | * - The extension is ".mjs" or ".mts" 37 | * - or the extension is ".js", ".ts", ".jsx", or ".tsx" and you have 38 | * "type": "module" in your package.json 39 | * - The module you are importing is in CJS mode. That is: 40 | * - The extension is ".cjs" or ".cts" 41 | * - or the extension is ".js", ".ts", ".jsx", or ".tsx" and you have 42 | * "type": "commonjs" in your package.json 43 | * 44 | * @param ns the namespace object provided by Node.js 45 | * @returns the adjusted namespace object 46 | * @example 47 | * ```ts 48 | * import * as nsOrig from "mod"; 49 | * const ns = interopImportCJSNamespaceT(nsOrig); 50 | * console.log([ns.default.foo, ns.default.default]); 51 | * ``` 52 | */ 53 | export function interopImportCJSNamespaceT(ns: T): T { 54 | type TT = NamespaceWrapper; 55 | return (ns as TT).default && (ns as TT).default.__esModule 56 | ? ns 57 | : { ...ns, default: ns }; 58 | } 59 | 60 | /** 61 | * Adjusts a default imported object to allow interoperation between 62 | * Node.js native ESM and Babel's ESM transpilation. 63 | * 64 | * @param d the default imported object provided by Node.js 65 | * @returns the adjusted default imported object 66 | * @example 67 | * ```ts 68 | * import valueOrig from "mod"; 69 | * const value = interopImportCJSDefault(value); 70 | * console.log(value); 71 | * ``` 72 | */ 73 | export function interopImportCJSDefault(d: T): T { 74 | return d && (d as DefaultWrapper).__esModule 75 | ? (d as DefaultWrapper).default 76 | : d; 77 | } 78 | 79 | type NamespaceWrapper = T & { 80 | __esModule?: boolean; 81 | default: T & { __esModule?: boolean }; 82 | }; 83 | 84 | type DefaultWrapper = T & { default: T; __esModule?: boolean }; 85 | -------------------------------------------------------------------------------- /packages/node-cjs-interop/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./configs/tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["src/**/*.test.ts"], 7 | "references": [{ "path": "./configs/tsconfig.main.json" }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/.gitignore: -------------------------------------------------------------------------------- 1 | /index.wasm 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/.prettierignore: -------------------------------------------------------------------------------- 1 | /tests/fixtures/**/output.* 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # swc-plugin-node-cjs-interop 2 | 3 | ## 0.1.20 4 | 5 | ### Patch Changes 6 | 7 | - 2dedf62: fix(deps): update swc monorepo 8 | 9 | ## 0.1.19 10 | 11 | ### Patch Changes 12 | 13 | - 78f2c14: fix(deps): update rust crate swc_core to v9 14 | 15 | ## 0.1.18 16 | 17 | ### Patch Changes 18 | 19 | - 2088681: fix(deps): update swc monorepo (major) 20 | 21 | ## 0.1.17 22 | 23 | ### Patch Changes 24 | 25 | - 89ad992: fix(deps): update rust crate swc_core to v5.0.4 26 | 27 | ## 0.1.16 28 | 29 | ### Patch Changes 30 | 31 | - 9b39921: update swc monorepo (major) 32 | 33 | ## 0.1.15 34 | 35 | ### Patch Changes 36 | 37 | - 9a01834: fix(deps): update swc monorepo 38 | 39 | ## 0.1.14 40 | 41 | ### Patch Changes 42 | 43 | - 5c6d95a: Update swc monorepo 44 | 45 | ## 0.1.13 46 | 47 | ### Patch Changes 48 | 49 | - cdcee32: chore(deps): update swc monorepo 50 | 51 | ## 0.1.12 52 | 53 | ### Patch Changes 54 | 55 | - 392044a: chore(deps): update swc monorepo 56 | 57 | ## 0.1.11 58 | 59 | ### Patch Changes 60 | 61 | - e788916: chore(deps): update swc monorepo 62 | 63 | ## 0.1.10 64 | 65 | ### Patch Changes 66 | 67 | - a5beaff: chore(deps): update swc monorepo 68 | 69 | ## 0.1.9 70 | 71 | ### Patch Changes 72 | 73 | - 5c4ec19: Update swc monorepo 74 | 75 | ## 0.1.8 76 | 77 | ### Patch Changes 78 | 79 | - be3d9ec: Update swc monorepo 80 | - a696577: Update serde 81 | - bcdc489: Update serde_json 82 | 83 | ## 0.1.7 84 | 85 | ### Patch Changes 86 | 87 | - 9437e69: Bump swc_core to 0.87 88 | 89 | ## 0.1.6 90 | 91 | ### Patch Changes 92 | 93 | - de38484: Update swc monorepo 94 | 95 | ## 0.1.5 96 | 97 | ### Patch Changes 98 | 99 | - 37a65b0: Support dynamic imports 100 | 101 | ## 0.1.4 102 | 103 | ### Patch Changes 104 | 105 | - b16c3ee: Update swc monorepo 106 | 107 | ## 0.1.3 108 | 109 | ### Patch Changes 110 | 111 | - 972e632: Add a variant helper that wraps default one more time. 112 | 113 | The helper is called `interopImportCJSNamespaceT` and you can use it just like `interopImportCJSNamespace` 114 | (except that it assumes `loose = true` by default). 115 | 116 | To use it from the Babel/SWC plugins, use the `packagesT` option instead of `packages`. 117 | 118 | ## 0.1.2 119 | 120 | - Update dependencies 121 | 122 | ## 0.1.1 123 | 124 | Add `loose` option to support more libraries. 125 | 126 | ## 0.1.0 127 | 128 | Initial release. 129 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swc-plugin-node-cjs-interop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | # rlib for testing 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } 12 | serde_json = { version = "1.0.113", default-features = false, features = ["alloc"] } 13 | swc_core = { version = "10.0.0", features = ["ecma_plugin_transform"] } 14 | 15 | [dev-dependencies] 16 | swc_ecma_parser = "6.0.0" 17 | swc_ecma_transforms_testing = "7.0.0" 18 | swc_ecma_transforms_typescript = "7.0.0" 19 | testing = "5.0.0" 20 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/README.md: -------------------------------------------------------------------------------- 1 | # `swc-plugin-node-cjs-interop`: fix the default import interoperability issue in Node.js 2 | 3 | ## The problem to solve 4 | 5 | Consider the following modules: 6 | 7 | ```javascript 8 | // a.js 9 | 10 | export default function greet() { 11 | console.log("Hello, world!"); 12 | } 13 | ``` 14 | 15 | ```javascript 16 | // b.js 17 | 18 | import greet from "a.js"; 19 | 20 | greet(); 21 | ``` 22 | 23 | They usually work, unless the following conditions are met: 24 | 25 | - `a.js` (the module being imported) is a **simulated ESM**. That is, the module is transpiled as a CommonJS module (by Babel or TypeScript) before execution. And, 26 | - `b.js` (the importing module) is a **native ESM**, That is, the module is run on Node.js' native ES Module support. 27 | 28 | You can reproduce the above condition by placing the following files: 29 | 30 | ```javascript 31 | // a.cjs 32 | 33 | "use strict"; 34 | 35 | Object.defineProperty(exports, "__esModule", { 36 | value: true, 37 | }); 38 | exports.default = greet; 39 | 40 | function greet() { 41 | console.log("Hello, world!"); 42 | } 43 | ``` 44 | 45 | ```javascript 46 | // b.mjs 47 | 48 | import greet from "./a.cjs"; 49 | 50 | greet(); 51 | ``` 52 | 53 | ``` 54 | $ node ./b.mjs 55 | ./b.mjs:3 56 | greet(); 57 | ^ 58 | 59 | TypeError: greet is not a function 60 | at ./b.mjs:3:1 61 | at ModuleJob.run (node:internal/modules/esm/module_job:185:25) 62 | at async Promise.all (index 0) 63 | at async ESMLoader.import (node:internal/modules/esm/loader:281:24) 64 | at async loadESM (node:internal/process/esm_loader:88:5) 65 | at async handleMainPromise (node:internal/modules/run_main:65:12) 66 | ``` 67 | 68 | The following packages solve the problem: 69 | 70 | - [`babel-plugin-node-cjs-interop`](https://npmjs.com/package/babel-plugin-node-cjs-interop) / `swc-plugin-node-cjs-interop` (this package): automatically inserts the compatibility wrapper. 71 | - [`node-cjs-interop`](https://npmjs.com/package/node-cjs-interop): allows manually wrapping the exported value. 72 | 73 | ## Getting started 74 | 75 | Install the SWC plugin: 76 | 77 | ``` 78 | npm install -D swc-plugin-node-cjs-interop 79 | # or: 80 | yarn add -D swc-plugin-node-cjs-interop 81 | ``` 82 | 83 | Configure it in your SWC configuration: 84 | 85 | ```javascript 86 | // .swcrc 87 | 88 | { 89 | "jsc": { 90 | "experimental": { 91 | "plugins": [ 92 | ["swc-plugin-node-cjs-interop", { 93 | // List the packages you're experiencing problems 94 | // importing from Node.js' native ESM. 95 | // I.e. list the packages in the simulated ESM format. 96 | "packages": ["styled-components", "@babel/helper-plugin-test-runner"] 97 | }] 98 | ] 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | If you're unsure what packages to specify in the configuration, [`node-cjs-interop-finder`](https://npmjs.com/package/node-cjs-interop-finder) might be useful: 105 | 106 | ```javascript 107 | npx node-cjs-interop-finder 108 | ``` 109 | 110 | ## Caveats 111 | 112 | ### re-exports 113 | 114 | `export ... from` is not detected yet. 115 | 116 | ### Effects to tree-shaking 117 | 118 | It may negatively affect tree-shaking because the wrapper function makes it difficult to analyze unused imports. 119 | 120 | ### Observing the export's newest value 121 | 122 | In ES Modules, a module can change its exports' values after it's been loaded, and the importing module can observe the update. 123 | 124 | ```javascript 125 | export let counter = 0; 126 | export function countUp() { 127 | counter++; 128 | } 129 | ``` 130 | 131 | ```javascript 132 | import { counter, countUp } from "./counter.js"; 133 | 134 | console.log(counter); // => 0 135 | countUp(); 136 | console.log(counter); // => 1 137 | ``` 138 | 139 | Here the semantics is also not preserved when importing a simulated ESM module from a native ESM; the importing module always observes the initial value. 140 | 141 | This plugin also restores the intended behavior of updating exported variables. Therefore, in rare cases, you may find your program behaving differently regarding named imports other than `default`. 142 | 143 | ### Accesses to missing named exports 144 | 145 | When using native ESM, it is an error to import a non-existent named export: 146 | 147 | ```javascript 148 | // Error 149 | import { nonExistent } from "./a.js"; 150 | ``` 151 | 152 | With `babel-plugin-node-cjs-interop`, it silently returns `undefined`. 153 | 154 | ### False positives 155 | 156 | This plugin uses the `__esModule` flag to detect Babel's ES Modules support. Technically speaking, it may lead to false positives in a rare situation. Consider the following code: 157 | 158 | ```javascript 159 | // a.js (simulated ESM) 160 | export const val = 42; 161 | ``` 162 | 163 | ```javascript 164 | // b.js (native ESM; this plugin is not applied) 165 | export * from "a.js"; 166 | export const foo = 100; 167 | ``` 168 | 169 | ```javascript 170 | // c.js (native ESM) 171 | import { foo } from "b.js"; 172 | ``` 173 | 174 | When this plugin is applied to the last import, the import will return an unintended value. 175 | 176 | ## Options 177 | 178 | ### packages 179 | 180 | - type: `string[]` 181 | - default: `[]` 182 | 183 | List of packages to apply the transformation. If empty, no transformation is applied. 184 | 185 | Currently there is no way to apply the transformation to all imports. 186 | 187 | You can use [`node-cjs-interop-finder`](https://npmjs.com/package/node-cjs-interop-finder) to figure out packages that might suit in the option: 188 | 189 | ```javascript 190 | npx node-cjs-interop-finder 191 | ``` 192 | 193 | ### packagesT 194 | 195 | - type: `string[]` 196 | - default: `[]` 197 | 198 | Similar to `packages`, but applies the "twisted" variant instead. In this mode, the imported `default` value 199 | would be the namespace object rather than the proper default export. 200 | 201 | This is useful when you have `"module"` or `"moduleResolution"` set to `"nodenext"` or `"node16"` 202 | in your `tsconfig.json` and you need to import `default` from a "dual package" in which the 203 | type definitions are recognized in the `.cts` mode. 204 | 205 | ### loose 206 | 207 | - type: `boolean` 208 | - default: `false` 209 | 210 | Skips check of the `ns.__esModule` export. Note that it still checks 211 | for `ns.default.__esModule`. 212 | 213 | This is useful if a transpiler or a bundler generates a module 214 | which cjs-module-lexer cannot correctly parse. 215 | 216 | ### useRuntime 217 | 218 | - type: `boolean` 219 | - default: `false` 220 | 221 | Imports helpers from the `node-cjs-interop` package. You need to add `node-cjs-interop` to your package's dependency. 222 | 223 | ## How it works 224 | 225 | ### How Node.js and Babel act differently with CommonJS interoperation 226 | 227 | ES Modules and CommonJS Modules use different models for module exports: 228 | 229 | - In **ES Modules** (ESM), a module exports **multiple values**, each having a name in string. 230 | - In **CommonJS Modules** (CJS), a module exports a **single unnamed value**. 231 | 232 | For this reason, exports are mapped differently in each direction: 233 | 234 | - Named imports in ESM are mapped to exports in CJS in the following ways: 235 | - The import named `default` is mapped to the single exported value. 236 | - Other named imports are mapped to each property of the single exported value. 237 | - Imports in CJS are mapped to the exported namespaces (the record containing all exported values) from ESM. 238 | 239 | And it has a big downside: **`default` exports from ESM don't round-trip** when shipped through CJS. This is problematic for transpilers like Babel, which needs to embed the semantics of ESM into CJS. Therefore Babel implements the additional rule to the above: 240 | 241 | - If a CJS module defines `exports.__esModule` as a truthy value, ESM's importing rule is modified so that the `default` import is treated uniformly with other named imports (i.e. mapped to `exports.default` rather than `exports`). 242 | 243 | And Babel defines `exports.__esModule` when transpiling modules in ESM to CJS format. 244 | 245 | However, Node.js didn't implement the rule for `__esModule`. It's [a result of careful consideration](https://github.com/nodejs/node/pull/40892#issuecomment-974654787), but is still problematic to gradual migration from CJS to ESM. 246 | 247 | ### node-cjs-interop 248 | 249 | node-cjs-interop works around the problem by **replacing the namespace object** appropriately. This is achieved by the following function: 250 | 251 | ```javascript 252 | function interopImportCJSNamespace(ns) { 253 | return ns.__esModule && ns.default && ns.default.__esModule 254 | ? // `ns.default` likely comes from Babel's ESM emulation. 255 | // In this case `ns.default` works as the namespace object. 256 | ns.default 257 | : // Original namespace object 258 | ns; 259 | } 260 | ``` 261 | 262 | swc-node-cjs-interop first transforms named imports: 263 | 264 | ```javascript 265 | import f, { a } from "mod"; 266 | console.log({ f, a }); 267 | ``` 268 | 269 | to namespace imports: 270 | 271 | ```javascript 272 | import * as ns from "mod"; 273 | console.log({ f: ns.default, a: ns.a }); 274 | ``` 275 | 276 | and then wraps the namespace object by the aforementioned function: 277 | 278 | ```javascript 279 | import * as nsOrig from "mod"; 280 | const ns = interopImportCJSNamespace(nsOrig); 281 | console.log({ f: ns.default, a: ns.a }); 282 | ``` 283 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swc-plugin-node-cjs-interop", 3 | "description": "An SWC plugin to fix the default import interoperability issue in Node.js", 4 | "keywords": [ 5 | "swc-plugin", 6 | "commonjs", 7 | "node", 8 | "esm", 9 | "mjs", 10 | "cjs", 11 | "default import" 12 | ], 13 | "homepage": "https://github.com/qnighy/node-cjs-interop#readme", 14 | "bugs": { 15 | "url": "https://github.com/qnighy/node-cjs-interop/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/qnighy/node-cjs-interop.git", 20 | "directory": "packages/swc-plugin-node-cjs-interop" 21 | }, 22 | "license": "MIT", 23 | "author": "Masaki Hara ", 24 | "version": "0.1.20", 25 | "main": "./index.wasm", 26 | "scripts": { 27 | "build:rust": "cargo build --target wasm32-unknown-unknown && cp ../../target/wasm32-unknown-unknown/debug/swc_plugin_node_cjs_interop.wasm ./index.wasm", 28 | "build:rust:release": "cargo build --release --target wasm32-unknown-unknown && cp ../../target/wasm32-unknown-unknown/release/swc_plugin_node_cjs_interop.wasm ./index.wasm", 29 | "prepack": "$npm_execpath run build:rust:release" 30 | }, 31 | "files": [ 32 | "./src/**/*.rs", 33 | "./Cargo.toml", 34 | "./index.wasm" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/src/options.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] 4 | #[serde(deny_unknown_fields)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct Options { 7 | #[serde(default)] 8 | pub packages: Vec, 9 | #[serde(default)] 10 | pub packages_t: Vec, 11 | #[serde(default)] 12 | pub loose: bool, 13 | #[serde(default)] 14 | pub use_runtime: bool, 15 | } 16 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/src/package_name.rs: -------------------------------------------------------------------------------- 1 | pub(crate) fn get_package_name(module_path: &str) -> Option<&str> { 2 | if module_path.is_empty() { 3 | return None; 4 | } 5 | 6 | let first = module_path.as_bytes()[0]; 7 | if first == b'/' || first == b'.' { 8 | return None; 9 | } 10 | 11 | #[allow(clippy::collapsible_else_if)] 12 | if first == b'@' { 13 | #[allow(clippy::question_mark)] 14 | let index1 = if let Some(index) = module_path.find('/') { 15 | index 16 | } else { 17 | return None; 18 | }; 19 | 20 | if let Some(index2_rel) = module_path[index1 + 1..].find('/') { 21 | let index2 = index1 + 1 + index2_rel; 22 | Some(&module_path[..index2]) 23 | } else { 24 | Some(module_path) 25 | } 26 | } else { 27 | if let Some(index) = module_path.find('/') { 28 | Some(&module_path[..index]) 29 | } else { 30 | Some(module_path) 31 | } 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn test_empty() { 41 | assert_eq!(get_package_name(""), None); 42 | } 43 | 44 | #[test] 45 | fn test_absolute_paths() { 46 | assert_eq!(get_package_name("/"), None); 47 | assert_eq!(get_package_name("/foo"), None); 48 | assert_eq!(get_package_name("/foo/bar"), None); 49 | } 50 | 51 | #[test] 52 | fn test_relative_paths() { 53 | assert_eq!(get_package_name("."), None); 54 | assert_eq!(get_package_name("./"), None); 55 | assert_eq!(get_package_name("./foo"), None); 56 | assert_eq!(get_package_name("./foo/bar"), None); 57 | assert_eq!(get_package_name("../foo"), None); 58 | assert_eq!(get_package_name("../foo/bar"), None); 59 | } 60 | 61 | #[test] 62 | fn test_unscoped_main_module() { 63 | assert_eq!(get_package_name("foo"), Some("foo")); 64 | assert_eq!(get_package_name("foo-bar"), Some("foo-bar")); 65 | assert_eq!(get_package_name("foo.bar"), Some("foo.bar")); 66 | } 67 | 68 | #[test] 69 | fn test_unscoped_sub_module() { 70 | assert_eq!(get_package_name("foo/index"), Some("foo")); 71 | assert_eq!(get_package_name("foo-bar/server"), Some("foo-bar")); 72 | assert_eq!(get_package_name("foo.bar/dist/index.js"), Some("foo.bar")); 73 | } 74 | 75 | #[test] 76 | fn test_scoped_main_module() { 77 | assert_eq!(get_package_name("@test/foo"), Some("@test/foo")); 78 | assert_eq!(get_package_name("@test/foo-bar"), Some("@test/foo-bar")); 79 | assert_eq!(get_package_name("@test/foo.bar"), Some("@test/foo.bar")); 80 | } 81 | 82 | #[test] 83 | fn test_scoped_sub_module() { 84 | assert_eq!(get_package_name("@test/foo/index"), Some("@test/foo")); 85 | assert_eq!( 86 | get_package_name("@test/foo-bar/server"), 87 | Some("@test/foo-bar") 88 | ); 89 | assert_eq!( 90 | get_package_name("@test/foo.bar/dist/index.js"), 91 | Some("@test/foo.bar") 92 | ); 93 | } 94 | 95 | #[test] 96 | fn test_scoped_incomplete() { 97 | assert_eq!(get_package_name("@test"), None); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/braces-only-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import {} from "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/braces-only-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/call-replacement/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | 3 | console.log(f()); 4 | console.log(f.g()); 5 | console.log(f?.()); 6 | console.log(f`foo`); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/call-replacement/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log((0, _ns.f)()); 7 | console.log(_ns.f.g()); 8 | console.log((0, _ns.f)?.()); 9 | console.log((0, _ns.f)`foo`); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log(_ns.default); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/dynamic-imports/input.mjs: -------------------------------------------------------------------------------- 1 | const M = await import("mod"); 2 | console.log(M); 3 | 4 | import("mod").then((M2) => { 5 | console.log(M2); 6 | }); 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/dynamic-imports/output.mjs: -------------------------------------------------------------------------------- 1 | function _interopImportCJSNamespace(ns, loose) { 2 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule 3 | ? ns.default 4 | : ns; 5 | } 6 | const M = _interopImportCJSNamespace(/*#__CJS__*/ await import("mod")); 7 | console.log(M); 8 | /*#__CJS__*/ import("mod") 9 | .then((ns) => _interopImportCJSNamespace(ns)) 10 | .then((M2) => { 11 | console.log(M2); 12 | }); 13 | export {}; 14 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/multiple-import-decls/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod1"; 2 | import { a as x, b } from "mod2"; 3 | import "mod1"; 4 | import {} from "mod1"; 5 | import { default as f2 } from "mod1"; 6 | import * as ns2 from "mod2"; 7 | import * as ns2b from "mod2"; 8 | 9 | console.log({ f, x, b, f2, ns2, ns2b }); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/multiple-import-decls/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const ns2b = _interopImportCJSNamespace(_nsOrig4); 2 | const ns2 = _interopImportCJSNamespace(_nsOrig3); 3 | const _ns = _interopImportCJSNamespace(_nsOrig2); 4 | const _ns1 = _interopImportCJSNamespace(_nsOrig1); 5 | const _ns2 = _interopImportCJSNamespace(_nsOrig); 6 | function _interopImportCJSNamespace(ns, loose) { 7 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 8 | } 9 | import * as _nsOrig from "mod1"; 10 | /*#__CJS__*/ import * as _nsOrig1 from "mod2"; 11 | import "mod1"; 12 | import "mod1"; 13 | /*#__CJS__*/ import * as _nsOrig2 from "mod1"; 14 | /*#__CJS__*/ import * as _nsOrig3 from "mod2"; 15 | /*#__CJS__*/ import * as _nsOrig4 from "mod2"; 16 | console.log({ 17 | f: _ns2.default, 18 | x: _ns1.a, 19 | b: _ns1.b, 20 | f2: _ns.default, 21 | ns2, 22 | ns2b 23 | }); 24 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/multiple-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f, { a as x, b } from "mod"; 2 | console.log({ f, x, b }); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/multiple-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log({ 7 | f: _ns.default, 8 | x: _ns.a, 9 | b: _ns.b 10 | }); 11 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/named-default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { default as f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/named-default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log(_ns.default); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/named-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/named-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log(_ns.f); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/namespace-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import * as M from "mod"; 2 | console.log(M); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/namespace-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const M = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log(M); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/side-effect-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/side-effect-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/todo-forbidden-rewriting/input.mjs: -------------------------------------------------------------------------------- 1 | import { v } from "mod"; 2 | 3 | v = 42; 4 | [v] = [42]; 5 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/basic/todo-forbidden-rewriting/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | v = 42; 7 | [v] = [ 8 | 42 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/braces-only-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import {} from "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/braces-only-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/call-replacement/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | 3 | console.log(f()); 4 | console.log(f.g()); 5 | console.log(f?.()); 6 | console.log(f`foo`); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/call-replacement/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log((0, _ns.f)()); 7 | console.log(_ns.f.g()); 8 | console.log((0, _ns.f)?.()); 9 | console.log((0, _ns.f)`foo`); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log(_ns.default); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/multiple-import-decls/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod1"; 2 | import { a as x, b } from "mod2"; 3 | import "mod1"; 4 | import {} from "mod1"; 5 | import { default as f2 } from "mod1"; 6 | import * as ns2 from "mod2"; 7 | import * as ns2b from "mod2"; 8 | 9 | console.log({ f, x, b, f2, ns2, ns2b }); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/multiple-import-decls/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const ns2b = _interopImportCJSNamespace(_nsOrig4, true); 2 | const ns2 = _interopImportCJSNamespace(_nsOrig3, true); 3 | const _ns = _interopImportCJSNamespace(_nsOrig2, true); 4 | const _ns1 = _interopImportCJSNamespace(_nsOrig1, true); 5 | const _ns2 = _interopImportCJSNamespace(_nsOrig, true); 6 | function _interopImportCJSNamespace(ns, loose) { 7 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 8 | } 9 | import * as _nsOrig from "mod1"; 10 | /*#__CJS__*/ import * as _nsOrig1 from "mod2"; 11 | import "mod1"; 12 | import "mod1"; 13 | /*#__CJS__*/ import * as _nsOrig2 from "mod1"; 14 | /*#__CJS__*/ import * as _nsOrig3 from "mod2"; 15 | /*#__CJS__*/ import * as _nsOrig4 from "mod2"; 16 | console.log({ 17 | f: _ns2.default, 18 | x: _ns1.a, 19 | b: _ns1.b, 20 | f2: _ns.default, 21 | ns2, 22 | ns2b 23 | }); 24 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/multiple-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f, { a as x, b } from "mod"; 2 | console.log({ f, x, b }); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/multiple-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log({ 7 | f: _ns.default, 8 | x: _ns.a, 9 | b: _ns.b 10 | }); 11 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/named-default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { default as f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/named-default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log(_ns.default); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/named-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/named-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log(_ns.f); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/namespace-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import * as M from "mod"; 2 | console.log(M); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/namespace-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const M = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | console.log(M); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/side-effect-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/side-effect-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/todo-forbidden-rewriting/input.mjs: -------------------------------------------------------------------------------- 1 | import { v } from "mod"; 2 | 3 | v = 42; 4 | [v] = [42]; 5 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/loose/todo-forbidden-rewriting/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig, true); 2 | function _interopImportCJSNamespace(ns, loose) { 3 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 4 | } 5 | import * as _nsOrig from "mod"; 6 | v = 42; 7 | [v] = [ 8 | 42 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/package-filtering/test/input.mjs: -------------------------------------------------------------------------------- 1 | import foo from "foo"; 2 | import bar from "bar"; 3 | import baz from "baz"; 4 | import foobar from "foobar"; 5 | import scopedFoo from "@scoped/foo"; 6 | import scopedBar from "@scoped/bar"; 7 | import scopedBaz from "@scoped/baz"; 8 | import scopedFoobar from "@scoped/foobar"; 9 | import scoopedFoo from "@scooped/foo"; 10 | import scoopedBar from "@scooped/bar"; 11 | import scoopedBaz from "@scooped/baz"; 12 | import scoopedFoobar from "@scooped/foobar"; 13 | import fooSub from "foo/sub.js"; 14 | import barSub from "bar/sub.js"; 15 | import bazSub from "baz/sub"; 16 | import relative from "./relative.js"; 17 | import absolute from "/path/to/absolute.js"; 18 | 19 | console.log({ 20 | foo, 21 | bar, 22 | baz, 23 | foobar, 24 | scopedFoo, 25 | scopedBar, 26 | scopedBaz, 27 | scopedFoobar, 28 | scoopedFoo, 29 | scoopedBar, 30 | scoopedBaz, 31 | scoopedFoobar, 32 | fooSub, 33 | barSub, 34 | bazSub, 35 | relative, 36 | absolute, 37 | }); 38 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/package-filtering/test/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig5); 2 | const _ns1 = _interopImportCJSNamespace(_nsOrig4); 3 | const _ns2 = _interopImportCJSNamespace(_nsOrig3); 4 | const _ns3 = _interopImportCJSNamespace(_nsOrig2); 5 | const _ns4 = _interopImportCJSNamespace(_nsOrig1); 6 | const _ns5 = _interopImportCJSNamespace(_nsOrig); 7 | function _interopImportCJSNamespace(ns, loose) { 8 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 9 | } 10 | import * as _nsOrig from "foo"; 11 | /*#__CJS__*/ import * as _nsOrig1 from "bar"; 12 | import baz from "baz"; 13 | import foobar from "foobar"; 14 | /*#__CJS__*/ import * as _nsOrig2 from "@scoped/foo"; 15 | /*#__CJS__*/ import * as _nsOrig3 from "@scoped/bar"; 16 | import scopedBaz from "@scoped/baz"; 17 | import scopedFoobar from "@scoped/foobar"; 18 | import scoopedFoo from "@scooped/foo"; 19 | import scoopedBar from "@scooped/bar"; 20 | import scoopedBaz from "@scooped/baz"; 21 | import scoopedFoobar from "@scooped/foobar"; 22 | /*#__CJS__*/ import * as _nsOrig4 from "foo/sub.js"; 23 | /*#__CJS__*/ import * as _nsOrig5 from "bar/sub.js"; 24 | import bazSub from "baz/sub"; 25 | import relative from "./relative.js"; 26 | import absolute from "/path/to/absolute.js"; 27 | console.log({ 28 | foo: _ns5.default, 29 | bar: _ns4.default, 30 | baz, 31 | foobar, 32 | scopedFoo: _ns3.default, 33 | scopedBar: _ns2.default, 34 | scopedBaz, 35 | scopedFoobar, 36 | scoopedFoo, 37 | scoopedBar, 38 | scoopedBaz, 39 | scoopedFoobar, 40 | fooSub: _ns1.default, 41 | barSub: _ns.default, 42 | bazSub, 43 | relative, 44 | absolute 45 | }); 46 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/braces-only-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import {} from "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/braces-only-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/call-replacement/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | 3 | console.log(f()); 4 | console.log(f.g()); 5 | console.log(f?.()); 6 | console.log(f`foo`); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/call-replacement/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | import * as _nsOrig from "mod"; 9 | console.log((0, _ns.f)()); 10 | console.log(_ns.f.g()); 11 | console.log((0, _ns.f)?.()); 12 | console.log((0, _ns.f)`foo`); 13 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | import * as _nsOrig from "mod"; 9 | console.log(_ns.default); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/multiple-import-decls/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod1"; 2 | import { a as x, b } from "mod2"; 3 | import "mod1"; 4 | import {} from "mod1"; 5 | import { default as f2 } from "mod1"; 6 | import * as ns2 from "mod2"; 7 | import * as ns2b from "mod2"; 8 | 9 | console.log({ f, x, b, f2, ns2, ns2b }); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/multiple-import-decls/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const ns2b = _interopImportCJSNamespaceT(_nsOrig4); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | const ns2 = _interopImportCJSNamespaceT1(_nsOrig3); 9 | function _interopImportCJSNamespaceT1(ns) { 10 | return ns.default && ns.default.__esModule ? ns : { 11 | ...ns, 12 | default: ns 13 | }; 14 | } 15 | const _ns = _interopImportCJSNamespaceT2(_nsOrig2); 16 | function _interopImportCJSNamespaceT2(ns) { 17 | return ns.default && ns.default.__esModule ? ns : { 18 | ...ns, 19 | default: ns 20 | }; 21 | } 22 | const _ns1 = _interopImportCJSNamespaceT3(_nsOrig1); 23 | function _interopImportCJSNamespaceT3(ns) { 24 | return ns.default && ns.default.__esModule ? ns : { 25 | ...ns, 26 | default: ns 27 | }; 28 | } 29 | const _ns2 = _interopImportCJSNamespaceT4(_nsOrig); 30 | function _interopImportCJSNamespaceT4(ns) { 31 | return ns.default && ns.default.__esModule ? ns : { 32 | ...ns, 33 | default: ns 34 | }; 35 | } 36 | import * as _nsOrig from "mod1"; 37 | /*#__CJS__*/ import * as _nsOrig1 from "mod2"; 38 | import "mod1"; 39 | import "mod1"; 40 | /*#__CJS__*/ import * as _nsOrig2 from "mod1"; 41 | /*#__CJS__*/ import * as _nsOrig3 from "mod2"; 42 | /*#__CJS__*/ import * as _nsOrig4 from "mod2"; 43 | console.log({ 44 | f: _ns2.default, 45 | x: _ns1.a, 46 | b: _ns1.b, 47 | f2: _ns.default, 48 | ns2, 49 | ns2b 50 | }); 51 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/multiple-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f, { a as x, b } from "mod"; 2 | console.log({ f, x, b }); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/multiple-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | import * as _nsOrig from "mod"; 9 | console.log({ 10 | f: _ns.default, 11 | x: _ns.a, 12 | b: _ns.b 13 | }); 14 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/named-default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { default as f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/named-default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | import * as _nsOrig from "mod"; 9 | console.log(_ns.default); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/named-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/named-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | import * as _nsOrig from "mod"; 9 | console.log(_ns.f); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/namespace-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import * as M from "mod"; 2 | console.log(M); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/namespace-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const M = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | import * as _nsOrig from "mod"; 9 | console.log(M); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/side-effect-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/side-effect-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/todo-forbidden-rewriting/input.mjs: -------------------------------------------------------------------------------- 1 | import { v } from "mod"; 2 | 3 | v = 42; 4 | [v] = [42]; 5 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/ts-twisted/todo-forbidden-rewriting/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespaceT(_nsOrig); 2 | function _interopImportCJSNamespaceT(ns) { 3 | return ns.default && ns.default.__esModule ? ns : { 4 | ...ns, 5 | default: ns 6 | }; 7 | } 8 | import * as _nsOrig from "mod"; 9 | v = 42; 10 | [v] = [ 11 | 42 12 | ]; 13 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/braces-only-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import {} from "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/braces-only-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/call-replacement/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | 3 | console.log(f()); 4 | console.log(f.g()); 5 | console.log(f?.()); 6 | console.log(f`foo`); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/call-replacement/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | import * as _nsOrig from "mod"; 4 | console.log((0, _ns.f)()); 5 | console.log(_ns.f.g()); 6 | console.log((0, _ns.f)?.()); 7 | console.log((0, _ns.f)`foo`); 8 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | import * as _nsOrig from "mod"; 4 | console.log(_ns.default); 5 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/multiple-import-decls/input.mjs: -------------------------------------------------------------------------------- 1 | import f from "mod1"; 2 | import { a as x, b } from "mod2"; 3 | import "mod1"; 4 | import {} from "mod1"; 5 | import { default as f2 } from "mod1"; 6 | import * as ns2 from "mod2"; 7 | import * as ns2b from "mod2"; 8 | 9 | console.log({ f, x, b, f2, ns2, ns2b }); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/multiple-import-decls/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const ns2b = _interopImportCJSNamespace(_nsOrig4); 2 | const ns2 = _interopImportCJSNamespace(_nsOrig3); 3 | const _ns = _interopImportCJSNamespace(_nsOrig2); 4 | const _ns1 = _interopImportCJSNamespace(_nsOrig1); 5 | const _ns2 = _interopImportCJSNamespace(_nsOrig); 6 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 7 | import * as _nsOrig from "mod1"; 8 | /*#__CJS__*/ import * as _nsOrig1 from "mod2"; 9 | import "mod1"; 10 | import "mod1"; 11 | /*#__CJS__*/ import * as _nsOrig2 from "mod1"; 12 | /*#__CJS__*/ import * as _nsOrig3 from "mod2"; 13 | /*#__CJS__*/ import * as _nsOrig4 from "mod2"; 14 | console.log({ 15 | f: _ns2.default, 16 | x: _ns1.a, 17 | b: _ns1.b, 18 | f2: _ns.default, 19 | ns2, 20 | ns2b 21 | }); 22 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/multiple-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import f, { a as x, b } from "mod"; 2 | console.log({ f, x, b }); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/multiple-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | import * as _nsOrig from "mod"; 4 | console.log({ 5 | f: _ns.default, 6 | x: _ns.a, 7 | b: _ns.b 8 | }); 9 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/named-default-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { default as f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/named-default-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | import * as _nsOrig from "mod"; 4 | console.log(_ns.default); 5 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/named-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import { f } from "mod"; 2 | console.log(f); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/named-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | import * as _nsOrig from "mod"; 4 | console.log(_ns.f); 5 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/namespace-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import * as M from "mod"; 2 | console.log(M); 3 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/namespace-imports/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const M = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | import * as _nsOrig from "mod"; 4 | console.log(M); 5 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/side-effect-imports/input.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/side-effect-imports/output.mjs: -------------------------------------------------------------------------------- 1 | import "mod"; 2 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/todo-forbidden-rewriting/input.mjs: -------------------------------------------------------------------------------- 1 | import { v } from "mod"; 2 | 3 | v = 42; 4 | [v] = [42]; 5 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/use-runtime/todo-forbidden-rewriting/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 2 | import { interopImportCJSNamespace as _interopImportCJSNamespace } from "node-cjs-interop"; 3 | import * as _nsOrig from "mod"; 4 | v = 42; 5 | [v] = [ 6 | 42 7 | ]; 8 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/with-react/jsx/input.mjs: -------------------------------------------------------------------------------- 1 | import { Component, Foo } from "mod"; 2 | import * as ns from "mod2"; 3 | 4 | console.log(); 5 | console.log(); 6 | console.log(); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/with-react/jsx/output.mjs: -------------------------------------------------------------------------------- 1 | /*#__CJS__*/ const ns = _interopImportCJSNamespace(_nsOrig1); 2 | const _ns = _interopImportCJSNamespace(_nsOrig); 3 | function _interopImportCJSNamespace(ns, loose) { 4 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 5 | } 6 | import * as _nsOrig from "mod"; 7 | /*#__CJS__*/ import * as _nsOrig1 from "mod2"; 8 | console.log(<_ns.Component />); 9 | console.log(<_ns.Foo.Component />); 10 | console.log(); 11 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/with-typescript/basic/input.mts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import type { T } from "mod"; 3 | import { x, type T2 } from "mod"; 4 | import * as ns from "mod"; 5 | import * as ns2 from "mod"; 6 | 7 | export type Foo = T & T2 & ns2.T; 8 | 9 | console.log({ x, ns }); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/with-typescript/basic/output.mjs: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | const ns2 = _interopImportCJSNamespace(_nsOrig2); 3 | const ns = _interopImportCJSNamespace(_nsOrig1); 4 | const _ns = _interopImportCJSNamespace(_nsOrig); 5 | function _interopImportCJSNamespace(ns, loose) { 6 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 7 | } 8 | /*#__CJS__*/ import * as _nsOrig from "mod"; 9 | /*#__CJS__*/ import * as _nsOrig1 from "mod"; 10 | /*#__CJS__*/ import * as _nsOrig2 from "mod"; 11 | console.log({ 12 | x: _ns.x, 13 | ns 14 | }); 15 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/with-typescript/dual-use/input.mts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { T } from "mod"; 3 | 4 | export type Foo = T; 5 | 6 | console.log({ T }); 7 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/fixtures/with-typescript/dual-use/output.mjs: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /*#__CJS__*/ const _ns = _interopImportCJSNamespace(_nsOrig); 3 | function _interopImportCJSNamespace(ns, loose) { 4 | return (loose || ns.__esModule) && ns.default && ns.default.__esModule ? ns.default : ns; 5 | } 6 | import * as _nsOrig from "mod"; 7 | console.log({ 8 | T: _ns.T 9 | }); 10 | -------------------------------------------------------------------------------- /packages/swc-plugin-node-cjs-interop/tests/index.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use swc_core::common::Mark; 4 | use swc_core::ecma::visit::visit_mut_pass; 5 | use swc_ecma_parser::{EsSyntax, Syntax}; 6 | use swc_ecma_transforms_testing::{test_fixture, FixtureTestConfig}; 7 | use swc_ecma_transforms_typescript::strip; 8 | use swc_plugin_node_cjs_interop::{TransformOptions, TransformVisitor}; 9 | 10 | #[testing::fixture("tests/fixtures/basic/*/input.mjs")] 11 | fn test_basic(input: PathBuf) { 12 | let output = input.with_file_name("output.mjs"); 13 | test_fixture( 14 | Syntax::Es(EsSyntax::default()), 15 | &|t| { 16 | visit_mut_pass(TransformVisitor::new( 17 | t.comments.clone(), 18 | TransformOptions { 19 | packages: vec!["mod".to_owned(), "mod1".to_owned(), "mod2".to_owned()], 20 | packages_t: vec![], 21 | loose: false, 22 | use_runtime: false, 23 | }, 24 | )) 25 | }, 26 | &input, 27 | &output, 28 | FixtureTestConfig::default(), 29 | ); 30 | } 31 | 32 | #[testing::fixture("tests/fixtures/loose/*/input.mjs")] 33 | fn test_loose(input: PathBuf) { 34 | let output = input.with_file_name("output.mjs"); 35 | test_fixture( 36 | Syntax::Es(EsSyntax::default()), 37 | &|t| { 38 | visit_mut_pass(TransformVisitor::new( 39 | t.comments.clone(), 40 | TransformOptions { 41 | packages: vec!["mod".to_owned(), "mod1".to_owned(), "mod2".to_owned()], 42 | packages_t: vec![], 43 | loose: true, 44 | use_runtime: false, 45 | }, 46 | )) 47 | }, 48 | &input, 49 | &output, 50 | FixtureTestConfig::default(), 51 | ); 52 | } 53 | 54 | #[testing::fixture("tests/fixtures/ts-twisted/*/input.mjs")] 55 | fn test_ts_twisted(input: PathBuf) { 56 | let output = input.with_file_name("output.mjs"); 57 | test_fixture( 58 | Syntax::Es(EsSyntax::default()), 59 | &|t| { 60 | visit_mut_pass(TransformVisitor::new( 61 | t.comments.clone(), 62 | TransformOptions { 63 | packages: vec![], 64 | packages_t: vec!["mod".to_owned(), "mod1".to_owned(), "mod2".to_owned()], 65 | loose: false, 66 | use_runtime: false, 67 | }, 68 | )) 69 | }, 70 | &input, 71 | &output, 72 | FixtureTestConfig::default(), 73 | ); 74 | } 75 | 76 | #[testing::fixture("tests/fixtures/package-filtering/*/input.mjs")] 77 | fn test_package_filtering(input: PathBuf) { 78 | let output = input.with_file_name("output.mjs"); 79 | test_fixture( 80 | Syntax::Es(EsSyntax::default()), 81 | &|t| { 82 | visit_mut_pass(TransformVisitor::new( 83 | t.comments.clone(), 84 | TransformOptions { 85 | packages: vec![ 86 | "foo".to_owned(), 87 | "bar".to_owned(), 88 | "@scoped/foo".to_owned(), 89 | "@scoped/bar".to_owned(), 90 | ], 91 | packages_t: vec![], 92 | loose: false, 93 | use_runtime: false, 94 | }, 95 | )) 96 | }, 97 | &input, 98 | &output, 99 | FixtureTestConfig::default(), 100 | ); 101 | } 102 | 103 | #[testing::fixture("tests/fixtures/use-runtime/*/input.mjs")] 104 | fn test_use_runtime(input: PathBuf) { 105 | let output = input.with_file_name("output.mjs"); 106 | test_fixture( 107 | Syntax::Es(EsSyntax::default()), 108 | &|t| { 109 | visit_mut_pass(TransformVisitor::new( 110 | t.comments.clone(), 111 | TransformOptions { 112 | packages: vec!["mod".to_owned(), "mod1".to_owned(), "mod2".to_owned()], 113 | packages_t: vec![], 114 | loose: false, 115 | use_runtime: true, 116 | }, 117 | )) 118 | }, 119 | &input, 120 | &output, 121 | FixtureTestConfig::default(), 122 | ); 123 | } 124 | 125 | #[testing::fixture("tests/fixtures/with-react/*/input.mjs")] 126 | fn test_with_react(input: PathBuf) { 127 | let output = input.with_file_name("output.mjs"); 128 | test_fixture( 129 | Syntax::Es(EsSyntax { 130 | jsx: true, 131 | ..Default::default() 132 | }), 133 | &|t| { 134 | visit_mut_pass(TransformVisitor::new( 135 | t.comments.clone(), 136 | TransformOptions { 137 | packages: vec!["mod".to_owned(), "mod2".to_owned()], 138 | packages_t: vec![], 139 | loose: false, 140 | use_runtime: false, 141 | }, 142 | )) 143 | }, 144 | &input, 145 | &output, 146 | FixtureTestConfig::default(), 147 | ); 148 | } 149 | 150 | #[testing::fixture("tests/fixtures/with-typescript/*/input.mts")] 151 | fn test_with_typescript(input: PathBuf) { 152 | let output = input.with_file_name("output.mjs"); 153 | test_fixture( 154 | Syntax::Typescript(Default::default()), 155 | &|t| { 156 | ( 157 | visit_mut_pass(TransformVisitor::new( 158 | t.comments.clone(), 159 | TransformOptions { 160 | packages: vec!["mod".to_owned()], 161 | packages_t: vec![], 162 | loose: false, 163 | use_runtime: false, 164 | }, 165 | )), 166 | strip(Mark::new(), Mark::new()), 167 | ) 168 | }, 169 | &input, 170 | &output, 171 | FixtureTestConfig::default(), 172 | ); 173 | } 174 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "postUpdateOptions": ["yarnDedupeHighest"], 5 | "schedule": ["on the 1st day of February", "on the 1st day of August"], 6 | "packageRules": [ 7 | { 8 | "matchManagers": ["npm"], 9 | "matchDepTypes": ["dependencies", "peerDependencies"], 10 | "rangeStrategy": "widen" 11 | }, 12 | { 13 | "matchManagers": ["npm"], 14 | "matchDepTypes": ["devDependencies"], 15 | "rangeStrategy": "bump" 16 | }, 17 | { 18 | "matchSourceUrlPrefixes": ["https://github.com/swc-project/swc"], 19 | "matchUpdateTypes": ["digest", "patch", "minor", "major"], 20 | "schedule": ["on the 1st day of the month"] 21 | } 22 | ] 23 | } 24 | --------------------------------------------------------------------------------