Node has been implementing more and more ES6+ (ESNext) features natively. One of the features that is taking the longest to implement is modules. The reason for this is that Node and npm run on what is called CommonJS, with which you use require(
'
module-name
'
)
to import from other modules and use the module.exports
object to expose entities from a module.
Node’s CommonJS was actually one of the first widely adopted module systems in JavaScript. The ease with which one can bundle CommonJS coupled with its widespread use in Node applications and tools means CommonJS quickly displaced RequireJS and SystemJS for frontend application dependency and module management
CommonJS has some drawbacks, like being hard to statically analyse, which leads to for example bloated bundles. It’s also just not part of the ECMAScript specification, which ES modules are.
For anyone who is still wondering, ECMAScript (or ES) modules use a syntax with import thing from 'my-module';
or import { something } from 'my-module'
to import things and export default
or export something
to expose entities from the module.
Bundlers like Webpack, Rollup and Parcel have support for ES modules. For a Node server I’ve still tended to write in CommonJS style because Node has great support for most ESNext features out of the box (eg. rest/spread, async/await, destructuring, class, shorthand object syntax) and I don’t like messing with bundlers and transpilers.
I’ve discovered the esm module, “Tomorrow’s ECMAScript modules today!” by John-David Dalton (of lodash 😄). It allows you to use ES modules in Node with no compilation step. It’s small, has a small footprint and comes with some extra goodies
What follows is some ways to use it that aren’t strictly documented. This covers use-cases like incremental adoption of ES modules (ie. convert some modules to ESM but not the whole app). Using this will help you share
Import default export from an ES module in CommonJS
const esmImport = require('esm')(module);
const foo = esmImport('./my-foo');
console.log(foo);
Import named exports from an ES module in CommonJS
const esmImport = require('esm')(module);
const { bar, baz } = esmImport('./my-foo');
console.log(bar, baz);
Re-export an ES module as CommonJS
This is documented in the docs but I thought I would include it for completeness
module.exports = require('esm')(module)('./my-es-module');
// see the docs
// https://github.com/standard-things/esm#getting-started
Load whole application using ES modules
Again, this in the docs but including it for completeness
node -r esm app.js
// see the docs
// https://github.com/standard-things/esm#getting-started
Using top-level await
Let’s say we have this module cli.module.js
(taken from github.com/HugoDF/wait-for-pg):
const waitForPostgres = () => Promise.resolve();
try {
await waitForPostgres();
console.log('Success');
process.exit(0);
} catch (error) {
process.exit(1);
}
The interesting bit is that this is using await
without being in an async
function. That’s something esm
allows you to do. This can be enabled by setting "
esm
"
: {
"
await
"
: true }
in package.json
but it can also be enabled at conversion time cli.js
:
const esmImport = require('esm')(module, { await: true });
module.exports = esmImport('./cli.module');
Lo and behold it works:
$ node cli.js
Success
That wraps up how to use ES modules now, without transpilation. There’s a more thorough walkthrough of what that means at ES6 by example: a module/CLI.
If you’re interested in “history of JavaScript module, bundling + dependency management” article, let me know by subscribing.
Top comments (0)