UNPKG

cjs-es

Version:

Transform CommonJS module into ES module.

484 lines (341 loc) 11.2 kB
cjs-es ====== [![.github/workflows/build.yml](https://github.com/eight04/cjs-es/actions/workflows/build.yml/badge.svg)](https://github.com/eight04/cjs-es/actions/workflows/build.yml) [![codecov](https://codecov.io/gh/eight04/cjs-es/branch/master/graph/badge.svg)](https://codecov.io/gh/eight04/cjs-es) [![install size](https://packagephobia.now.sh/badge?p=cjs-es)](https://packagephobia.now.sh/result?p=cjs-es) Transform CommonJS module into ES module. Features -------- * Transform the syntax that is interchangeable between mjs and js e.g. `const foo = require("foo")` -> `import * as foo from "foo";`. * Hoist the `require`/`exports` statement that is not top-level. * Transform dynamic imports i.e. `Promise.resolve(require("foo"))` -> `import("foo")`. * Prefer named import/export when possible. There are more examples under `test/cases` folder. Usage ----- ```js const {parse} = require("acorn"); const {transform} = require("cjs-es"); const code = ` function foo() {} function bar() {} module.exports = {foo, bar}; `; transform({code, ast: parse(code, {ecmaVersion: "latest"})}) .then(result => { console.log(result.code); /* -> function foo() {} function bar() {} export {foo}; export {bar}; */ }); ``` Import style ------------ When binding the module into one identifier: ```js const foo = require("foo"); ``` The transformer imports all members from the module by default: ```js import * as foo from "foo"; ``` To import the default member, mark `require()` as `// default`: ```js const foo = require("foo"); // default ``` Result: ```js import foo from "foo"; ``` Note that if the identifier is used as the callee of a function/new expression, it would be considered as the default member since the namespace is not callable. Export style ------------ If the `module.exports` is assigned with an object pattern: ```js const foo = "foo"; const bar = "bar"; module.exports = { foo, bar }; ``` The transformer converts it into named exports: ```js const foo = "foo"; const bar = "bar"; export {foo}; export {bar}; ``` To export the entire object as the default member, mark `module.exports` as `// default`: ```js const foo = "foo"; const bar = "bar"; module.exports = { // default foo, bar }; ``` Result: ```js const foo = "foo"; const bar = "bar"; export default { foo, bar }; ``` Also note that if you set `exportStyle` to `default`, all named exports would be merged into a namespace object: ```js const foo = "foo"; const bar = "bar"; exports.foo = foo; exports.bar = bar; ``` Result: ```js const foo = "foo"; const bar = "bar"; const _module_exports_ = {}; export {_module_exports_ as default}; _module_exports_.foo = foo; _module_exports_.bar = bar; ``` Hoist ----- If the `require`/`module`/`exports` statement are nested, they would be hoisted. #### Require statement ```js if (foo) { require("foo").foo(); } ``` Result: ```js import * as _require_foo_ from "foo"; if (foo) { _require_foo_.foo(); } ``` #### Export statement ```js if (foo) { module.exports = () => "foo"; } else { module.exports = () => "bar"; } ``` Result: ```js let _module_exports_; export {_module_exports_ as default}; if (foo) { _module_exports_ = () => "foo"; } else { _module_exports_ = () => "bar"; } ``` #### Named export ```js if (foo) { exports.foo = () => "foo"; } function test() { exports.foo = () => "bar"; } ``` Result: ```js let _export_foo_; export {_export_foo_ as foo}; if (foo) { _export_foo_ = () => "foo"; } function test() { _export_foo_ = () => "bar"; } ``` Dynamic import -------------- ES6 lazy load `import("...")` is async and return a promise. It is interchangeable with `Promise.resolve(require("..."))` in CommonJS: ```js module.exports = () => { return Promise.resolve(require("foo")); }; ``` Result: ```js export default () => { return import("foo"); }; ``` Use `module.exports`/`exports` at the same time ----------------------------------------------- It is not a good idea to put `exports` everywhere, but it is a common pattern: ```js if (foo) { exports = module.exports = () => "foo"; } else { module.exports = exports = () => "bar"; } exports.OK = "OK"; console.log(module.exports); ``` All `module.export` and `exports` would be converted into a single reference: ```js let _module_exports_; export {_module_exports_ as default}; if (foo) { _module_exports_ = () => "foo"; } else { _module_exports_ = () => "bar"; } _module_exports_.OK = "OK"; console.log(_module_exports_); ``` Passing `module` around ----------------------- It will generate a module wrapper in this case: ```js var define = require('amdefine')(module); define(() => {}); ``` Result: ```js const _module_ = {exports: {}}; import _require_amdefine_ from "amdefine"; var define = _require_amdefine_(_module_); define(() => {}); export default _module_.exports; ``` API reference ------------- This module exports following members. * `transform`: A function which can convert CJS module synax into ES module syntax. ### transform ```js async transform({ parse?: (code: String) => ESTree, code: String, ast?: ESTree, sourceMap?: Boolean = false, importStyle?: String | async (moduleId) => String, exportStyle?: String | async () => String, nested?: Boolean = false, warn?: (message: String, pos: Number) => void }) => TransformResult ``` * `parse` is a parser function which can parse JavaScript code into AST. The module will use this function to parse `code`. You don't have to provide the `parse` function if `ast` is set. * `code` is the JavaScript source code. * `ast` - if you already have the AST of the code, you can set it as `ast` so the module don't have to parse the code again. * `sourceMap` - if `true` then generate the source map. * `importStyle` and `exportStyle` are used to decide how to transform import/export statements. The value or the value returned by the function must be `"named"` or `"default"`. By default, the transformer always prefer to use named exports for import/export statements. If `importStyle` is a function, it will only be called once for each `moduleId` if needed. If `exportStyle` is a function, it will only be called once if needed. * `nested` - By default, only top-level nodes are analyzed and transformed. To analyze the entire tree, set this to true. * `warn` - the transformer uses `warn` function to emit a warning. If `warn` is not set then the transformer will print the message to the console using `console.error`. If an error is thrown during walking the AST, the error has a property `pos` which points to the index of the current node. ### TransformResult ```js { code: String, isTouched: Boolean, map: Object | null } ``` * `code` - the result ES source code. * `isTouched` - if `true` then the code is changed. * `map` is the source map object generated by [`magicString.generateMap`](https://github.com/Rich-Harris/magic-string#sgeneratemap-options-). Only available if `isTouched` and the `sourceMap` option are both `true`. Changelog --------- * 0.9.2 (Aug 8, 2022) - Fix: always put named export wrapper at the top. * 0.9.1 (Aug 8, 2022) - Fix: the module wrapper is removed when imports are trasnformed. * 0.9.0 (Aug 8, 2022) - Bump dependencies. - Fix: always put module wrapper at the top. * 0.8.2 (Jul 2, 2019) - Fix: nested export assignment doesn't check if exports is shadowed. * 0.8.1 (Jun 18, 2019) - Fix: don't hoist duplicated imports. * 0.8.0 (Jun 13, 2019) - Refactor scope analyzer and import writer. - Add: `context.finalImportType`. * 0.7.0 (Jun 13, 2019) - Add: collect import/exrpot information. - Change: export names when module exports object literal and uses nested exports. * 0.6.4 (Jun 6, 2019) - Fix: export default if the object literal has function properties and the function contains `this`. * 0.6.3 (Jun 6, 2019) - Fix: assign a default object if `typeof exports` exists. * 0.6.2 (Sep 19, 2018) - Enhance: try to export live-binding when exporting defaults. - Fix: the logic of module wrapper. - Fix: mixed exports. - Fix: nested module assigned with named exports. * 0.6.1 (Sep 19, 2018) - Bump dependencies. * 0.6.0 (Sep 19, 2018) - Fix: computed properties are detected as named exports. - Fix: TypeError when analyzing empty array elements: `[, foo]`. - **Breaking: convert `exports` and `module.exports` to a single reference.** * 0.5.0 (Jul 19, 2018) - Add: don't hoist export statements in some cases. * 0.4.9 (Jun 29, 2018) - Fix: failed to transform code without semicolon. * 0.4.8 (Jun 22, 2018) - Add: transform multi-line variable declaration. - Fix: super class cannot be a namespace. * 0.4.7 (May 15, 2018) - Fix: default function/class should be converted into an expression. - Fix: exporting default IIFE causes syntax error. * 0.4.6 (May 13, 2018) - Fix: use hires map. * 0.4.5 (May 1, 2018) - Fix: arguments of callable require node is ignored. * 0.4.4 (May 1, 2018) - Fix: write export statement after last statement instead of the end of the file. * 0.4.3 (May 1, 2018) - Fix: reassigned import is not a namespace. - Add: `options.warn`. - Add: warn users for unconverted `require`. - Add: support rename for declared named import. - Add: support declared export `const foo = module.exports = ...`. * 0.4.2 (Apr 30, 2018) - Fix: template tag is callable. * 0.4.1 (Apr 30, 2018) - Fix: syntax error if exported value is enclosed by parentheses. * 0.4.0 (Apr 30, 2018) - Rewrite for async. `options.importStyle` and `options.exportStyle` are async now. - **Change: `transform` function is async now.** - **Drop: `options.hoist`, `options.dynamicImport`.** - Add: `options.nested`. - Fix: namespace is not callable. * 0.3.3 (Apr 29, 2018) - Add: `options.ast`. * 0.3.2 (Apr 28, 2018) - Add: expose `.node` property for tree-walk error. - Fix: hoist named export if prefer default + hoist. - Fix: hoist named require if prefer default + hoist. - Fix: declare.init could be null. * 0.3.1 (Apr 28, 2018) - Fix: error while binding default export to object pattern. * 0.3.0 (Apr 27, 2018) - Merge cjs-hoist. - Add: `hoist` option. - Add: `dynamicImport` option. * 0.2.2 (Apr 26, 2018) - Add: `isTouched` property. * 0.2.1 (Apr 26, 2018) - Add: transform top-level require call. * 0.2.0 (Apr 26, 2018) - Change: don't suppress parse error. - Change: remove `// all` comment. - Add: importStyle, exportStyle option. - Add: use `// default` to change import/export style. * 0.1.0 (Apr 25, 2018) - Initial release.