DEV Community

Paul Walker
Paul Walker

Posted on • Originally published at on

Things I wish I’d known sooner about… JavaScript modules

Things I wish I knew about… JavaScript modules

I have to confess it took me a while to get my head around JavaScript modules. This is at least partly because there are two different popular module systems, CommonJS and ECMAScript 2015 (usually shortened to ES2015).

The ECMAScript 2015 standard is newer than CommonJS. Personally I find it more straightforward, and I use it in preference where I can. Quite a lot of the modules on NPM still use the old style, however.

One important note - browsers only support ES2015, not CommonJS.


CommonJS was started in 2009 by a member of the Mozilla team, as an attempt to unify JavaScript functionality. CommonJS covers more than just modules - check the website.


In CommonJS modules, symbols are exported in an object (understandably named exports). Each property of the object is an exported symbol.

The usual object assignment rules apply. This means you can easily export a symbol with a different name than it has internal to the file.

function sayHello(name) {
  console.log("Hello", name);

module.exports = {
  anotherSayHello: sayHello

Enter fullscreen mode Exit fullscreen mode


You can then require either the whole module or some symbols from that module.

const testModule = require("./module_name");
const { sayHello } = require("./module_name");

sayHello("second world");
Enter fullscreen mode Exit fullscreen mode

NB : importing named symbols from a CJS-style module was only introduced in Node 14, and doesn't work perfectly - it relies on some heuristics within Node.



Exporting is easier in ES2015; an item (function, constant, variable etc) can be exported simply by adding the export keyword.

export function sayHello(name) {
  console.log("Hello", name);

export const myName = "Rumpelstiltskin";
Enter fullscreen mode Exit fullscreen mode

Alternatively, you can export the items en-masse at the end of the file.

export { sayHello, myName };

Enter fullscreen mode Exit fullscreen mode


Like many other languages - Java, Python - you then import the parts of the module that you want to use.

import { sayHello } from "./moduleName";

Enter fullscreen mode Exit fullscreen mode

Or if you want to keep them namespaced:

import * as moduleStuff from "./moduleName";

Enter fullscreen mode Exit fullscreen mode

Default exports

In most of the examples above, we've imported a specific named symbol from module:

import { sayHello } from "./module_name";
Enter fullscreen mode Exit fullscreen mode

A module can also have a default export, which is what you then get if you don't specify what you want to import from the module.


CJS modules actually only export one single item - in that sense, they only have a default export. It's why the items we want to export need to be properties of an object.


With this system you do get more flexibility. More than one thing can be exported, and you're able to mark which one you want to be the default.

export default const theAnswer = 42;
Enter fullscreen mode Exit fullscreen mode

User beware

This isn't always a good thing; you're never totally sure what you're importing, and it can be imported with a completely different (and misleading) name to the original.

For example, take the trivial module basicMaths:

export default sum(x, y) { return x + y; }
Enter fullscreen mode Exit fullscreen mode

That default export can be imported as anything.

import divide from "./basicMaths";

// This will not do what you expect.
divide(2, 4);
Enter fullscreen mode Exit fullscreen mode

The advantage of named exports is that you import exactly what you want, no surprises.

import { divide } from "./basicMaths";

// divide is empty, so this crashes (or doesn't build, depending on how
// good your tools are). Either way it's better than using the wrong 
// function.
divide(2, 4);
Enter fullscreen mode Exit fullscreen mode

Other reading


Oldest comments (0)