Giter Club home page Giter Club logo

cjstoesm's Introduction

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.

It can also be used as a tool for migrating a CommonJS-based codebase to one based on ES-modules via a simple CLI.

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.js";

And for complex require calls such as:

Input:

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

Output:

import {something} from "./my-module.js";
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.

It includes Import Assertions too! And out of the box, these are added where relevant and necessary:

Input

const pkg = require("./package.json");

Output

import pkg from "./package.json" assert {type: "json"};

Features

  • Transformation of CommonJS to ESM
  • Tree-shaking friendly
  • Clean, idiomatic output
  • Automatic and configurable addition of file extensions to module specifiers
  • Automatic and configurable addition of import assertions to relevant import declarations
  • 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

Backers

Bubbles Christopher Blanchard Ideal Postcodes Xerox Trent Raymond scrubtheweb Joel
Bubbles
Twitter: @usebubbles
Christopher Blanchard Ideal Postcodes Xerox Trent Raymond scrubtheweb Joel

Patreon

Patrons on Patreon

Table of Contents

Install

npm

$ npm install cjstoesm

Yarn

$ yarn add cjstoesm

pnpm

$ pnpm add cjstoesm

Run once with npx

First, add the peer dependency typescript as a dependency to the package from which you're going to run cjstoesm. Alternatively, if you want to run it from anywhere, you can also install it globally: npm i -g typescript. Now, you can simply run:

$ npx cjstoesm

You can also run cjstoesm along with its peer dependencies in one combined command:

$ npx -p typescript -p cjstoesm cjstoesm

Peer Dependencies

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

Engine

cjstoesm requires Node.js v14.19.0 or newer to function correctly.

File extension handling

The default behavior is to add file extensions to module specifiers to align with the implementation in node.js and across browsers.

You can customize this with the --preserve-module-specifiers command line option, or with the preserveModuleSpecifiers API option. See the API Options for documentation for the possible values you can pass to it.

Import Assertion handling

The default behavior is to add Import Assertions to Import Declarations when necessary and relevant, such as for when referencing JSON files. This aligns with the implementation in node.js and across browsers.

You can customize this with the --import-assertions command line option, or with the importAssertions API option. See the API Options for documentation for the possible values you can pass to it.

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.

The following command transforms all files matched by the glob **/*.* and overwrites them in-place:

cjstoesm **/*.*

You can also just pass in a folder name, in which case all direct descendents of that folder will be transformed and overwritten:

cjstoesm src

You can also pass in a second argument, outDir, to avoid overwriting the source files. The following command transforms all files matched by the glob **/*.* and emits them to the folder dist from the current working directory:

cjstoesm **/*.* dist

Here's an overview of the options that can be passed via the CLI:

$ cjstoesm --help
Usage: cjstoesm [options] <input> <outDir>

Transforms CJS to ESM modules based on the input glob

Options:
  -d, --debug [arg]                       Whether to print debug information
  -v, --verbose [arg]                     Whether to print verbose information
  -s, --silent [arg]                      Whether to not print anything
  -c, --cwd [arg]                         Optionally which directory to use as the current working directory
  -p, --preserve-module-specifiers [arg]  Determines whether or not module specifiers are preserved. Possible values are: "external", "internal", "always", and "never" (default: "external")
  -a, --import-assertions [arg]           Determines whether or not Import Assertions are included where they are relevant. Possible values are: true and false (default: true)
  -m, --dry [arg]                         If true, no files will be written to disk
  -h, --help                              display help for command

API Usage

You can also use this library programmatically:

import {transform} from "cjstoesm";

await transform({
	input: "src/**/*.*",
	outDir: "dist"
});

Alternatively, if you don't want the transform function to automatically write files to disk, you can pass write: false as an option and handle it yourself:

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

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

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

API options

