26

I am working in NodeJS. I have a great deal of legacy code including several packages that are used in many places. This code is all CommonJS, Node require() module structures.

Node now supports ES6. Since it is a Javascript language feature, I would like to migrate to it.

Today, I started a small project. My small project boilerplate requires() a couple of my favorite utilities and then says 'Hello World'. I edited it to import said utilities. Node told me I needed to add "type":"module" to my package.json and I did.

When I ran it, I was told that "require is not defined", this in reference to one of the utility modules I imported.

I infer that this means that a project is either CommonJS or ES6 and it appears that never the twain shall meet. I am surprised by this because it means that I will never use ES6 in NodeJS because I will never be able to change all of the modules I require(). Some are not even mine, others are used in projects (npm!) that I do not even know about.

Honestly, I have a hard time believing that this is the case. I don't understand how ES6 can ever become a widely used standard because of if ES^ and CommonJS cannot be used together in an application. I realize that Webpack, etc, will preprocess code and revise all the require() statements but not everyone uses that sort of utility.

My questions are:

Is this analysis correct?

Is there some workaround that will let me use both module systems (without a preprocessor)?

Is my impending decision to never, ever use ES6 the right one?

6
  • Different packages should be able to use different module formats. Have a look at stackoverflow.com/q/61549406/1048572 Commented Apr 24, 2021 at 20:41
  • pencilflip.medium.com/… Commented Aug 3, 2021 at 7:48
  • I can't see anything that properly and clearly answers this question... did you work out a way of mixing and matching? Commented Nov 4, 2021 at 7:42
  • I did not find a decent way to mix module formats. In various places, people suggested cumbersome workarounds. I have been writing NodeJS code since 2010. I have a ton of useful library code in CommonJS. I have concluded that I will never use ES6. There is no possibility that ES6 modules provide sufficient benefit to rewrite everything. Commented Oct 20, 2022 at 11:27
  • 1
    There are modules that are only published as ES6. When those are needed, use:L const thing=await import('thing'); //remember to add "async" to the containing function definition Commented Dec 8, 2023 at 15:51

7 Answers 7

6

I'm NOT a javascript person so I humbly accept any corrections here. I had the same question and was surprised at the lack of clarity on it in the usual places.

The confusing thing is that where you actually mix these, there are two files (modules) involved - the importing file and the imported file. So, if your index (or other entry point) file is ES-based and it needs to include a CommonJS file - which format do you use? For the most common basic case of importing CommonJS files/libraries into a modern ES6 app):

  1. Do NOT specify type:module in package.json (this leaves the default as CommonJS)
  2. File Extensions: Use the .mjs extension for all of the ES6 files. Use (or leave) the older .js extension for all of the CommonJS files
  3. Export statements: Use export keyword (e.g."export functionName") for all ES6 exports - use "module.exports" for all CommonJS exports
  4. Import statements: use the importing syntax for whatever file type you are importing to. For example, if you are in an ES6 module file and need to bring in a CommonJS file, use the ES6 import syntax

enter image description here

Sign up to request clarification or add additional context in comments.

2 Comments

It should be module.exports and not modules.export. And yeah, unfortunately, anything JS-related always lacks explanations.
3

For node.js 16.18.0

package.json

{
  "name": "es6_commonjs",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

book.cjs

module.exports = class Book {
    constructor(name) {
        this.name = name;
    }
}

student.js

export class Student {
    constructor(name) {
        this.name = name;
    }
}

app.js

import { Student } from "./student.js";
import Book from './book.cjs';

const student = new Student('Name');
console.log(student);

const book = new Book('Name');
console.log(book);

run command node ./app.js

result:

Student { name: 'Name' }
Book { name: 'Name' }

1 Comment

Your code shows that commonjs and es6 work well together if you only use import and not require.
3

Here's how to deal with ES6 in a CommonJS project:

const thing=await import('thing'); //remember to add "async" to the containing function definition

Nothing else is needed. Also, nothing bad will happen. Everything will just work. This should have been the only answer.

2 Comments

Im using a reactjs functional component and when I place this at the top of the file with the other imports it throws an error on the async
This answer lacks explanation, and is also wrong. Or at least, it didn't work for me. Which is a totally contradiction to the highly confident statement Everything will just work.
1

I update the NodeJs documentation link: https://nodejs.org/api/packages.html#determining-module-system

The rules for dual CommonJS/ES module packages are well explained...

2 Comments

I think that documentation talks about how to create dual use modules, not how to use an ES module in a CommonJS project.
1

The simple way to make this work (as already answered by @tqwhite) is to use dynamic imports. This poses some challenges because:

  • we can not await at the top level (yet), so we need to create an async function around it
  • we might need to use the imported module in several places and importing multiple times might break things

Here is a slightly improved version that can work in a wide range of use cases

let thiing;
const initialize = async () => {
  if (!thing) thing = await import('thing');
  return thing;
}
// ...
// use inside async function
await initialize();
thing.someFunction();
// use with promise chaining
initialize().then(thing => thing.someFunction());
// use with React => your component will know when when it's safe ot use 'thing'
const [initialized, setInitialized] = React.useState(false);
React.useEffect(() => {
  initialize().then(() => setInitialized(true));
}, [])

Important: if you do use Babel and preset-env, it is important to exclude the proposal-dynamic-import plugin or it will break the dynamic import.

Comments

1

I wanted to give a full example based on @tqwhite's answer. I have a simple legacy javascript code base and want to use the ES6 based "svgcanvas" library. This is how I import it in my html file:

<script type="module">
// es6 modules javascript

async function loadEsModules() {
    const module = await import('./js/svgcanvas.js');
    window.SvgCanvas = module.default;
}
await loadEsModules();

</script>

<script>
    //legacy javascript
    let editor = new SvgCanvas(...)
</script>

Comments

-4

use import( ... ) in .cjs or .mjs files

see

2 Comments

While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From Review
This post does not address the question of using ES6 alongside CommonJS modules. These are links to information about using the Import statement and not useful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.