cjs-es

0.9.2 • Public • Published

cjs-es

.github/workflows/build.yml codecov install size

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");
};

Use module.exports/exports at the same time

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_);

Passing module around

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 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

{
  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. 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.

Package Sidebar

Install

npm i cjs-es

Weekly Downloads

747

Version

0.9.2

License

MIT

Unpacked Size

54.8 kB

Total Files

10

Last publish

Collaborators

  • eight04