interface TransformOptions {
	/**
	 * The input glob(s) to match against the file system
	 */
	input: string[] | string;
	/**
	 * Optionally, the output directory to use. Defaults to inheriting that of the matched input files`
	 */
	outDir?: string;
	/**
	 * If write is false, no files will be written to disk
	 */
	write: boolean;
	/**
	 * The FileSystem to use. Useful if you want to work with a virtual file system. Defaults to using the "fs" module
	 */
	fileSystem: FileSystem;
	/**
	 * A logger that can print messages of varying severity depending on the log level
	 */
	logger: Loggable;
	/**
	 * The base directory (defaults to process.cwd())
	 */
	cwd: string;
	/**
	 * Determines how module specifiers are treated.
	 * - external (default): CommonJS module specifiers identifying libraries or built-in modules are preserved (default)
	 * - internal: CommonJS module specifiers identifying anything else than libraries or built-in modules are preserved
	 * - always: CommonJS module specifiers are never transformed.
	 * - never: CommonJS module specifiers are always transformed
	 * It can also take a function that is invoked with a module specifier and returns a boolean determining whether or not it should be preserved
	 */
	preserveModuleSpecifiers: "always" | "never" | "external" | "internal" | ((specifier: string) => boolean);

	/**
	 * Determines whether or not to include import assertions when converting require() calls referencing JSON files to ESM.
	 * - true (default): Import assertions will always be added when relevant.
	 * - false: Import assertions will never be added.
	 * It can also take a function that is invoked with a module specifier and returns a boolean determining whether or not an import assertion should be added
	 */
	importAssertions: boolean | ((specifier: string) => boolean);

	/**
	 * If given, a specific TypeScript version to use
	 */
	typescript: typeof TS;
	/**
	 * If true, debug information will be printed. If a function is provided, it will be invoked for each file name. Returning true from the function
	 * determines that debug information will be printed related to that file
	 */
	debug: boolean | string | ((file: string) => boolean);
}

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 simplest 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 "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.
fileSystem (optional) If given, the file system to use. Useful if you are using cjstoesm inside a virtual file system.
preserveModuleSpecifiers (optional) Determines whether or not module specifiers are preserved. Possible values are: "external", "internal", "always", and "never". See API options for more details
importAssertions (optional) Determines whether or not Import Assertions are included where relevant. Possible values are: true and false. See API options for more details
typescript (optional) If given, the TypeScript version to use internally for all operations.
cwd (optional) The directory to use as the current working directory.

Contributing

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

Maintainers

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

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.js";
import bar from "./bar.js";

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)

cjstoesm's People

Contributors

fdiskas avatar wessberg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

cjstoesm's Issues

preserve newlines ?

hello,
Is it possible ? because it shrinks my code so much its hard to read the output.
i will be forced to create a shitty regexp replacer.
please.

Doesn't work with newer TypeScript versions

  • Version: 2.1.2
  • TypeScript Version: 5.1.6

Description

cjstoesm doesn't seem to work with TypeScript 5. Since TypeScript is a peer dependency, I don't think there is a way to provide cjstoesm with the right version, if a newer TypeScript is installed in the parent project. This workaround: #33 (comment) only works if it is not installed.

Expected Behavior

cjstoesm should work with projects that are using TypeScript 5.

Actual Behavior

C:\work\test\node_modules\typescript\lib\typescript.js:111514
      Debug.fail(`Unhandled SyntaxKind: ${Debug.formatSyntaxKind(node.kind)}.`);
            ^

