mixing es modules and commonjs modules: don't

2020-02-16

 | 

~3 min read

 | 

539 words

Progress toward a unified module system in Javascript is proceeding, but we’ve yet to arrive at a unified experience.

While we wait, I ran into a bit of a headache with some utility functions I’d written. (I have written a primer to some of the differences from a tactical perspective between the module standards previously. Unfortunately, that didn’t save me.)

I almost always start writing functions as ES Modules, however, I noticed some of my code needed to run in Node, so I converted it over to CommonJS.1

This is when I ran into problems because I didn’t refactor correctly.

Let’s look at some simple examples and some error messages we might see if we make a misstep.

This:

utils/myFunc.js
import React from 'react'

export function myFunc(args) {
  /*... */
  return (/*...*/);
}

Becomes this:

utils/myFunc.js
const React = require('react')

function myFunc(args) {
  /*... */
  return (/*...*/);
}

module.exports = { myFunc }

Note that not only did we change the export type, but we no longer import react. Instead we require it. If we don’t, we will get the error: TypeError: Cannot assign to read only property 'exports' of object '#<Object.

Furthermore, the way we handle this with an index.js file is also different than when the files are using ESModules.

utils/index.js
const myFunc = require("./myFunc")

module.exports = { ...myFunc }

Because the export from myFunc is an object, we need to spread the methods within the new exports object. While we could have simply written module.exports = myFunc in utils/myFunc.js, the use of the {} makes it more easily extendable in the future.

This raises another potentially easy error to get. Imagine not refactoring to CommonJS modules and instead of a named export, we use a default export:

utils/myFunc.js
import React from 'react'

function myFunc(args) {
  /*... */
  return (/*...*/);
}

export default myFunc

If we tried to reference the myFunc in the module.exports within utils/index.js we’d get the error: TypeError: Object(...) is not a function.

This is true in any of these situations:

utils/index.js
const myFunc = require("./myFunc")
//OR
const myFunc = require("./myFunc").myFunc

module.exports = { myFunc }
//OR
module.exports = { ...myFunc }

I’m a little fuzzy on why, however, we can resolve this by using the named export:

/utils/myFunc.js
import React from 'react'

export function myFunc(args) {
  /*... */
  return (/*...*/);
}

And then in utils/index.js:

utils/index.js
const myFunc = require("./myFunc").myFunc

module.exports = { getBlurb }

In this case, we’re pulling the specific function out of the module that’s required and then passing it along to the module.exports object in utils/index.js.

Conclusion

My key takeaway: Don’t mix CommonJS and ES Modules within the same file.

Beyond that, it’s just a matter of syntax. Hopefully my enduring this headache and writing about it will help you avoid a similarly painful experience in the future!

Footnotes

  • 1 As a reminder, ES Modules are those that are loaded into a file with syntax like:
import <module> from <package>

Whereas the same function in CommonJS would be:

const <module> = require(<package>)

Flavio Copes has a nice introduction on ES MOdules that’s worth a read if you would like more info.


Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!