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
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:
const foo = require("foo");
The transformer imports all members from the module by default:
import * as foo from "foo";
To import the default member, mark require()
as // default
:
const foo = require("foo"); // default
Result:
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:
const foo = "foo";
const bar = "bar";
module.exports = {
foo,
bar
};
The transformer converts it into named exports:
const foo = "foo";
const bar = "bar";
export {foo};
export {bar};
To export the entire object as the default member, mark module.exports
as // default
:
const foo = "foo";
const bar = "bar";
module.exports = { // default
foo,
bar
};
Result:
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:
const foo = "foo";
const bar = "bar";
exports.foo = foo;
exports.bar = bar;
Result:
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
if (foo) {
require("foo").foo();
}
Result:
import * as _require_foo_ from "foo";
if (foo) {
_require_foo_.foo();
}
Export statement
if (foo) {
module.exports = () => "foo";
} else {
module.exports = () => "bar";
}
Result:
let _module_exports_;
export {_module_exports_ as default};
if (foo) {
_module_exports_ = () => "foo";
} else {
_module_exports_ = () => "bar";
}
Named export
if (foo) {
exports.foo = () => "foo";
}
function test() {
exports.foo = () => "bar";
}
Result:
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:
module.exports = () => {
return Promise.resolve(require("foo"));
};
Result:
export default () => {
return import("foo");
};
module.exports
/exports
at the same time
Use It is not a good idea to put exports
everywhere, but it is a common pattern:
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:
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_);
module
around
Passing It will generate a module wrapper in this case:
var define = require('amdefine')(module);
define(() => {});
Result:
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
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 parsecode
. You don't have to provide theparse
function ifast
is set. -
code
is the JavaScript source code. -
ast
- if you already have the AST of the code, you can set it asast
so the module don't have to parse the code again. -
sourceMap
- iftrue
then generate the source map. -
importStyle
andexportStyle
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 eachmoduleId
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 useswarn
function to emit a warning. Ifwarn
is not set then the transformer will print the message to the console usingconsole.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
{
code: String,
isTouched: Boolean,
map: Object | null
}
-
code
- the result ES source code. -
isTouched
- iftrue
then the code is changed. -
map
is the source map object generated bymagicString.generateMap
. Only available ifisTouched
and thesourceMap
option are bothtrue
.
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
.
- Fix: export default if the object literal has function properties and the function contains
-
0.6.3 (Jun 6, 2019)
- Fix: assign a default object if
typeof exports
exists.
- Fix: assign a default object if
-
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
andmodule.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
andoptions.exportStyle
are async now. - Change:
transform
function is async now. - Drop:
options.hoist
,options.dynamicImport
. - Add:
options.nested
. - Fix: namespace is not callable.
- Rewrite for async.
-
0.3.3 (Apr 29, 2018)
- Add:
options.ast
.
- Add:
-
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.
- Add: expose
-
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.
- Add:
-
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.