Error: Debug Failure. Unhandled SyntaxKind: Unknown.
    at pipelineEmitWithHintWorker (C:\work\test\node_modules\typescript\lib\typescript.js:111514:13)
    at pipelineEmitWithHint (C:\work\test\node_modules\typescript\lib\typescript.js:111058:9)
    at pipelineEmitWithComments (C:\work\test\node_modules\typescript\lib\typescript.js:114842:7)
    at pipelineEmit (C:\work\test\node_modules\typescript\lib\typescript.js:111007:7)
    at emitExpression (C:\work\test\node_modules\typescript\lib\typescript.js:110991:7)
    at emitExportAssignment (C:\work\test\node_modules\typescript\lib\typescript.js:113087:7)
    at pipelineEmitWithHintWorker (C:\work\test\node_modules\typescript\lib\typescript.js:111270:20)
    at pipelineEmitWithHint (C:\work\test\node_modules\typescript\lib\typescript.js:111058:9)
    at pipelineEmitWithComments (C:\work\test\node_modules\typescript\lib\typescript.js:114842:7)
    at pipelineEmit (C:\work\test\node_modules\typescript\lib\typescript.js:111007:7)

How to use it from CLI

Why you not explain how to use it from CLI?
We need a tool to convert all repo with CJS to ESM.

Cannot read property 'text' of undefined

The following file content throws TypeError: Cannot read property 'text' of undefined at transformSourceFile ([redacted]\node_modules\cjstoesm\dist\cli\index.js:3019:61)

function myFunction() {
    console.log( 'lorem ipsum' );
}

module.exports = {
    myFunction
};

[Bug] Multiple left-hand assignment require case, throws error.

I am building a bundle-less bundler using your wonderful project (thank you!), I have run into an issue with a case that looks like this:

const constants = exports.constants = require('./constants.js')

What is the problem

  • This is an extract from a CJS module (shall we say ... loose code style), this library is called minizlib.
  • The snippet is valid JS although less common, I'd love to help you with a PR but not sure where to start?
  • Throwing a stack trace. I don't know but my best guess is that this case is not handled?
TypeError: Cannot read property 'kind' of undefined
    at Object.isExpressionNode (/Users/me/Code/typescript/unpack/node_modules/typescript/lib/typescript.js:14615:33)
    at isExpression (/Users/me/Code/typescript/unpack/node_modules/@wessberg/cjs-to-esm-transformer/src/transformer/util/is-expression.ts:8:81)
    at visitBinaryExpression (/Users/me/Code/typescript/unpack/node_modules/@wessberg/cjs-to-esm-transformer/src/transformer/before/visitor/visit/visit-binary-expression.ts:295:76)
    at visitNode (/Users/me/Code/typescript/unpack/node_modules/@wessberg/cjs-to-esm-transformer/src/transformer/before/visitor/visit/visit-node.ts:24:10)
    at /Users/me/Code/typescript/unpack/node_modules/@wessberg/cjs-to-esm-transformer/src/transformer/before/transform-source-file.ts:212:26
    at visitNode (/Users/me/Code/typescript/unpack/node_modules/typescript/lib/typescript.js:74775:23)
    at Object.visitEachChild (/Users/me/Code/typescript/unpack/node_modules/typescript/lib/typescript.js:75140:224)
    at childContinuation (/Users/me/Code/typescript/unpack/node_modules/@wessberg/cjs-to-esm-transformer/src/transformer/before/transform-source-file.ts:209:15)
    at visitVariableDeclaration (/Users/me/Code/typescript/unpack/node_modules/@wessberg/cjs-to-esm-transformer/src/transformer/before/visitor/visit/visit-variable-declaration.ts:29:10)
    at visitNode (/Users/me/Code/typescript/unpack/node_modules/@wessberg/cjs-to-esm-transformer/src/transformer/before/visitor/visit/visit-node.ts:22:10)

Id probably want to see an output fixed up like this:

export constants from './constants.js';

or even

import constants from './constants.js';
export constants;

What do you think, hope you are still active.

package creates variable names starting with numbers

  • Version: 2.1.2
  • TypeScript Version:
  • Operating System and version (if applicable): Win
  • Node Version (if applicable): 14

Description

cjstoesm creates invalid variable names beginning with numbers

// input

