Tool to convert Commonjs files into ESM
npm install to-esm



---
shell
npm install to-esm -g
`
---
Usage
`shell
to-esm [--output=] [--html=] [--noheader] [--target=< browser|esm|package >]
[--prefixpath=] [--bundle=] [--watch] [--update-all] [--minify] [--no-bundle-minify]
`
---
Contributions
$3
> We welcome contributions to the 'to-esm' project! Your input can help improve and expand its capabilities.
If you're interested in contributing, please visit the project's description to start getting involved in this open-source project.
Contribute to 'to-esm' on PerspectiveDev
Thank you for considering supporting the 'to-esm' project!
$3
Examples
π
#### Generate ESM code
To generate an .mjs(ES module) file from a .js file do:
---
`shell
π» < Command
$> to-esm example/cjs/input.js
`
> π«
> NOTE: to-esm should run from the project root folder.
---
###### Click on the arrow to expand or collapse
β³ Before...
`
πproject β½ Ran from here
β
ββββπexample
β β
β ββββπcode
β β π library.js
β β π demo.js β½
β β ...
β
`
π _library.js_ β΄
`javascript
function hi()
{
console.log(I wanted to say hi!)
}
module.exports = hi;
`
π _demo.js_ β΄
`javascript
const hi = require("./library.js");
hi();
`
./demo.js => ./demo.mjs π
β After...
`
πproject
β
ββββπexample
β β
β ββββπcode
β β π library.js
β β π demo.js
β β π library.mjs β½
β β ...
β
β π demo.mjs β½
`
π _library.js_ β΄
`javascript
function hi()
{
console.log(I wanted to say hi!)
}
export default hi;
`
π _demo.js_ β΄
`javascript
import hi from "./example/code/library.mjs";
hi();
`
to-esm will convert the entry point inside the working
directory. The others will depend on the source location.
---
π
#### Generate code into a dedicated directory
> --output < folder >
`shell
π» < Command
$> to-esm example/cjs/input.cjs --output generated/esm
`
---
###### Click on the arrow to expand or collapse
β³ Before...
`
πproject β½ Ran from here
β
ββββπexample
β β
β ββββπcode
β β π library.js
β β π demo.js β½ π©
β β ...
`
β After...
`
πproject
β
ββββπexample
β β
β ββββπcode
β β π library.js
β β π demo.js
β β ...
β
ββββπgenerated β½ π©
β ββββπesm
β ββββπexample
β ββββπcode
β π library.mjs β½ π©
β π demo.mjs β½
β ...
`
β Check...
##### Checking the conversion has succeeded
`shell
node generated/esm/example/code/demo.mjs
`
---
π
#### Remove automatically generated header
> --noheader
`shell
π» < Command
$> to-esm example/cjs/input.cjs --output generated --noheader
`
---
β --noheader in action
#### β³ - Without the --noheader option
`javascript
/**
* DO NOT EDIT THIS FILE DIRECTLY.
* This file is generated following the conversion of
* [./example/code/demo.js]{@link ./example/code/demo.js}
*
**/
import hi from "./example/code/library.mjs";
hi();
`
#### β With the --noheader option
`javascript
import hi from "./example/code/library.mjs";
hi();
`
---
π
#### Generate code for the browser
> --target < browser | esm | package >
`shell
Command < π»
$> to-esm example/cjs/input.cjs --output generated --target browser
`
---
###### Click on the arrow to expand or collapse
You see a warning when using node native module within the browser
#### 1- When generating code for the browser, **
to-esm** will display a warning when the code uses a native Node library.
π _demo.js_ β΄
`javascript
const path = require("path"); // See directives below to see how to remove this call
function hi()
{
console.log(I wanted to say hi!)
}
module.exports = hi;
`
###### During conversion:
`shell
π» >
to-esm: (1130) ================================================================
to-esm: (1132) Processing: ./example/code/demo.js
to-esm: (1134) ----------------------------------------------------------------
to-esm: (1060) β SUCCESS: Converted [./example/code/demo.js] to [generated-browser\demo.mjs]
to-esm: (1150)
to-esm: (1130) ================================================================
to-esm: (1132) Processing: ./example/code/library.js
to-esm: (1134) ----------------------------------------------------------------
to-esm: (1017) path is a built-in NodeJs module. β½ π©
to-esm: (1060) β SUCCESS: Converted [./example/code/library.js] to [generated-browser\example\code\library.mjs]
to-esm: (1150)
`
#### 2- To load your files in the HTML code, you only point to the entry file (demo.js).
The browser will automatically load the other files.
!img.png
> demo.mjs is the entrypoint.
The browser automatically loads all the related files.
---
###### Click on the arrow to expand or collapse
NodeJs Third Party modules in browser
When there is a requirement to load libraries from the node_modules folder,
to-esm will generate a converted copy of the files to the output directory.
π _demo.js_ β΄
`javascript
const toAnsi = require("to-ansi");
const rgbHex = require("rgb-hex-cjs");
const {COLOR_TABLE, SYSTEM} = require("./some-lib.js");
// ...
`
`
πproject
ββββ π original
ββββ π index.html
ββββ π demo.js
ββββ π some-lib.js
β
ββββ π generated
ββββ π demo.mjs β¬
π©
ββββ π some-lib.mjs β¬
π©
β
ββββ π node_modules β¬
π©
β
ββββπ rgb-hex
β βββ π index.cjs
β
ββββπ to-ansi
βββ π index.cjs
`
The two libraries used will be easily accessible by the system and ease the bundling in production.
See the importmap section to have a more modular approach.
---
π
#### Generate importMaps within html files
> --html < pattern | html >
`shell
Generates => π ./demo.mjs & update index.html
$> to-esm example/cjs/demo.cjs --html index.html
`
> π«
> **NOTE: When this option is used, the target is always "browser".
> --target.**
---
###### An import map will allow writing imports like this
`javascript
import rgbhex from "rgb-hex"
`
###### instead of
`javascript
import rgbhex from "../../../path/to/rgb-hex.mjs"
`
---
###### Click on the arrow to expand or collapse
HTML importmap section
###### Before
π index.html β΄
`html
`
###### After
`html
`
---
importmap allows some more elaborated setups where third party caching will be entirely handled by the browser.
---
π
#### Convert files with patterns
You can have multiple files converted in one go. It can be helpful if some files are not connected.
`shell
$> to-esm --input="example/cjs/*.?(c)js" --output=example/esm/
`
---
$3
> π«
> NOTE: This option is experimental
To generate a non-bundled package code for npm, use this option.
It will add an extra prefix ../../ to all relative path to third party modules.
---
$3
If your code point to a npm module with the option `--target browser`, the system will convert the import to
the module location entrypoint.
For instance:
π _source.cjs_ β΄
> const toAnsi = require("to-ansi");
Will be converted to this path (or similar):
π _source.mjs_ β΄
> import toAnsi from "../../node_modules/to-ansi/index.mjs";
The path will be okay at conversion time. However, if the generated file (`source.mjs`) is required inside a
browser, the path may no longer be valid. It will depend on your server configuration.
The option `--prefixpath` allows correcting the issue by prepending some value to the target path.
This way, you can redirect the converted path to point to the correct location on your server.
`
π» < Command
to-esm source.cjs --output out/ --prefixpath ../somewhere/
`
π _source.mjs_ β΄
> import toAnsi from "../somewhere/../../node_modules/to-ansi/index.mjs";
πͺ
Options (via command line)
| Options | Description | Expect | default |
|---------------------|-------------------------------------------------------------------------------------------------------|--------------------------------|---------|
| filepath | _File or pattern to convert_ | file path | |
| --bundle | _Generate minified bundle for esm environment_ | file path | |
| --bundle-browser | _Generate minified bundle for browser environment_ | file path | |
| --bundle-cjs | _Generate minified bundle for cjs environment_ | file path | |
| --bundle-esm | _Same as above_ | file path | |
| --entrypoint | _Explicitely set entry point (otherwise use the first file set in cli)_ | file path | |
| --extension | _Set generated files extension_ | string | .mjs |
| --force-lf | _Replace CLRF with LF_ | | |
| --html | _html files to receive importmaps_ | glob | |
| --keep-external | _Do not try to copy files from absolute paths into generated folder_ | boolean | false |
| --keepExisting | _Options to skip already converted files_ | | |
| --minify | _Options to minify converted files_ | boolean | |
| --nmBrowserImported | _Destination folder name for imported third parties when target is "browser"_ | directory
path
| |
| --no-comments | _Options to remove comments from converted files_ | boolean | false |
| --no-bundle-minify | _Options to not minify bundled files_ | boolean | false |
| --noHeader | _Options to not generate automatic header_ | boolean | false |
| --output | _Output directory_ | directory path | |
| --prefixpath | _Add a path to paths targeting third party modules_ | directory path
| |
| --resolve-absolute | _Extra folders to look for when trying to solve absolute imported paths_ | string
| |
| --skipEsmResolution | _Do not try to resolve third party libraries_ | boolean | false |
| --skipLinks | _Don't follow links (It will not convert linked files, but resulting links will need manual updates)_ | boolean | false |
| --target | _Setting the targeted environment_ | esm / browser / package | esm |
| --update-all | _Automatically update package.json to set entry points_ | | |
| --use-bundle | _When updating package.json use bundled/minified code_ | | |
| --watch | _Watch mode to automatically apply conversions when changes detected_ | directory path
| |
| ~~--subRootDir~~ | ~~Allow to retarget the output sub-directory~~ | ~~true~~
| |
π
Advanced Options (via config file)
To apply advanced options, create a config file (.to-esm, .to-esm.json or .to-esm.cjs) and it will be automatically
loaded.
Otherwise, you can create a file with a custom name and explicitly tell the system to load it.
`shell
to-esm --config=.my-to-esm-config.cjs
`
Keys within the config file are case sensitive.
$3
#### [replaceStart, replaceEnd]
π .toesm.cjs β΄
`javascript
module.exports = {
replaceStart: [
{
search : "const chalk = require(\"chalk\");",
replace: "// *"
},
{
search : /const\s+colors\s=\srequire\(.colors.\);/g,
replace: "// *"
}
],
replaceEnd : [
{
search : // *,
replace: "// --------- chalk and colors were replaced ----------------"
}
]
}
`
| Properties | Description |
|----------------------|-----------------------------------------------------------------------|
| replaceStart | _will perform a replacement _before_ doing the conversion to ESM_ |
| replaceEnd | _will perform a replacement _after_ doing the conversion to ESM_ |
| replaceStart.search | _The regex pattern or string to replace_ |
| replaceStart.replace | _The replacement sentence_ |
$3
> "replaceModules": ...
Sometimes, you may find libraries where only ESM is available when CJS was available on older versions.
This option allows setting a different version depending on the environment used.
For instance, the module "chalk" uses ESM for its Export on its latest version (5.0.0) and CJS for the older version (4.
1.2).
You can setup toesm to use the appropriate version:
π .toesm.cjs β΄
`javascript
module.exports = {
replaceModules:
{
"rgb-hex":
{
cjs: {
name: "rgb-hex-cjs", // β¬
π© .cjs files will use
// ... = require("rgb-hex-cjs")
// to load the module (v3.0.0)
version: "@^3.0.0"
},
esm: {
version: "@latest" // β¬
π© .mjs files will use
// import ... from "rgb-hex"
// to load the module
}
}
},
}
`
In the .cjs file to convert, you would write:
`javascript
const rgbhex = require("rgb-hex-cjs");
`
Which is going to be transformed to:
`javascript
import rgbhex from "rgb-hex";
`
---
| Properties | Description |
|-----------------------------------|---------------------------------------------------------|
| replaceModules[\] | _The module we want to use two different versions with_ |
| replaceModules[\].cjs | _The module version to use with CommonJs files_ |
| replaceModules[\].mjs | _The module version to use with converted files_ |
$3
> "html": ...
`javascript
module.exports =
{
html:
{
pattern : "/index.html",
importmap :
{
"my-project": "../node_modules/my-project/src/esm/add.mjs",
"lodash" : "https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"
},
importmapReplace: [
{
search : "./node_modules",
replace: /node_modules,
}
],
}
}
`
| Properties | Description |
|------------------|----------------------------------------------------|
| pattern | _HTML file pattern where importmap needs updating_ |
| importmap | _value to add to HTML files_ |
| importmapReplace | _Apply replacements on the importmap list_ |
###### The options above will be deployed as below:
`html
`
Allowing to write this:
`javascript
import {add} from "my-project"
`
> NOTE: You can imagine improved caching offered by browsers soon
---
π
Directives
#### Directives allow more control over the generated code.
---
$3
You can, if you want, also use some to-esm directives within the code.
For instance, the code below will not appear when the target is a browser.
`javascript
/ to-esm-browser: remove /
const path = require("path");
const fs = require("fs");
const os = require("os");
/ to-esm-browser: end-remove /
`
---
$3
It is also possible to add code.
π code.cjs β΄
`javascript
/** to-esm-browser: add
this.realConsoleLog("LogToFile is not supported in this environment. ")
**/
`
In this example, after conversion, the above code will become this:
π code.mjs (with target browser) β΄
`javascript
this.realConsoleLog("LogToFile is not supported in this environment. ")
`
---
$3
`javascript
/ to-esm-all: skip /
console.log("Skip this");
/ to-esm-all: end-skip /
`
---
$3
`javascript
/ to-esm-all: do-not-overwrite /
`
If the .mjs file already exists and contains this directive, it will not be overwritten.
---
#### Remove comments from generated code
> --no-comments
The example below will remove all comments from the scanned .cjs files.
`shell
π» < Command
$> to-esm example/*/.cjs --output generated --target cjs
`
---
π‘
Working with both CJS and ESM
You may want to work with both CommonJs and ESM together. So, you benefit from both worlds.
The CommonJs approach is a dynamic one. So, for example, you can do things like:
`javascript
if (a)
{
// load module a
require(a);
}
else
{
// load module b
require(b)
}
`
With ESM and its static approach, loading both modules is necessary.
`javascript
// load module a
import "a";
// load module b
import "b";
`
π‘
Some conventions to write code for both CommonJs and ES Modules
Here are a few guides to writing code easily convertible.
$3
For having the best compatibility between the two systems, it is best to use named exports.
Replace structures like:
`javascript
module.exports = {
TABLE1 : ...,
TABLE2 : ...,
otherKey: ...
}
`
with:
`javascript
module.exports.TABLE1 =
...
;
module.exports.TABLE2 =
...
;
module.exports.otherKey =
...
;
`
Or, if you want to provide a default export too:
`javascript
// Default export
module.exports = {
TABLE1, TABLE2, ...
}
// Named export
module.exports.TABLE1 =
...
;
module.exports.TABLE2 =
...
;
module.exports.otherKey =
...
;
`
---
$3
Rather than using "requires" like below forms (or more complex ones)
π€ β΄
`javascript
const Something = require("electron-data-exchanger").myThing;
const anything = require("electron-data-exchanger")(...);
`
Which may introduce temporary variables (_toesmTemp1)
`javascript
import _toesmTemp1 from "electron-data-exchanger";
const Something = _toesmTemp1.myThing;
`
It is best to have them uncomplicated, so the conversion is straightforward.
π β΄
`javascript
const MySomething = require("electron-data-exchanger");
const myAnything = require("electron-data-exchanger");
// ... The code that uses what was required
`
---
$3
to-esm will not convert the below code as it would require some refactoring that would make the generated code
not resemble the original code.
##### Ambiguous form for to-esm
`javascript
let myVar = "something";
// ...
myVar = require("my-module")
`
##### Expected form:
`javascript
let myVar = require("my-module");
`
---
$3
#### Library targeting both Node and the Browser
If it needs to do an import in an ESM environment but not in the Browser, you can use directives to solve the issue.
`javascript
/** to-esm-browser: add
let myMod = () => { return "somethings"; };
**/
/ to-esm-browser: remove /
let myMod = require("my-mod");
/ to-esm-browser: end-remove /
`
###### The code above will convert to:
Node
`javascript
import myMod from "my-mod";
`
Browser
`javascript
let myMod = () =>
{
return "somethings";
};
`
π‘
Create a Hybrid Library with to.esm
$3
`
πproject
ββββ π index.cjs β¬
π©
ββββ π package.json
ββββ πexample
β β
β ββββπcode
β ββββ π library.cjs β½ π©
β ...
β
ββββ π node_modules
β βββ ...
`
$3
`shell
$> to-esm index.cjs --update-all
`
> π«
_The option --update-all will modify your package.json to make it point to your entrypoint._
> `javascript
> // Will point to .index.cjs
> require("your-module-name")
>
> // Will point to .index.mjs
> import("your-module-name")
> `
---
###### Click on the arrow to expand or collapse
β³ Before...
π ./package.json β΄
`json
{
"name": "my-project",
"main": "./index.cjs",
"scripts": {
"build": "to-esm index.cjs"
},
"devDependencies": {
"to-esm": "file:.."
}
}
`
π ./index.cjs β΄
`javascript
const hi = require("./example/code/library.cjs");
hi();
`
π ././example/code/library.cjs β΄
`javascript
function hi()
{
console.log(I wanted to say hi!)
}
module.exports = hi;
`
---
β After...
π ./package.json β΄
`json
{
"name": "my-project",
"main": "./index.cjs",
"scripts": {
"build": "to-esm index.cjs"
},
"devDependencies": {
"to-esm": "file:.."
},
"module": "./index.mjs",
β¬
π©
"type": "module",
β¬
π©
// (Change to commonjs if you don't want to use .cjs extension)
"exports": {
".": {
"require": "./index.cjs",
β¬
π©
"import": "./index.mjs"
β¬
π©
}
}
}
`
π ./index.mjs β΄
`javascript
/**
* DO NOT EDIT THIS FILE DIRECTLY.
* This file is generated following the conversion of
* [./index.cjs]{@link ./index.cjs}
*
**/
import hi from "./example/code/library.mjs";
hi();
`
π ././example/code/library.mjs β΄
`javascript
/**
* DO NOT EDIT THIS FILE DIRECTLY.
* This file is generated following the conversion of
* [./example/code/library.cjs]{@link ./example/code/library.cjs}
*
**/
function hi()
{
console.log(I wanted to say hi!)
}
export default hi;
`
$3
To test it in NodeJs.
`shell
node index.mjs
`
---
π‘
Create a Hybrid Library with to.esm supporting the browser
$3
`
πproject
ββββ π index.cjs β¬
π©
ββββ π package.json
ββββ πexample
β β
β ββββπcode
β ββββ π library.cjs β½ π©
β ...
β
ββββ π node_modules
β βββ ...
`
$3
###### For the browser
`shell
$> to-esm index.cjs --output ./generated --target browser --bundle-browser dist/index.min.js
`
---
###### Click on the arrow to expand or collapse
β³ Before...
π ./index.cjs β΄
`javascript
const hi = require("./example/code/library.cjs");
hi();
`
π ././example/code/library.cjs β΄
`javascript
function hi()
{
console.log(I wanted to say hi!)
}
module.exports = hi;
`
---
β After...
π ./index.mjs β΄
`javascript
/**
* DO NOT EDIT THIS FILE DIRECTLY.
* This file is generated following the conversion of
* [./index.cjs]{@link ./index.cjs}
*
**/
import hi from "./example/code/library.mjs";
hi();
`
π ././example/code/library.mjs β΄
`javascript
/**
* DO NOT EDIT THIS FILE DIRECTLY.
* This file is generated following the conversion of
* [./example/code/library.cjs]{@link ./example/code/library.cjs}
*
**/
function hi()
{
console.log(I wanted to say hi!)
}
export default hi;
`
π ./dist/index.min.js β΄ (Generated because of the --bundle option)
`javascript
const c = {"95c93": {}};
c["95c93"].default = function ()
{
console.log("I wanted to say hi!")
};
{
c.bbc7e = {};
let b = c["95c93"].default;
b()
}
`
---
$3
`
πproject
ββββ π index.cjs
ββββ π package.json
ββββ πgenerated
β β
β ββββ π index.mjs β¬
π©
β β
β ββββ π ...
β
ββββ π dist
β βββ index.min.js β¬
π©
`
##### Insert the JavaScript browser version
`html
...
...
`
##### Usage for the ESM version
`javascript
import "..."
from
"index.mjs"
`
---
π‘
Make your CJS module ESM compatible in one go.
Start by creating a .cjs file (or migrate your code) to have this structure.
`
πproject
ββββ π package.json
ββββ π cjs
β βββ π index.cjs
`
#### Run to-esm against en entry point
`shell
$> to-esm cjs/index.cjs --output esm/ --bundle-esm bundle/mycode.min.mjs --bundle-cjs bundle/mycode.min.cjs
--bundle-browser dist/mycode-browser.min.js --update-all --use-bundle --watch
`
`
πproject
ββββ π package.json
ββββ π cjs
β βββ π index.cjs β¬
π© Original code
ββββ π mjs
β βββ π mycode.mjs β¬
ESM Converted code (--output esm/)
ββββ π bundle
β βββ π mycode.min.mjs β¬
Bundled and minified code for ESM (--bundle-esm bundle/mycode.min.mjs)
β βββ π mycode.min.cjs β¬
Bundled and minified code for CommonJs (--bundle-cjs bundle/mycode.min.mjs)
ββββ π dist
β βββ π mycode-browser.min.mjs β¬
Bundled and minified code for browsers (--bundle-browser bundle/mycode.min.
mjs)
`
* The option `--update-all` will automatically modify your package.json to make your module load the correct file
depending on the environment
* The option `--use-bundle` will make --update-all configure the module to use minified and bundled versions
("mycode.min.mjs", "mycode.min.cjs") when a "require" or "import" is done against your module (Otherwise, it loads "
index.
cjs" and "mycode.mjs")
* The option `--watch` will automatically regenerate files when a change happens to the original cjs file (.
/cjs/index.cjs)
#### This is it. Your module is fully hybrid, and you can keep working with CommonJs while not worrying about ESM.
---
###### Click on the arrow to expand or collapse
β³ Before...
π ./index.cjs β΄
`javascript
const hi = require("./example/code/library.cjs");
hi();
`
π ././example/code/library.cjs β΄
`javascript
function hi()
{
console.log(I wanted to say hi!)
}
module.exports = hi;
`
---
β After...
π ./index.mjs β΄
`javascript
/**
* DO NOT EDIT THIS FILE DIRECTLY.
* This file is generated following the conversion of
* [./index.cjs]{@link ./index.cjs}
*
**/
import hi from "./example/code/library.mjs";
hi();
`
π ././example/code/library.mjs β΄
`javascript
/**
* DO NOT EDIT THIS FILE DIRECTLY.
* This file is generated following the conversion of
* [./example/code/library.cjs]{@link ./example/code/library.cjs}
*
**/
function hi()
{
console.log(I wanted to say hi!)
}
export default hi;
`
π ./dist/index.min.js β΄ (Generated because of the --bundle option)
`javascript
const c = {"95c93": {}};
c["95c93"].default = function ()
{
console.log("I wanted to say hi!")
};
{
c.bbc7e = {};
let b = c["95c93"].default;
b()
}
`
---
$3
`
πproject
ββββ π index.cjs
ββββ π package.json
ββββ πgenerated
β β
β ββββ π index.mjs β¬
π©
β β
β ββββ π ...
β
ββββ π dist
β βββ index.min.js β¬
π©
`
##### Insert the JavaScript browser version
`html
...
...
`
##### Usage for the ESM version
`javascript
import "..."
from
"index.mjs"
``