ca0v / cjstoesm

A tool that can transform CommonJS to ESM

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Logo

A tool that can transform CommonJS to ESM

Downloads per month NPM version Dependencies Contributors code style: prettier License: MIT Support on Patreon

Description

This is a tool that converts CommonJS modules into tree-shakeable ES Modules. This allows you to not only bundle CommonJS modules for the browser, but also makes it possible for you to bundle them in modern tools such as Rollup.

cjstoesm can be used from the Command Line, as a JavaScript library, and as a TypeScript Custom Transformer.

Prior art such as babel-plugin-transform-commonjs and rollup-plugin-commonjs exists, but this Custom Transformer aims at producing code that is just as tree-shakeable as equivalent code written natively with ES Modules. Additionally, it aims to be as clean as possible, with no "wrappers" around modules as can be seen in other similar solutions.

For example, here's how cjstoesm may rewrite a CommonJS module:

Input

exports.foo = function foo() {};

Output

export function foo() {}

Here's another example:

Input

module.exports = {
	foo() {
		return 2 + 2;
	},
	bar: 3,
	baz: new RegExp("")
};

Output

export function foo() {
	return 2 + 2;
}
export const bar = 3;
export const baz = new RegExp("");
export default {foo, bar, baz};

The same goes for require(...) calls:

Input:

const {foo: bar} = require("./my-module");

Output:

import {foo as bar} from "./my-module";

And for complex require calls such as:

Input:

const {
	foo: {bar: baz}
} = require("./my-module").something("bar");

Output:

import {something} from "./my-module";
const {
	foo: {bar: baz}
} = {something}.something("bar");

As you can see, this transformer will attempt to produce code that generates as granular imports and exports as possible.

Features

  • Transformation of CommonJS to ESM
  • Tree-shaking friendly
  • Clean, idiomatic output
  • No wrappers
  • Low-level implementation that can be used as the foundation for other tools such as Loaders, Plugins, CLIs, and Linters.
  • CLI integration, enabling you to convert a project from CJS to ESM from the command line.
  • API support, enabling you to convert a project from CJS to ESM programmatically.
Feature image

Table of Contents

Install

npm

$ npm install cjstoesm

Yarn

$ yarn add cjstoesm

pnpm

$ pnpm add cjstoesm

Run once with npx

$ npx -p typescript cjstoesm

Peer Dependencies

cjstoesm depends on typescript, so you need to manually install this as well.

Usage

cjstoesm can be used in a variety of ways. The most straightforward usage is directly from the CLI:

CLI usage

You can use this library as a CLI to convert your project files from using CommonJS to using ESM.

If you install cjstoesm globally, you'll have cjstoesm in your path. If you install it locally, you can run npx cjstoesm. If you don't install it globally nor locally, you can also just run it once with the command npx -p typescript cjstoesm <glob> <outdir>.

$ cjstoesm --help

Welcome to the CJS to ESM CLI!

Usage: cjstoesm [options] [command]

Options:
  -h, --help                            output usage information

Commands:
  transform [options] <input> <outDir>  Transforms CJS to ESM modules based on the input glob

For example, you can run cjstoesm transform "**/*.*" dist to transform all files matched by the glob **/*.* and emit them to the folder dist from the current working directory. You can also just run cjstoesm "**/*.*" dist which is an alias for the transform command.

API Usage

You can also use this library programmatically:

import {transform} from "cjstoesm";
import {writeFileSync} from "fs";

const result = await transform({
	input: "src/**/*.*"
});

// Write to disk
for (const {fileName, text} of result.files) {
	writeFileSync(fileName, text);
}

API options

interface TransformOptions {
	/**
	 * The input glob to match against the file system
	 */
	input: string;

	/**
	 * The FileSystem to use. Useful if you want to work with a virtual file system. Defaults to using the "fs" module
	 */
	fileSystem?: FileSystem;

	/**
	 * The TypeScript module to use.
	 */
	typescript?: typeof Typescript;

	/**
	 * The current working directory to use as the base. Defaults to process.cwd()
	 */
	cwd?: string;

	/**
	 * A custom logger to be provide.
	 */
	logger?: Loggable;
}

Usage with TypeScript's Compiler APIs

cjstoesm also provides its functionality as a Custom Transformer for Typescript. This makes it possible for you to use it directly with TypeScript's Compiler APIs. It works completely fine on JavaScript files, so long as you enable allowJs in your CompilerOptions.

The most simple way of transpiling with Typescript would be with transpileModule:

import {ModuleKind, transpileModule} from "typescript";
import {cjsToEsm} from "cjstoesm";

