├── .babelrc ├── .github ├── release.yml └── workflows │ └── test.yml ├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── package.json ├── transforms ├── __testfixtures__ │ ├── module-exports-to-export-default │ │ ├── basic-case.input.js │ │ ├── basic-case.output.js │ │ ├── multiple-exports.input.js │ │ └── multiple-exports.output.js │ ├── module-exports-to-named-export │ │ ├── basic-case-exports.input.js │ │ ├── basic-case-exports.output.js │ │ ├── basic-case-module-exports.input.js │ │ ├── basic-case-module-exports.output.js │ │ ├── exports-variable.input.js │ │ └── exports-variable.output.js │ ├── require-to-import-default │ │ ├── bad-argument.input.js │ │ ├── bad-argument.output.js │ │ ├── basic-case-with-comment.input.js │ │ ├── basic-case-with-comment.output.js │ │ ├── basic-case.input.js │ │ ├── basic-case.output.js │ │ ├── chained-requires-with-rest.input.js │ │ ├── chained-requires-with-rest.output.js │ │ ├── chained-requires.input.js │ │ ├── chained-requires.output.js │ │ ├── destructure-multiple-require.input.js │ │ ├── destructure-multiple-require.output.js │ │ ├── destructure-require-alias.input.js │ │ ├── destructure-require-alias.output.js │ │ ├── destructure-require.input.js │ │ ├── destructure-require.output.js │ │ ├── too-many-arguments.input.js │ │ └── too-many-arguments.output.js │ ├── require-with-props-to-named-import │ │ ├── alias.input.js │ │ ├── alias.output.js │ │ ├── basic-case-with-comment.input.js │ │ ├── basic-case-with-comment.output.js │ │ ├── basic-case.input.js │ │ └── basic-case.output.js │ ├── single-require.input.js │ └── single-require.output.js ├── __tests__ │ ├── .eslintrc.yml │ ├── module-exports-to-export-default-test.js │ ├── module-exports-to-named-export-test.js │ ├── require-to-import-default-test.js │ ├── require-with-props-to-named-import-test.js │ └── single-require-test.js ├── __testutils__ │ └── defineTests.js ├── index.js ├── module-exports-to-export-default.js ├── module-exports-to-named-export.js ├── require-to-import-default.js ├── require-with-props-to-named-import.js ├── single-require.js └── utils │ ├── filters.js │ └── logger.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - 'Type: Meta' 5 | - 'Type: Question' 6 | - 'Type: Release' 7 | 8 | categories: 9 | - title: Security Fixes 10 | labels: ['Type: Security'] 11 | - title: Breaking Changes 12 | labels: ['Type: Breaking Change'] 13 | - title: Features 14 | labels: ['Type: Feature'] 15 | - title: Bug Fixes 16 | labels: ['Type: Bug'] 17 | - title: Documentation 18 | labels: ['Type: Documentation'] 19 | - title: Refactoring 20 | labels: ['Type: Refactoring'] 21 | - title: Testing 22 | labels: ['Type: Testing'] 23 | - title: Maintenance 24 | labels: ['Type: Maintenance'] 25 | - title: CI 26 | labels: ['Type: CI'] 27 | - title: Dependency Updates 28 | labels: ['Type: Dependencies', "dependencies"] 29 | - title: Other Changes 30 | labels: ['*'] 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push, pull_request] 3 | env: 4 | CI: true 5 | jobs: 6 | test: 7 | name: "Test on Node.js ${{ matrix.node-version }}" 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node-version: [ 16, 18 ] 12 | steps: 13 | - name: checkout 14 | uses: actions/checkout@v2 15 | - name: setup Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: Install 20 | run: yarn install 21 | - name: Test 22 | run: yarn test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | lib 4 | dist -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | transforms/__testfixtures__ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ulysse Buonomo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # commonjs-to-es-module-codemod [![Actions Status: test](https://github.com/azu/commonjs-to-es-module-codemod/workflows/test/badge.svg)](https://github.com/azu/commonjs-to-es-module-codemod/actions?query=workflow%3A"test") 2 | 3 | jscodeshift codemod that convert CommonJS(require/exports) to ES Modules(import/export) for JavaScript/TypeScript 4 | 5 | ## Support Patterns 6 | 7 | ### Exports 8 | 9 | - [x] named export: `module.exports.foo = foo` & `exports.foo = foo` to `export { foo }` 10 | - [x] named export: `module.exports.bar = foo` & `exports.bar = foo` to `export { foo as bar }` 11 | - [x] default export: `module.exports = foo` to `export default foo` 12 | - [x] ignore multiple `module.exports = x` 13 | 14 | ### Imports 15 | 16 | - [x] `require("mod")` to `import "mod"` 17 | - [x] `const foo = require("mod")` to `import foo from "mod"` 18 | - [x] `const { foo } = require("mod")` to `import { foo } from "mod"` 19 | - [x] `const foo = require("mod").foo` to `import { foo } from "mod"` 20 | - [x] `const bar = require("mod").foo` to `import { foo as bar } from "mod"` 21 | - [ ] `const o = { foo: require("foo") } ` to `import foo from "mod"; const o = { foo }` 22 | 23 | ## Usage 24 | 25 | `commonjs-to-es-module-codemod` is published on [npm](https://www.npmjs.com/package/commonjs-to-es-module-codemod). 26 | 27 | You can convert `index.js` and `index.ts` to ES Modules using [jscodeshift](https://github.com/facebook/jscodeshift) and [unpkg](https://unpkg.com/). 28 | 29 | # Install jscodeshift 30 | npm install --global jscodeshift 31 | # Transform using latest version 32 | LATEST_VERSION=$(npm view commonjs-to-es-module-codemod version) 33 | jscodeshift -t "https://unpkg.com/commonjs-to-es-module-codemod@${LATEST_VERSION}/dist/index.js" "index.js" 34 | # Transform TypeScript 35 | jscodeshift -t "https://unpkg.com/commonjs-to-es-module-codemod@${LATEST_VERSION}/dist/index.js" --extensions ts "index.ts" 36 | 37 | Convert `src/*.js`: 38 | 39 | LATEST_VERSION=$(npm view commonjs-to-es-module-codemod version) 40 | find src -name "*.js" | xargs jscodeshift -t "https://unpkg.com/commonjs-to-es-module-codemod@${LATEST_VERSION}/dist/index.js" 41 | 42 | ## Related 43 | 44 | - [azu/eslint-cjs-to-esm: ESLint wrapper for migration from CJS to ESM.](https://github.com/azu/eslint-cjs-to-esm) 45 | 46 | ## Tests 47 | 48 | yarn test 49 | 50 | ## Contributing 51 | 52 | 1. Fork it! 53 | 2. Create your feature branch: `git checkout -b my-new-feature` 54 | 3. Commit your changes: `git commit -am 'Add some feature'` 55 | 4. Push to the branch: `git push origin my-new-feature` 56 | 5. Submit a pull request :D 57 | 58 | ## License 59 | 60 | MIT © azu 61 | 62 | It includes [BuonOmo/CommonJS-to-ES6-codemod](https://github.com/BuonOmo/CommonJS-to-ES6-codemod). 63 | 64 | MIT © Ulysse Buonomo 65 | 66 | 67 | ## Related 68 | 69 | - [lebab/lebab: Turn your ES5 code into readable ES6. Lebab does the opposite of what Babel does.](https://github.com/lebab/lebab) 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "commonjs-to-es-module-codemod", 3 | "version": "0.5.7", 4 | "description": "jscodeshift codemod that convert CommonJS(require/exports) to ES Modules(import/export) for JavaScript/TypeScript", 5 | "files": [ 6 | "dist" 7 | ], 8 | "source": "transforms/index.js", 9 | "main": "dist/index.js", 10 | "scripts": { 11 | "build": "microbundle", 12 | "test": "jest", 13 | "updateSnapshot": "jest -u", 14 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,css}\"" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/azu/commonjs-to-es-module-codemod.git" 19 | }, 20 | "keywords": [ 21 | "amd", 22 | "es6", 23 | "modules", 24 | "import", 25 | "require", 26 | "export", 27 | "exports", 28 | "codemod", 29 | "jscodeshift" 30 | ], 31 | "author": "azu ", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/azu/commonjs-to-es-module-codemod/issues" 35 | }, 36 | "homepage": "https://github.com/azu/commonjs-to-es-module-codemod#readme", 37 | "jest": { 38 | "roots": [ 39 | "transforms" 40 | ] 41 | }, 42 | "devDependencies": { 43 | "@babel/cli": "^7.10.5", 44 | "@babel/core": "^7.0.0", 45 | "@babel/preset-env": "^7.0.0", 46 | "babel-core": "^7.0.0-bridge.0", 47 | "babel-jest": "^23.4.2", 48 | "coveralls": "^3.1.0", 49 | "jest": "^26.4.2", 50 | "lint-staged": "^10.2.11", 51 | "microbundle": "^0.13.0", 52 | "prettier": "^2.2.1" 53 | }, 54 | "dependencies": { 55 | "jscodeshift": "^0.10.0" 56 | }, 57 | "prettier": { 58 | "singleQuote": false, 59 | "printWidth": 120, 60 | "tabWidth": 4, 61 | "trailingComma": "none" 62 | }, 63 | "lint-staged": { 64 | "*.{js,jsx,ts,tsx,css}": [ 65 | "prettier --write" 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-export-default/basic-case.input.js: -------------------------------------------------------------------------------- 1 | // comment 2 | module.exports = function () { 3 | return 42; 4 | }; -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-export-default/basic-case.output.js: -------------------------------------------------------------------------------- 1 | // comment 2 | export default function () { 3 | return 42; 4 | }; -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-export-default/multiple-exports.input.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return 42; 3 | }; 4 | 5 | module.exports = 42; -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-export-default/multiple-exports.output.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return 42; 3 | }; 4 | 5 | module.exports = 42; -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-named-export/basic-case-exports.input.js: -------------------------------------------------------------------------------- 1 | exports.a = function () { 2 | return "a"; 3 | }; 4 | // comment 5 | exports.b = function () { 6 | return "b"; 7 | }; 8 | 9 | const c = () => { 10 | return 42; 11 | }; 12 | exports.c = c; 13 | exports.d = c; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-named-export/basic-case-exports.output.js: -------------------------------------------------------------------------------- 1 | export const a = function () { 2 | return "a"; 3 | }; 4 | 5 | // comment 6 | export const b = function () { 7 | return "b"; 8 | }; 9 | 10 | const c = () => { 11 | return 42; 12 | }; 13 | export { c }; 14 | export { c as d }; 15 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-named-export/basic-case-module-exports.input.js: -------------------------------------------------------------------------------- 1 | module.exports.a = function () { 2 | return "a"; 3 | }; 4 | // comment 5 | module.exports.b = function () { 6 | return "b"; 7 | }; 8 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-named-export/basic-case-module-exports.output.js: -------------------------------------------------------------------------------- 1 | export const a = function () { 2 | return "a"; 3 | }; 4 | 5 | // comment 6 | export const b = function () { 7 | return "b"; 8 | }; 9 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-named-export/exports-variable.input.js: -------------------------------------------------------------------------------- 1 | const a = () => { 2 | return 42; 3 | }; 4 | module.exports.a = a; 5 | // comment 6 | module.exports.b = a; 7 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/module-exports-to-named-export/exports-variable.output.js: -------------------------------------------------------------------------------- 1 | const a = () => { 2 | return 42; 3 | }; 4 | export { a }; 5 | 6 | // comment 7 | export { a as b }; 8 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/bad-argument.input.js: -------------------------------------------------------------------------------- 1 | const Lib = require('li' + 'b'); 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/bad-argument.output.js: -------------------------------------------------------------------------------- 1 | const Lib = require('li' + 'b'); 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/basic-case-with-comment.input.js: -------------------------------------------------------------------------------- 1 | // hoge 2 | const Lib = require('lib'); 3 | 4 | // hoge2 5 | const Lib2 = require('lib2'); 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/basic-case-with-comment.output.js: -------------------------------------------------------------------------------- 1 | // hoge 2 | import Lib from 'lib'; 3 | 4 | // hoge2 5 | import Lib2 from 'lib2'; 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/basic-case.input.js: -------------------------------------------------------------------------------- 1 | const Lib = require('lib'); 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/basic-case.output.js: -------------------------------------------------------------------------------- 1 | import Lib from 'lib'; 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/chained-requires-with-rest.input.js: -------------------------------------------------------------------------------- 1 | const A = require('a'), 2 | B = require('b'), 3 | C = require('c'), 4 | foo = 'bar', 5 | baz = 42; 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/chained-requires-with-rest.output.js: -------------------------------------------------------------------------------- 1 | import A from 'a'; 2 | import B from 'b'; 3 | import C from 'c'; 4 | const foo = 'bar', baz = 42; 5 | 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/chained-requires.input.js: -------------------------------------------------------------------------------- 1 | const A = require('a'), 2 | B = require('b'), 3 | C = require('c'); 4 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/chained-requires.output.js: -------------------------------------------------------------------------------- 1 | import A from 'a'; 2 | import B from 'b'; 3 | import C from 'c'; 4 | 5 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/destructure-multiple-require.input.js: -------------------------------------------------------------------------------- 1 | const { a, b, c } = require("lib"); 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/destructure-multiple-require.output.js: -------------------------------------------------------------------------------- 1 | import { a, b, c } from "lib"; 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/destructure-require-alias.input.js: -------------------------------------------------------------------------------- 1 | const { k: v } = require("lib"); 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/destructure-require-alias.output.js: -------------------------------------------------------------------------------- 1 | import { k as v } from "lib"; 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/destructure-require.input.js: -------------------------------------------------------------------------------- 1 | const { method } = require("lib"); 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/destructure-require.output.js: -------------------------------------------------------------------------------- 1 | import { method } from "lib"; 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/too-many-arguments.input.js: -------------------------------------------------------------------------------- 1 | const Lib = require('lib', 'nop'); 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-to-import-default/too-many-arguments.output.js: -------------------------------------------------------------------------------- 1 | const Lib = require('lib', 'nop'); 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-with-props-to-named-import/alias.input.js: -------------------------------------------------------------------------------- 1 | const b = require('lib').a; 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-with-props-to-named-import/alias.output.js: -------------------------------------------------------------------------------- 1 | import { a as b } from 'lib'; 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-with-props-to-named-import/basic-case-with-comment.input.js: -------------------------------------------------------------------------------- 1 | // comment a 2 | const a = require('lib').a; 3 | 4 | // comment b 5 | const b = require('lib').a; -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-with-props-to-named-import/basic-case-with-comment.output.js: -------------------------------------------------------------------------------- 1 | // comment a 2 | import { a } from 'lib'; 3 | 4 | // comment b 5 | import { a as b } from 'lib'; -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-with-props-to-named-import/basic-case.input.js: -------------------------------------------------------------------------------- 1 | const a = require('lib').a; 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/require-with-props-to-named-import/basic-case.output.js: -------------------------------------------------------------------------------- 1 | import { a } from 'lib'; 2 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/single-require.input.js: -------------------------------------------------------------------------------- 1 | require('top'); 2 | 3 | function parent() { 4 | require('scoped'); 5 | } 6 | 7 | // comment 8 | require('bottom'); 9 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/single-require.output.js: -------------------------------------------------------------------------------- 1 | import 'top'; 2 | 3 | function parent() { 4 | require('scoped'); 5 | } 6 | 7 | // comment 8 | import 'bottom'; 9 | 10 | -------------------------------------------------------------------------------- /transforms/__tests__/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: ../../.eslintrc.js 2 | env: 3 | jest: true 4 | -------------------------------------------------------------------------------- /transforms/__tests__/module-exports-to-export-default-test.js: -------------------------------------------------------------------------------- 1 | import { defineTests } from "../__testutils__/defineTests"; 2 | 3 | describe("module-exports-to-export-default", () => { 4 | defineTests(__dirname, "module-exports-to-export-default"); 5 | }); 6 | -------------------------------------------------------------------------------- /transforms/__tests__/module-exports-to-named-export-test.js: -------------------------------------------------------------------------------- 1 | import { defineTests } from "../__testutils__/defineTests"; 2 | 3 | describe("module-exports-to-named-export", () => { 4 | defineTests(__dirname, "module-exports-to-named-export"); 5 | }); 6 | -------------------------------------------------------------------------------- /transforms/__tests__/require-to-import-default-test.js: -------------------------------------------------------------------------------- 1 | import { defineTests } from "../__testutils__/defineTests"; 2 | 3 | describe("require-to-import-default", () => { 4 | defineTests(__dirname, "require-to-import-default"); 5 | }); 6 | -------------------------------------------------------------------------------- /transforms/__tests__/require-with-props-to-named-import-test.js: -------------------------------------------------------------------------------- 1 | import { defineTests } from "../__testutils__/defineTests"; 2 | 3 | describe("require-with-props-to-named-import", () => { 4 | defineTests(__dirname, "require-with-props-to-named-import"); 5 | }); 6 | -------------------------------------------------------------------------------- /transforms/__tests__/single-require-test.js: -------------------------------------------------------------------------------- 1 | import { defineTest } from "jscodeshift/dist/testUtils"; 2 | 3 | defineTest(__dirname, "single-require"); 4 | -------------------------------------------------------------------------------- /transforms/__testutils__/defineTests.js: -------------------------------------------------------------------------------- 1 | import { defineTest } from "jscodeshift/dist/testUtils"; 2 | import fs from "fs"; 3 | import path from "path" 4 | 5 | export function defineTests(dirName, transformName) { 6 | const inputFileSuffixRegex = /\.input\.js$/i; 7 | const tests = fs.readdirSync(path.resolve(dirName, "../__testfixtures__", transformName)) 8 | .filter(fileName => inputFileSuffixRegex.test(fileName)) 9 | .map(fileName => fileName.replace(inputFileSuffixRegex, '')); 10 | 11 | tests.forEach((test) => { 12 | defineTest(dirName, transformName, { 13 | silent: true 14 | }, `${transformName}/${test}`); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /transforms/index.js: -------------------------------------------------------------------------------- 1 | import toImportDefault from "./require-to-import-default"; 2 | import toNamedImport from "./require-with-props-to-named-import"; 3 | import toExportDefault from "./module-exports-to-export-default"; 4 | import toNamedExport from "./module-exports-to-named-export"; 5 | import singleRequire from "./single-require"; 6 | 7 | const transformScripts = (fileInfo, api, options) => { 8 | return [toExportDefault, toNamedImport, singleRequire, toImportDefault, toNamedExport].reduce((input, script) => { 9 | return script( 10 | { 11 | source: input 12 | }, 13 | api, 14 | options 15 | ); 16 | }, fileInfo.source); 17 | }; 18 | 19 | module.exports = transformScripts; 20 | -------------------------------------------------------------------------------- /transforms/module-exports-to-export-default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transform 3 | * 4 | * module.exports = *; 5 | * 6 | * to 7 | * 8 | * export default *; 9 | * 10 | * Only on global context 11 | */ 12 | 13 | import Logger from "./utils/logger"; 14 | import { isTopNode } from "./utils/filters"; 15 | 16 | function transformer(file, api, options) { 17 | const j = api.jscodeshift; 18 | const _isTopNode = (path) => isTopNode(j, path); 19 | const logger = new Logger(file, options); 20 | 21 | // ------------------------------------------------------------------ SEARCH 22 | const nodes = j(file.source) 23 | .find(j.ExpressionStatement, { 24 | expression: { 25 | left: { 26 | object: { 27 | name: "module" 28 | }, 29 | property: { 30 | name: "exports" 31 | } 32 | }, 33 | operator: "=" 34 | } 35 | }) 36 | .filter(_isTopNode); 37 | 38 | if (nodes.length > 1) { 39 | logger.error( 40 | "There should not be more than one `module.exports` declaration in a file. Aborting transformation" 41 | ); 42 | return file.source; 43 | } 44 | 45 | logger.log(`${nodes.length} nodes will be transformed`); 46 | 47 | // ----------------------------------------------------------------- REPLACE 48 | return nodes 49 | .replaceWith((path) => { 50 | const newNode = j.exportDefaultDeclaration(path.node.expression.right); 51 | newNode.comments = path.node.comments; 52 | return newNode; 53 | }) 54 | .toSource(); 55 | } 56 | 57 | export default transformer; 58 | -------------------------------------------------------------------------------- /transforms/module-exports-to-named-export.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transform 3 | * 4 | * module.exports.a = *; 5 | * 6 | * to 7 | * 8 | * export const a = *; 9 | * 10 | * Only on global context 11 | */ 12 | 13 | import Logger from "./utils/logger"; 14 | import { isTopNode } from "./utils/filters"; 15 | 16 | function transformer(file, api, options) { 17 | const j = api.jscodeshift; 18 | const _isTopNode = (path) => isTopNode(j, path); 19 | const logger = new Logger(file, options); 20 | 21 | // ------------------------------------------------------------------ SEARCH 22 | // https://astexplorer.net/#/gist/334f5bd39244c7feab38a3fd3cc0ce7f/c332a5b4cbd1a9718e644febf2dce9e9bd032d1b 23 | const ast = j(file.source) 24 | const moduleExportNodes = ast 25 | .find(j.ExpressionStatement, { 26 | expression: { 27 | left: { 28 | object: { 29 | object: { 30 | name: "module" 31 | }, 32 | property: { 33 | name: "exports" 34 | } 35 | } 36 | // property is target 37 | }, 38 | operator: "=" 39 | } 40 | }) 41 | .filter(_isTopNode); 42 | 43 | const exportNodes = ast 44 | .find(j.ExpressionStatement, { 45 | expression: { 46 | left: { 47 | object: { 48 | name: "exports" 49 | } 50 | // property is target 51 | }, 52 | operator: "=" 53 | } 54 | }) 55 | .filter(_isTopNode); 56 | 57 | logger.log(`${moduleExportNodes.length + exportNodes.length} nodes will be transformed`); 58 | // ----------------------------------------------------------------- REPLACE 59 | const replace = (path) => { 60 | const node = path.node; 61 | // Identifier node 62 | const id = node.expression.left.property; 63 | const init = node.expression.right; 64 | // module.export.b = a 65 | // → export { a as b } 66 | if (id.type === "Identifier" && init.type === "Identifier") { 67 | const newNode = j.exportNamedDeclaration(null, [j.exportSpecifier.from({ exported: id, local: init })]); 68 | newNode.comments = node.comments; 69 | return newNode; 70 | } 71 | // https://babeljs.io/docs/en/babel-types#exportnameddeclaration 72 | const declaration = j.variableDeclaration("const", [j.variableDeclarator(id, init)]); 73 | const newNode = j.exportNamedDeclaration(declaration); 74 | newNode.comments = node.comments; 75 | return newNode; 76 | }; 77 | 78 | exportNodes 79 | .replaceWith(replace) 80 | moduleExportNodes 81 | .replaceWith(replace) 82 | return ast.toSource(); 83 | } 84 | 85 | export default transformer; 86 | -------------------------------------------------------------------------------- /transforms/require-to-import-default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transform 3 | * 4 | * const Lib = require('lib'); 5 | * 6 | * to 7 | * 8 | * import Lib from 'lib'; 9 | * 10 | * Only on global context 11 | */ 12 | 13 | // on https://astexplorer.net: Press ctrl+space for code completion 14 | 15 | import Logger from "./utils/logger"; 16 | import { isTopNode } from "./utils/filters"; 17 | 18 | function transformer(file, api, options) { 19 | const j = api.jscodeshift; 20 | const _isTopNode = (path) => isTopNode(j, path); 21 | const logger = new Logger(file, options); 22 | 23 | // ------------------------------------------------------------------ SEARCH 24 | const nodes = j(file.source) 25 | .find(j.VariableDeclaration, { 26 | declarations: [ 27 | { 28 | init: { 29 | type: "CallExpression", 30 | callee: { 31 | name: "require" 32 | } 33 | // property 34 | } 35 | } 36 | ] 37 | }) 38 | .filter(_isTopNode); 39 | 40 | logger.log(`${nodes.length} nodes will be transformed`); 41 | 42 | // ----------------------------------------------------------------- REPLACE 43 | return nodes 44 | .replaceWith((path) => { 45 | const rest = []; 46 | const imports = []; 47 | for (const declaration of path.node.declarations) { 48 | const isRequire = 49 | declaration.init !== null && 50 | declaration.init.type === "CallExpression" && 51 | declaration.init.callee.name === "require"; 52 | // https://astexplorer.net/#/gist/49d222c86971cbe3e5744958989dc061/b0b5d0c31a6e74f63365c2ec1f195d3227c49621 53 | // require("a").a 54 | const isRequireWithProp = isRequire && declaration.init.property !== undefined; 55 | if (isRequireWithProp) { 56 | if (declaration.id.type === "Identifier") { 57 | // default import 58 | const sourcePath = declaration.init.arguments.shift(); 59 | if (declaration.init.arguments.length) { 60 | logger.error( 61 | `${logger.lines(declaration)} too many arguments.` + "Aborting transformation" 62 | ); 63 | return file.source; 64 | } 65 | if (!j.Literal.check(sourcePath)) { 66 | logger.error( 67 | `${logger.lines(declaration)} bad argument.` + 68 | "Expecting a string literal, got " + 69 | j(sourcePath).toSource() + 70 | "`. Aborting transformation" 71 | ); 72 | return file.source; 73 | } 74 | if (declaration?.init?.property.type === "Identifier") { 75 | logger.log("Unknown declaration", declaration); 76 | } 77 | const specify = j.importSpecifier(declaration.init.property, declaration?.init?.property); 78 | imports.push(j.importDeclaration([specify], sourcePath)); 79 | } else if (declaration.id.type === "ObjectPattern") { 80 | // named import 81 | // const { c } = require("mod").a 82 | logger.log("Does not support pattern", declaration); 83 | } 84 | } else if (isRequire) { 85 | if (declaration.id.type === "Identifier") { 86 | // default import 87 | const importSpecifier = j.importDefaultSpecifier(declaration.id); 88 | const sourcePath = declaration.init.arguments.shift(); 89 | if (declaration.init.arguments.length) { 90 | logger.error( 91 | `${logger.lines(declaration)} too many arguments.` + "Aborting transformation" 92 | ); 93 | return file.source; 94 | } 95 | if (!j.Literal.check(sourcePath)) { 96 | logger.error( 97 | `${logger.lines(declaration)} bad argument.` + 98 | "Expecting a string literal, got " + 99 | j(sourcePath).toSource() + 100 | "`. Aborting transformation" 101 | ); 102 | return file.source; 103 | } 104 | imports.push(j.importDeclaration([importSpecifier], sourcePath)); 105 | } else if (declaration.id.type === "ObjectPattern") { 106 | // named import 107 | // const { specifierA, specifierB } = require("mod") 108 | // ObjectPattern 109 | const specifiers = declaration.id.properties.map((property) => { 110 | const key = j.identifier(property.key.name); 111 | const value = j.identifier(property.value.name); 112 | return j.importSpecifier(key, value); 113 | }); 114 | const sourcePath = declaration.init.arguments.shift(); 115 | if (declaration.init.arguments.length) { 116 | logger.error( 117 | `${logger.lines(declaration)} too many arguments.` + "Aborting transformation" 118 | ); 119 | return file.source; 120 | } 121 | if (!j.Literal.check(sourcePath)) { 122 | logger.error( 123 | `${logger.lines(declaration)} bad argument.` + 124 | "Expecting a string literal, got " + 125 | j(sourcePath).toSource() + 126 | "`. Aborting transformation" 127 | ); 128 | return file.source; 129 | } 130 | imports.push(j.importDeclaration(specifiers, sourcePath)); 131 | } 132 | } else { 133 | rest.push(declaration); 134 | } 135 | } 136 | 137 | if (imports.length > 0) { 138 | imports[0].comments = path.node.comments; 139 | } 140 | 141 | if (rest.length > 0) { 142 | logger.warn(`${logger.lines(path.node)} introduced leftover`); 143 | return [...imports, j.variableDeclaration(path.node.kind, rest)]; 144 | } 145 | return imports; 146 | }) 147 | .toSource(); 148 | } 149 | 150 | export default transformer; 151 | -------------------------------------------------------------------------------- /transforms/require-with-props-to-named-import.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transform 3 | * 4 | * const a = require('lib').a; 5 | * const b = require('lib').a; 6 | * 7 | * to 8 | * 9 | * import { a } from 'lib'; 10 | * import { a as b } from 'lib'; 11 | * 12 | * Only on global context 13 | */ 14 | 15 | // on https://astexplorer.net: Press ctrl+space for code completion 16 | 17 | import Logger from "./utils/logger"; 18 | import { isTopNode } from "./utils/filters"; 19 | 20 | function transformer(file, api, options) { 21 | const j = api.jscodeshift; 22 | const _isTopNode = (path) => isTopNode(j, path); 23 | const logger = new Logger(file, options); 24 | 25 | // ------------------------------------------------------------------ SEARCH 26 | const nodes = j(file.source) 27 | .find(j.VariableDeclaration, { 28 | declarations: [ 29 | { 30 | init: { 31 | type: "MemberExpression", 32 | object: { 33 | type: "CallExpression", 34 | callee: { 35 | name: "require" 36 | } 37 | } 38 | // property 39 | } 40 | } 41 | ] 42 | }) 43 | .filter(_isTopNode); 44 | 45 | logger.log(`${nodes.length} nodes will be transformed`); 46 | 47 | // ----------------------------------------------------------------- REPLACE 48 | return nodes 49 | .replaceWith((path) => { 50 | const rest = []; 51 | const imports = []; 52 | for (const declaration of path.node.declarations) { 53 | // https://astexplorer.net/#/gist/49d222c86971cbe3e5744958989dc061/b0b5d0c31a6e74f63365c2ec1f195d3227c49621 54 | // const a = require("a").a 55 | const isRequireWithProp = 56 | declaration.init !== null && 57 | declaration.init.type === "MemberExpression" && 58 | declaration.init.object.type === "CallExpression" && 59 | declaration.init.object.callee.name === "require" && 60 | declaration.init.property !== undefined; 61 | if (isRequireWithProp) { 62 | if (declaration.id.type === "Identifier") { 63 | const sourcePath = declaration.init.object.arguments.shift(); 64 | if (declaration.init.object.arguments.length) { 65 | logger.error( 66 | `${logger.lines(declaration)} too many arguments.` + "Aborting transformation" 67 | ); 68 | return file.source; 69 | } 70 | if (!j.Literal.check(sourcePath)) { 71 | logger.error( 72 | `${logger.lines(declaration)} bad argument.` + 73 | "Expecting a string literal, got " + 74 | j(sourcePath).toSource() + 75 | "`. Aborting transformation" 76 | ); 77 | return file.source; 78 | } 79 | if (declaration?.init?.property.type !== "Identifier") { 80 | logger.log("Unknown declaration", declaration); 81 | return file.source; 82 | } 83 | const specify = j.importSpecifier(declaration.init.property, declaration.id); 84 | imports.push(j.importDeclaration([specify], sourcePath)); 85 | } else if (declaration.id.type === "ObjectPattern") { 86 | // named import 87 | // const { c } = require("mod").a 88 | logger.log("Does not support pattern", declaration); 89 | } 90 | } else { 91 | rest.push(declaration); 92 | } 93 | } 94 | 95 | if (imports.length > 0) { 96 | imports[0].comments = path.node.comments; 97 | } 98 | 99 | if (rest.length > 0) { 100 | logger.warn(`${logger.lines(path.node)} introduced leftover`); 101 | return [...imports, j.variableDeclaration(path.node.kind, rest)]; 102 | } 103 | return imports; 104 | }) 105 | .toSource(); 106 | } 107 | 108 | export default transformer; 109 | -------------------------------------------------------------------------------- /transforms/single-require.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transform 3 | * 4 | * require('lib'); 5 | * 6 | * to 7 | * 8 | * import 'lib'; 9 | * 10 | * Only on global context 11 | */ 12 | 13 | import Logger from "./utils/logger"; 14 | import { isTopNode } from "./utils/filters"; 15 | 16 | function transformer(file, api, options) { 17 | const j = api.jscodeshift; 18 | const _isTopNode = (path) => isTopNode(j, path); 19 | const ಠ_ಠ = new Logger(file, options); 20 | 21 | // ------------------------------------------------------------------ SEARCH 22 | const nodes = j(file.source) 23 | .find(j.ExpressionStatement, { 24 | expression: { 25 | callee: { 26 | name: "require" 27 | } 28 | } 29 | }) 30 | .filter(_isTopNode); 31 | 32 | ಠ_ಠ.log(`${nodes.length} nodes will be transformed`); 33 | 34 | // ----------------------------------------------------------------- REPLACE 35 | return nodes 36 | .replaceWith((path) => { 37 | const sourcePath = path.node.expression.arguments.pop(); 38 | const newNode = j.importDeclaration([], sourcePath); 39 | newNode.comments = path.node.comments; 40 | return newNode; 41 | }) 42 | .toSource(); 43 | } 44 | 45 | export default transformer; 46 | -------------------------------------------------------------------------------- /transforms/utils/filters.js: -------------------------------------------------------------------------------- 1 | const isTopNode = (j, path) => j.Program.check(path.parent.value); 2 | 3 | export { isTopNode }; 4 | -------------------------------------------------------------------------------- /transforms/utils/logger.js: -------------------------------------------------------------------------------- 1 | class Logger { 2 | constructor(file, options) { 3 | this.prefix = `${file.path}:`; 4 | this.silent = options.silent; 5 | this.verbose = options.verbose; 6 | } 7 | 8 | log(...text) { 9 | if (!this.silent && this.verbose > 0) console.log("[LOG]", this.prefix, ...text); 10 | } 11 | 12 | warn(...text) { 13 | if (!this.silent) console.warn("[WARNING]", this.prefix, ...text); 14 | } 15 | 16 | error(...text) { 17 | if (!this.silent) console.error("[ERROR]", this.prefix, ...text); 18 | } 19 | 20 | /** 21 | * Show lines in the form (lines to ) from a node. 22 | * If and correspond to the same line, show (line ). 23 | * 24 | * @param {Node} node 25 | * @return {String} 26 | */ 27 | lines(node) { 28 | if (node.loc) { 29 | if (node.loc.start.line === node.loc.end.line) return `(line ${node.loc.start.line})`; 30 | return `(lines ${node.loc.start.line} to ${node.loc.end.line})`; 31 | } 32 | return ""; 33 | } 34 | } 35 | 36 | export default Logger; 37 | --------------------------------------------------------------------------------