'use strict'
var low = require('./lib/core.js')
module.exports = low
low.registerLanguage('1c', require('highlight.js/lib/languages/1c'))
low.registerLanguage('abnf', require('highlight.js/lib/languages/abnf'))

Actual Behavior

// output

import * as low from "./lib/core.js";
import 1c from "highlight.js/lib/languages/1c";
import abnf from "highlight.js/lib/languages/abnf";
'use strict';
low.registerLanguage('1c', 1c);
low.registerLanguage('abnf', abnf);
export default low;

Expected Behavior

// output

import * as low from "./lib/core.js";
import _1c from "highlight.js/lib/languages/1c"; // use underscore for valid var name
import abnf from "highlight.js/lib/languages/abnf";
'use strict';
low.registerLanguage('1c', _1c);
low.registerLanguage('abnf', abnf);
export default low;

Hi

Hi there!

I am looking for a way to programmatically convert CJS to ESM for my new framework.

I like your package, but it definitely seems to have some bugs.

I am wondering what the status of this is - are you trying to achieve 100% correct performance? I am also not sure if there is something else out there that is doing this completely already? Could potentially support this project if this seems like to way to go for cjs to esm.

Let me know what's happening, cheers : )

Option to convert exported objects into individual exports

Hi @wessberg, thank you for work on this library.

I want to ask you if it's any chance to add an option for specific exports module resolution.

I have a module that is exporting an object, like this:

const obj = {
  foo () {
    return 2 + 2
  },
  bar: 3,
  baz: new RegExp('')
}
module.exports = obj

And in this particular case the resolution needs to be:

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

but it's only export into:

const obj = {
  foo () {
    return 2 + 2
  },
  bar: 3,
  baz: new RegExp('')
}
export default obj;

Programmatic usage produces empty output

I'm using this script, which I adapted from the README:

const {transform} = require('cjstoesm');

async function main() {
    const input = require.resolve('./fixtures');

    console.log('Running on', input);
    const result = await transform({
        input
    });

    console.log(result);
}

main();

The output is:

$ node tools/codemod/cjs-to-esm/codemod.js                                                                                                                                                                                                                                                                                                                          
Running on /Users/nheiner/code/tvui/tools/codemod/cjs-to-esm/fixtures/index.js
{ files: [] }

Preserve white-space

I noticed that a every extra new-line (that we have for white-space as readability) in files was removed by the tool. Other than that it worked flawlessly!

This makes it impossible to use the tool, as line-breaks as white-space is important e.g. between functions so it does not become a big blob of text.

Cannot run with npx

Tried to run it according to the docs, but got a failure for typescript:

npx -p typescript cjstoesm "server/util/**/*.*" server/util/esm/
npx: installed 1 in 1.844s
internal/modules/cjs/loader.js:883
  throw err;
  ^

Error: Cannot find module 'typescript'
Require stack:
- /home/userA/code/proj/node_modules/cjstoesm/dist/cli/index.js
- /home/userA/code/proj/node_modules/cjstoesm/bin/cjstoesm

Usage documentation is unclear

Question

I've been trying to test out this program on one of my projects, but somehow I've been unable to succeed. I think I may be using it wrong but from the documentation I'm not entirely sure how this is happening.

My project uses Yarn workspaces, with the following structure:

package.json
packages\
packages\callisto-cli
packages\callisto-core
...

Each package has its own package.json.

My first intuition was to run cjstoesm packages/**/*.* since that's what the first example shows. That somehow causes it to attempt to create a directory named after the first file, which crashes:

$ cjstoesm packages/**/*.*
node:fs:1385
  handleErrorFromBinding(ctx);
  ^