const result = transpileModule(`const {join} = require("path");`, {
	transformers: cjsToEsm(),
	compilerOptions: {
		module: ModuleKind.ESNext
	}
});

// 'import { join } from "path"' is printed to the console
console.log(result.outputText);

You may use this is conjunction with other Custom Transformers by importing commonJsToEsmTransformerFactory instead:

import {ModuleKind, transpileModule} from "typescript";
import {cjsToEsmTransformerFactory} from "cjstoesm";

transpileModule(`const {join} = require("path");`, {
	transformers: {
		before: [cjsToEsmTransformerFactory(), someOtherTransformerFactory()],
		after: [
			// ...
		],
		afterDeclarations: [
			// ...
		]
	},
	compilerOptions: {
		module: ModuleKind.ESNext
	}
});

You can also use Custom Transformers with entire Typescript Programs:

import {getDefaultCompilerOptions, createProgram, createCompilerHost} from "typescript";
import {cjsToEsm} from "cjstoesm";

const options = getDefaultCompilerOptions();
const program = createProgram({
	options,
	rootNames: ["my-file.js", "my-other-file.ts"],
	host: createCompilerHost(options)
});
program.emit(undefined, undefined, undefined, undefined, cjsToEsm());

Usage with Rollup

There are two popular TypeScript plugins for Rollup that support Custom Transformers:

Usage with rollup-plugin-ts

import ts from "@wessberg/rollup-plugin-ts";
import {cjsToEsm} from "cjstoesm";

export default {
	input: "...",
	output: [
		/* ... */
	],
	plugins: [
		ts({
			transformers: [cjsToEsm()]
		})
	]
};

Usage with rollup-plugin-typescript2

import ts from "rollup-plugin-typescript2";
import {cjsToEsm} from "cjstoesm";

export default {
	input: "...",
	output: [
		/* ... */
	],
	plugins: [
		ts({
			transformers: [() => cjsToEsm()]
		})
	]
};

Usage with Webpack

There are two popular TypeScript loaders for Webpack that support Custom Transformers:

Usage with awesome-typescript-loader

import {cjsToEsm} from "cjstoesm";
const config = {
	// ...
	module: {
		rules: [
			{
				// Match .mjs, .js, .jsx, and .tsx files
				test: /(\.mjs)|(\.[jt]sx?)$/,
				loader: "awesome-typescript-loader",
				options: {
					// ...
					getCustomTransformers: () => cjsToEsm()
				}
			}
		]
	}
	// ...
};

Usage with ts-loader

import {cjsToEsm} from "cjstoesm";
const config = {
	// ...
	module: {
		rules: [
			{
				// Match .mjs, .js, .jsx, and .tsx files
				test: /(\.mjs)|(\.[jt]sx?)$/,
				loader: "ts-loader",
				options: {
					// ...
					getCustomTransformers: () => cjsToEsm
				}
			}
		]
	}
	// ...
};

Custom Transformer Options

You can provide options to the cjsToEsm Custom Transformer to configure its behavior:

Option Description
debug (optional) If true, errors will be thrown if unexpected or unhandled cases are encountered. Additionally, debugging information will be printed during transpilation.
readFile (optional) A function that will receive a file name and encoding and must return its string contents if possible, and if not, return undefined.
fileExists (optional) A function that will receive a file name and must return true if it exists, and false otherwise
typescript (optional) If given, the TypeScript version to use internally for all operations.

Contributing

Do you want to contribute? Awesome! Please follow these recommendations.

Maintainers

Frederik Wessberg
Frederik Wessberg
Twitter: @FredWessberg
Github: @wessberg
Lead Developer

Backers

Bubbles Christopher Blanchard
Bubbles
Twitter: @use_bubbles
Christopher Blanchard

Patreon

Patrons on Patreon

FAQ

Is conditional require(...) syntax converted into dynamic imports?

No. For the input:

const result = true ? require("./foo") : require("./bar");

The following may be the output, depending on the internal structure of the modules referenced by the require calls:

import foo from "./foo";
import bar from "./bar";

const result = true ? foo : bar;

CommonJS require() syntax are Expressions, whereas ESM import/export syntax are Declarations, and to achieve the same expressiveness with ESM, dynamic imports are required. However, these return Promises and as such cannot be transformed equivalently.

License

MIT © Frederik Wessberg (@FredWessberg) (Website)

About

A tool that can transform CommonJS to ESM

License:MIT License


Languages

Language:TypeScript 98.9%Language:JavaScript 1.1%