The main distinction between ESM and CJS is the loading of modules. ESM loads asynchronous and CJS loads synchronous. There are limitations in interoperability, while ESM can import CJS, CJS cannot require ESM, because it would break the synchronous constraint.
For modules to work in both worlds (ESM and CJS), they would have to expose a CJS interface, but ESM is JavaScripts native module system. As you can see, this is quite a tension point in the node ecosystem.
Let’s take this example of Chai module
- Chai now only supports EcmaScript Modules (ESM). This means your tests will need to either have
import {...} from 'chai'
orimport('chai')
.require('chai')
will cause failures in nodejs. If you’re using ESM and seeing failures, it may be due to a bundler or transpiler which is incorrectly converting import statements into require calls.
But the project is still a CJS module, hence, you would see the following error
Solution: There is a workaround to asynchronously load an ESM module for use in a CJS module, it is based on dynamic import.
let expect;
import('chai').then(chai => {
expect = chai.expect;
chai.use(require('chai-string'));
// @ts-ignore
chai.use(require('chai-exclude'));
chai.use(require('chai-match-pattern'));
chai.use(require('chai-json-schema'));
});
Then you’re good to go!