Error: EEXIST: file already exists, mkdir '/Users/msikma/Projects/callisto-test/packages/callisto-cli/package.json'
    at Object.mkdirSync (node:fs:1385:3)
    at Object.writeFile (file:///usr/local/lib/node_modules/cjstoesm/dist/cli/index.js:3348:24)
    at Object.writeFile (/usr/local/lib/node_modules/cjstoesm/node_modules/typescript/lib/typescript.js:18750:14)
    at printSourceFileOrBundle (/usr/local/lib/node_modules/cjstoesm/node_modules/typescript/lib/typescript.js:111960:16)
    at emitJsFileOrBundle (/usr/local/lib/node_modules/cjstoesm/node_modules/typescript/lib/typescript.js:111836:13)
    at emitSourceFileOrBundle (/usr/local/lib/node_modules/cjstoesm/node_modules/typescript/lib/typescript.js:111757:13)
    at forEachEmittedFile (/usr/local/lib/node_modules/cjstoesm/node_modules/typescript/lib/typescript.js:111498:34)
    at Object.emitFiles (/usr/local/lib/node_modules/cjstoesm/node_modules/typescript/lib/typescript.js:111738:9)
    at emitWorker (/usr/local/lib/node_modules/cjstoesm/node_modules/typescript/lib/typescript.js:119072:33)
    at /usr/local/lib/node_modules/cjstoesm/node_modules/typescript/lib/typescript.js:119049:72 {
  errno: -17,
  syscall: 'mkdir',
  code: 'EEXIST',
  path: '/Users/msikma/Projects/callisto-test/packages/callisto-cli/package.json'
}

Node.js v18.3.0

Then I saw that in the animated example, the glob is encased in quotes, which means it passes on the glob literally instead of expanding it first, so I tried that:

cjstoesm "packages/**/*.*"
✔ packages/callisto-cli/index.js

This worked on one package (although it killed all the empty lines in it and removed the copyright notice at the top for some reason, but that's a different issue), but somehow it failed to work on the callisto-core package. It just didn't detect it somehow. Same if I use packages/**.

I decided to try using the output directory option, so I ran cjstoesm "packages/**/*.*" "packages_new". Again this only worked for the callisto-cli package (which has only one index.js file) and not for the callisto-core package. It also did not replicate the package directory structure.

Is there some way I'm doing it wrong? What would be the appropriate invocation for my situation? Also, is there a way to prevent the converter from removing additional whitespace in my files? (Here's the old and new.) I appreciate any help!

[Bug] A negative case

Example

const { ModuleKind, transpileModule } = require('typescript');
const { cjsToEsm } = require('@wessberg/cjs-to-esm-transformer');

const result = transpileModule(
  `
    export const SomeLib = require('lib-2');
    export const SomeLibVar = require('lib-2').someVar;
  `,
  {
    transformers: cjsToEsm(),
    compilerOptions: {
      module: ModuleKind.ESNext,
      target: ModuleKind.ESNext
    }
  }
);

console.log(result.outputText);

Output

import SomeLib from "lib-2";
export const SomeLibVar = SomeLib.someVar;

image

Expecting

import SomeLib from "lib-2";

export { SomeLib };
export const SomeLibVar = SomeLib.someVar;

Support for direct in-place replacement?

cjstoesm src/**/. src creates another src directory in src src/src

doing ejstoesm **/*.* . in /src directory does not work

or please add an option to copy other files such as .yml, .json files in the directory as well. I think I have to manually move those non js files for now

Preserve Comments

Sometimes comments are lost. Make sure they are preserved by leveraging @wessberg/ts-clone-node.

UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'kind' of undefined

While trying to transform the following file using the CLI command:

https://github.com/marijnh/orderedmap/blob/master/index.js

I get the following error:

$ yarn run cjstoesm transform index.js dist
yarn run v1.19.1
$ /home/X/Projects/prosemirror-esm/orderedmap/node_modules/.bin/cjstoesm transform src/index.js test
(node:20706) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'kind' of undefined
    at Object.isExpressionNode (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/typescript/lib/typescript.js:11368:33)
    at isExpression (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/@wessberg/cjs-to-esm-transformer/dist/cli/transform-task-29d3bc0d.js:1743:15)
    at visitBinaryExpression (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/@wessberg/cjs-to-esm-transformer/dist/cli/transform-task-29d3bc0d.js:1898:97)
    at visitNode (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/@wessberg/cjs-to-esm-transformer/dist/cli/transform-task-29d3bc0d.js:2117:16)
    at ts.visitEachChild.cbNode (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/@wessberg/cjs-to-esm-transformer/dist/cli/transform-task-29d3bc0d.js:2388:37)
    at visitNode (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/typescript/lib/typescript.js:70904:23)
    at Object.visitEachChild (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/typescript/lib/typescript.js:71187:59)
    at Object.childContinuation (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/@wessberg/cjs-to-esm-transformer/dist/cli/transform-task-29d3bc0d.js:2387:23)
    at visitNode (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/@wessberg/cjs-to-esm-transformer/dist/cli/transform-task-29d3bc0d.js:2122:20)
    at ts.visitEachChild.cbNode (/home/X/Projects/prosemirror-esm/orderedmap/node_modules/@wessberg/cjs-to-esm-transformer/dist/cli/transform-task-29d3bc0d.js:2388:37)
(node:20706) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:20706) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Provide API method to transform a source code string

The currently provided transform method takes a path to a file (or an array of paths) and optionally writes the transformed file(s) to the file system.

It would be nice to provide another method which takes a source code string and returns a transformed source code string. For example:

import {transform} from 'cjstoesm'

const input = `
  const {foo: bar} = require("./my-module")
  module.exports = {baz: 3}
`

await transform({ input })
//=> 'import {foo as bar} from "./my-module.js"\nexport const baz = 3'

(The method name could be different.)

Issue when trying to transform module.exports with object

  • Version: 2.1.2
  • TypeScript Version: 4.8.4
  • Operating System and version (if applicable): mac
  • Node Version (if applicable): 14.18

Desc

First at all, thanks for thus transformer, it works in 99% scenarios.

But still there are some scenarios where it's not working.
For example

module.exports = {
    'font-face': require('./font-face'),
};

will transform into the:

export const font-face = require('./font-face');
export default {
    font-face,
};

Check font-face transform - assigned to the new const value but in incorrect syntax: "font-face"

Expected behaviour

  • do not assign new const value and keep it somehow in export default statement?
  • or when creating new const value check if its in correct syntax.

[BUG] Cannot find module 'typescript'

I just installed your package: npm i cjstoesm -g then run cjstoesm in the console and got this error:

Error: Cannot find module 'typescript'
Require stack:
- /usr/local/lib/node_modules/cjstoesm/dist/cli/index.js
- /usr/local/lib/node_modules/cjstoesm/bin/cjstoesm
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:880:15)
    at Function.Module._load (internal/modules/cjs/loader.js:725:27)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/usr/local/lib/node_modules/cjstoesm/dist/cli/index.js:6:16)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Module.require (internal/modules/cjs/loader.js:952:19) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/usr/local/lib/node_modules/cjstoesm/dist/cli/index.js',
    '/usr/local/lib/node_modules/cjstoesm/bin/cjstoesm'
  ]
}

Doesn't work: missing outdir

I'm trying to follow the README instructions, and I get an error:

$ yarn cjstoesm -p tools/codemod/cjs-to-esm/fixtures/index.js dist                                                                                                                                                                                                                                                                                                 
yarn run v1.22.10
$ /Users/nheiner/code/tvui/node_modules/.bin/cjstoesm -p tools/codemod/cjs-to-esm/fixtures/index.js dist
error: missing required argument 'outDir'
error Command failed with exit code 1.

Error: Debug Failure. Unhandled SyntaxKind: ImportClause.

I tied to run the command using npx but I get the following error:

❯ npx -p typescript -p cjstoesm cjstoesm ./src/lib
Need to install the following packages:
  [email protected]
  [email protected]
Ok to proceed? (y) 
/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:110792
      Debug.fail(`Unhandled SyntaxKind: ${Debug.formatSyntaxKind(node.kind)}.`);
            ^

Error: Debug Failure. Unhandled SyntaxKind: ImportClause.
    at pipelineEmitWithHintWorker (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:110792:13)
    at pipelineEmitWithHint (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:110333:9)
    at pipelineEmitWithComments (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:114112:7)
    at pipelineEmit (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:110282:7)
    at emitExpression (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:110266:7)
    at emitImportDeclaration (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:112325:7)
    at pipelineEmitWithHintWorker (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:110533:20)
    at pipelineEmitWithHint (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:110333:9)
    at pipelineEmitWithComments (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:114112:7)
    at pipelineEmit (/home/ user/.npm/_npx/6ca627352432bfda/node_modules/typescript/lib/typescript.js:110282:7)

Seems like TypeScript is broken somehow but I don't understand why this happens. Any clues?

Does cjstoesm get dependent sources from node_modules (am I using it correctly)?

I was using this module to try to solve a pesky Error: 'default' is not exported by node_modules\<xyz> problem. But it didn't go as planned, so in my desperation reaching here to double-check if this is a problem that in principle is what this module should solve. 🔢

As it may be that I do not understand correctly that if this module indeed would solve, at least in some cases, such problems. It looks to be these are because of UMD -> ES6 export changes, but I may be wrong. The other things are that I think I should also use @rollup/plugin-node-resolve to resolve paths. Is this right?

I have defined

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

[...]

plugins: [    
    resolve({ browser: true, preferBuiltins: true }),
    ts({ transformers: [cjsToEsm()] })
  ]

Another thing is, does this plugin pick up also the dependencies that are referenced by the libraries imported by the code?

(That config is at https://github.com/veikkoeeva/erc1155sample/blob/main/web/rollup.config.js#L27 if it matters, and strangely there is a difference in output if I move it from baseConfig to the merge section, though it's not related to this module).

Error: Export 'LRUMap' is not defined

Continuing this conversation here...

Great! You know, if you keep helping me enough I will at eventually hit an error in the software you wrote :-). I think I have. With the above config and v0.0.17 I now have the following error:

(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/lru_map/lru.js: (1:9)
... (irrelevant)
[!] Error: Export 'LRUMap' is not defined
node_modules/lru_map/lru.js (1:9)
1: /**
      ^
2:  * A doubly linked list-based Least Recently Used (LRU) cache. Will keep most
3:  * recently used items while discarding least recently used items when its limit
Error: Export 'LRUMap' is not defined
    at error (/Users/martien/Sync/Projects/alfred-workflow-todoist/node_modules/rollup/dist/shared/node-entry.js:5400:30)
    at Module.error (/Users/martien/Sync/Projects/alfred-workflow-todoist/node_modules/rollup/dist/shared/node-entry.js:9820:16)
    at tryParse (/Users/martien/Sync/Projects/alfred-workflow-todoist/node_modules/rollup/dist/shared/node-entry.js:9713:23)
    at Module.setSource (/Users/martien/Sync/Projects/alfred-workflow-todoist/node_modules/rollup/dist/shared/node-entry.js:10076:33)
    at /Users/martien/Sync/Projects/alfred-workflow-todoist/node_modules/rollup/dist/shared/node-entry.js:12362:20
    at async Promise.all (index 2)
    at async Promise.all (index 4)
    at async Promise.all (index 2)
    at async Promise.all (index 3)
    at async Promise.all (index 0)

LRU map has a weird looking UMD factory, but I'm no expert. Is this something this library could/should fix?

Do not replicate the whole directory structure in the output

In a npm project I'm trying to transform a library in node_modules from the CLI.
When I execute a command like this: cjstoesm node_modules/@something/some_library_name/dist/index.js target the transformed output files are stored in target/node_modules/@something/some_library_name/dist where I would expect to have the transformed files directly in target.
The CLI is replicating the whole folder structure in the target dir.
This makes it a bit complicated to use the command from a top level directory.
I think this behaviour would be fine if I would run the command with a nonspecific glob like cjstoesm **/*.js target because in this case of cause the tool would run into conflicting files.
Wouldn't it be better to start replicating the folder structure only at the point were the glob has it's wildcards, for example cjstoesm node_modules/@something/**/*.js should replicate the folders below @something/?

Support import assertions

When rewriting CJS require statements with .json extensions, include import assertions in the ESM output and add an option for customizing this behavior, such as potentially opting out of it.

import is needless for the dead code

I have the code below

require('./c');

if (true) {
  require('./a');
} else {
  require('./b');
}

function A () {
  console.log('hhhh');
}

module.exports = A;

and I use commond cjstoesm a.js esm to transform. I got the result below

image

would you please to support this feature?

Improper transform on module.exports = { a: require('./b') }

Input:

module.exports = {
    parse: require('./lib/parse'),
    stringify: require('./lib/stringify')
};

Output:

export const parse = require('./lib/parse');
export const stringify = require('./lib/stringify');
export default {
    parse,
    stringify
};

NPM package does not export Typescript transformer

Hi!

Thanks for providing with what seem to be a great tool!

I have been trying to use your Typescript transformer, but without success. It seems like the NPM package doesn't export it.

My code:

import {cjsToEsmTransformerFactory} from 'cjstoesm';

export default class TypescriptTranspiler {
	public transpileModule(
		code: string,
		filepath: string,
	): string {
		const results = TypeScript.transpileModule(code, {
			compilerOptions: COMPILER_OPTIONS,
			fileName: filepath,
			transformers: {
                                before: [cjsToEsmTransformerFactory()]
                            }
		});
                return results.outputText.toString();
	}
}

Code in cjstoesm/dist/lib/esm/index.js:

import 'typescript';
import 'resolve';
import 'path';
import 'slash';
import '@wessberg/stringutil';
import 'reserved-words';
import 'fs';
import 'util';
import 'glob';
import 'chalk';
//# sourceMappingURL=index.js.map

The exports seems to be missing and I can't find the code for "cjsToEsmTransformerFactory" in the package.

feature request: move requires at bottom of file to top

Feature Use Case

I have worked in a few large codebases, where due to difficulties with circular requires, the requires eventually were simply all added at the bottom of the file (unless used at top-level in the module, such as _.once). It would be very helpful if there were an option to move all requires at the bottom of the file, to imports at the top. It doesn't need to try and position them correctly alongside other imports, comments, etc, but simply move all contiguous imports at the bottom of the file, to the top (possibly after existing block comments and requires/imports).

Feature Proposal

Example: cjstoesm --move-to-top ...

Before:

/**
 * file block comment
**/

const _ = require('lodash')

module.exports = { ... }

const a = require('some-npm-package')

const b = require('some-local-package/a')

const c = require('./some-local-lib/c')
const d = require('./some-local-lib/d')

After:

/**
 * file block comment
**/

import _ from 'lodash'
import a from 'some-npm-package'

import b from 'some-local-package/a'

import c from './some-local-lib/c'
import d from './some-local-lib/d'

export default { ... }

CategoryInfo : ObjectNotFound: (cjstoesm:String) [], CommandNotFoundException

I've correctly installed cjstoesm with npm install cjstoesm but every time I run cjstoesm I got the error:

cjstoesm : Termine 'cjstoesm' non riconosciuto come nome di cmdlet, funzione, programma eseguibile o file script. Controllare l'ortografia del nome o verificare che il percorso sia incluso   
e corretto, quindi riprovare.
In riga:1 car:1
+ cjstoesm --help
+ ~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (cjstoesm:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

what am I doing wrong?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.