Files with .mjs extension for JavaScript modules

They are coming! You can see them in the Node.js documentation and also in the new brand Webpack 4. But… what did you said are those .mjs files for?

.mjs an extension for EcmaScript modules

Modules are the way we have in JavaScript to split our code in different files, keeping our code concise and well organized. Modules can be “required” or “imported” from other modules, that way we can reuse their functionality and compose our apps and libraries.

require and import are key terms when we speak about modules, they define the two most popular ways of using JavaScript modules nowadays.

Require is used to load CommonJS modules. CommonJS modules have been used by Node.js from its initial days and they are a very well known way to encapsulate your code in files. An example of a CommonJS module definition is:

function hi(){ console.log('Hi') };
module.exports = hi;

Can you see that module.exports = hi? That is what our module expose publicly to be used. We can save it in a file named hi.js and then access to the function hi from other modules by using var hi = require('./hi.js').

Import is how EcmaScript modules are loaded. They are much newer than CommonJS ones, but they are the standard since ES6 so we should get used to create this kind of modules. They look like this:

function hi(){ console.log('Hi') }
export default hi;

In this case, the hi function is rendered public by using export default hi. ES modules are the target for files with .mjs extension. So we can save our module in a file named hi.mjs and get to use the hi function by typing import hi from './hi.js' from any other module.

Are .mjs files needed at all?

You might be thinking that we could have saved the ES module in a file named hi.js and imported it in a ES fashion, no need for a new extension.

You are right, everybody does that. But we can do it just because there are nice bundlers like webpack, that you can configure to load ES modules by default, so no matter what extension the file has, you get your hi function anyways.

Webpack saves our asses. Thanks to it we can mix libraries written in CommonJS modules with our nice app written using the cool ES modules. However the ability of loading both module types is also a bit of a mess. The line that separates both type of modules is difficult to see, sometimes it’s just confusing to use var hi = require('./hi') to load a ES module, and then we need to do something like hi.default() to actually call it. We should just to get stuck to a single way of defining modules.

.mjs files are a nice tool to make clear what kind of module we are handling. Right now they are more than just a kind of convention, naming our files with .mjs instead of .js actually changes the way the files are handled by the interpreters, they feel like a new content-type.

Webpack 4 introduced modules types, and there is a javascript/esm type, active by default for .mjs files. When loading javascript/esm modules, webpack’s life easier, it doesn’t have to guess what kind of module is loading anymore.

Beyond the bundler world, using an specific extension for ES modules also may have consequences:

  • In Node.js, loaders are currently getting a format: 'esm' from the resolve hook for .mjs files, so they can handle those files in a different way.
  • Currently we can use <script type="module" src="hi.js"> to load ES modules in modern browsers. We are loading a “module” type of content, so wouldn’t be nice if we just could write <script src="hi.mjs"> and get the same experience? Not happening yet, though.

Should we start using .mjs files?

This should be the point of the article where we said: “The future has arrived! Don’t miss the chance of embracing the cutting edge technology!” (I am not really sure if a file extension can be called technology).

But it’s not that easy. As we saw before, changing the extension of your .js files into .mjs may change the behavior of the interpreter of the files (webpack, node, browser…), so we have be cautious about it.

This article started because I was making some tests with Inferno.js for the PassPill Project and I couldn’t make it work with webpack 4. The reason was that Inferno.js was using .mjs files for packaging their modules, and they were mixing them somehow with other CommonJS ones, so webpack just failed with lots of the errors in the way:

Can't import the named export 'render' from non EcmaScript module

Or

Can't reexport the named export 'Component' from non EcmaScript module

Inferno.js is preparing a new major version release and it won’t use .mjs files anymore.

But it wasn’t a problem just for inferno.js. After searching a bit about it, I have found lots of big libraries having problems with .mjs files. The problem is always trying to access to non ES modules from them. Some examples of those libraries are create-react-app, react-apollo or graphql-js.

If we decide to use the .mjs extension right now, we need to keep in mind that by changing the extension, we may change the way the module is compiled and, in case we have dependencies that are not defined as ES modules, we will be facing problems.

In the case that our project has no CommonJS dependencies and we can build it using only ES Modules, go for it! They will be understood natively by newest Node.js, Webpack will bundle them much faster and maybe, in the future, browser will load them automatically as modules.


.mjs turned out to be more than an extension, so we need watch out before start changing the names of our files.

This article is part of the PassPill Project that wants to share the whole development of a web app. In fact, it’s the first article that it’s not directly related to the project or the app’s development, but it’s always nice to write about interesting JavaScript topics. There will be more like this.

If you liked the post, don’t forget to follow our publication and @passpillio in twitter in order not to miss any update!