We're live-coding on YouTube! Join us!

New ECMAScript Modules in Node v12

New ECMAScript Modules in Node v12

If you’re familiar with popular JavaScript frontend frameworks like React, Angular, etc, then the concept of ECMAScript won’t be entirely new to you. ES Modules have the import and export syntax we see often in frontend frameworks. Node uses CommonJS which relies on require()** for imports.**

We can now use import and export in Node in addition to require().

That’s not all that comes with the new Node v12 release, let’s talk about all the new cool features we can expect.

New features in Node v12

New ES Modules

Before now, a previous Node release shipped with experimental support for ECMAScript modules. This support was behind the --experimental-modules **flag and still is, but a lot has happened since the last release and we are happy for what is coming next. We know for a fact that some of the concerns and feedbacks raised from the last release has been acted on and is coming to life in the next release.

The outstanding feedback being that Node.js needs to provide a way to use import and export syntax in *.js* files. In the new --experimental-modules, two ways will be provided for you to be able to do this:

  1. Set “type” : “module” in package.json
    // package.json
    {
      "type": "module"
    }

This tells Node.js to treat all .js files in your project as ES Modules. If you still have CommonJS files that you would not like to treat as modules, you can rename them with the .cjs file extension, which will tell Node.js to parse them as CommonJS explicitly. Alternatively, you can put all your CommonJS files in a subfolder containing a package.json file with "type":"commonjs", under which all .js files are treated as CommonJS.

Note, If the nearest parent package.json file lacks a "type" field, or contains "type": "commonjs", extensionless and .js files are treated as CommonJS. If the volume root is reached and no package.json is found, Node.js defers to the default, a package.json with no "type" field.import statements of .js and extensionless files are treated as ES modules if the nearest parent package.json contains "type": "module".

You can think of this type and extension scoping as the way CSS order of precedence works, it cascades down the tree from the child all the way up to the parent.

Essential Reading: Learn React from Scratch! (2019 Edition)

2.** Use the** --input-type flag

node --experimental-modules --input-type=module --eval \
  "import { sep } from 'path'; console.log(sep);"

echo "import { sep } from 'path'; console.log(sep);" | \
  node --experimental-modules --input-type=module

The second way of using the import and export syntax is to use --input-type=module to run string input (via --eval, --print or STDIN) as an ES module. The --input-type flag can be either --input-type=module or --input-type=commonjs. In our case, it’ll be the earlier since we are setting it to modules, not CommonJS.

More expected WIP features

Given that the Node.js Modules team are still working on these features, we can’t exactly report them as existing features. Why? they are still being worked on so they are all probably still going to change, however, we have an idea of what they are and how they are likely to behave.

Module Loaders

The Very WIP feature. Though the first implementation of the --loader API has shipped, it's still being worked on hence, it is still subject to changes **in the next release.

Package path maps

This feature has not shipped yet. It would redefine how we make module imports and allow for less verbose imports in certain situations.

Automatic entry point module type detection

This feature will make it possible for Node to identify the type of module we have present in our projects. When shipped, it will be possible for Node to know if a particular module is an ES Module or a CommonJS Module.

There are so many other awesome features in Node v12, feel free to dig deeper and get a piece of the awesomeness in the official blog post by the Modules Team on Medium. But in the meantime, let’s take a closer look at the types of modules we have in Node.

Different Types of Modules in Node

This might look very obvious at this point but i have come to understand with experience that most people have troubles understanding the different types of modules we have in Node. Given that until recently, all we needed in Node is the CommonJS syntax, it’s understandable that a few people find it confusing.

There are basically two module types in Node.

  1. The CommonJS Module type and
  2. The ES Module type

CommonJS Module

The CommonJS Module is the default module that comes with Node. Before the inception of the ES Modules, every Node application works with the CommonJS module construct that uses the require() and module.exports syntax to pull in modules and export them. Consider this example:

//src/main.js
var products = require('src/products'); // import module

Here, we just imported a products module into our main.js file from a different file in our app’s src directory. We can go ahead and use the module in the current file as we please. How is this possible? Because the products function is set to the exports object in the src/products file according to CommonJS specification.

//src/products.js
exports = function(){
    return response.get('all-products);
}

Node.js implementation

The Node.js implementation is not so different from what we’ve just seen above from CommonJS. The difference is in the way the modules are exported from their host files. While CommonJS exports modules with the exports variable, Node modules uses module.exports object.

//src/products
function products(){
    return response.get('all-products);
}
modules.exports = products;

Just like the CommonJS implementation, this is equally a synchronous process as the files are loaded one after the other in the order they appear inside the file.

ES Modules

ES Modules can be considered an improvement on the CommonJS module system. It offers possibilities for importing and exporting modules just by using the import and export keywords as a replacement for require in CommonJS. Unlike CommonJS, the ES Modules are compatible with both synchronous and asynchronous modes of operation.

Considering the products example we saw with CommonJS on the previous examples above, we can redo the file with ES Modules like so:

//src/main.js
import products from 'src/products'

As we explained before, the import statement is used to bring modules into the namespace. It operates almost exactly as the require alternative in CommonJS but it is not dynamic, hence, you cannot use it anywhere in the file. Again, we were able to import this file for use here because it has probably been exported from it’s host file.

//src/products.js
export function products() {
    return response.get('all-products);
}

The export statement here makes it possible for you to access this function from another file. Simply put, it makes the function widely accessible. As a result of this, static analyzers will first build the tree of dependencies while bundling the file before eventually running code. This is in my opinion a major advantage of using ES Modules.

ESM in Node in the past

The concept of ES Modules in Node is not exactly new, it’s been available since 2017 when Node.js 8.9.0 shipped experimental support for ECMAScript modules, known for their import and export statements behind the --experimental-modules flag.

However, up until this moment, this feature has remained in the experimental state. Reason? to allow the Node.js community the time to use it and provide actionable feedback on that design. Since then, a lot has happened. Major browsers now support ECMAScript modules (ES modules) via *<script type=*``"``*module*``"``*>*. Npm packages with ES module sources are now available for use in browsers via *<script type=*``"``*module*``"``*>*. Support for import maps, which bring to browsers Node.js-style package names in import statements, is coming to Chrome. And a lot more other things.

Having waited for a long time, received feedback on the experimental ESM design and owing to the fact that there are other runtimes and environments where ES modules are in use, there’s no better time for Node.js to support this ESM JavaScript standard than now. And this is why the Modules Team has announced a new implementation for supporting ES modules. According to them, it will ship as part of Node.js 12 and will replace the old *--experimental-modules* implementation, behind the same flag.

Get started with v12 using nvm

While we wait for a long term support for ESM, we can start getting familiar with its operations and features using nvm. What is nvm? nvm lets you install multiple versions of node on one machine and switch between them when you need to.

What this means is that, even though you’re currently running Node v6 or v10 whatever version, you can switch up to the latest version or an older version and try out things while still keeping your current version and files intact.

I would love to cover how you can go about installing nvm, and using it to manage your Node versions to try out different features that don’t yet exist in your current version but Michael Wanyoike has already done a great job at it on this post on Sitepoint. If you will be interested in learning how nvm works on different OSS platforms, give the post a visit.

Use —experimental-modules flag to support imports until LTS

Like we mentioned earlier on in this post, ES Modules has been around for a while and there are basically two intuitive ways we can go about using them until LTS.

  1. node index.js --experimental-modules
  2. node --experimental-modules index.js

Unfortunately the first way i.e using the node index.js --experimental-modules does not work, hence, to support ESM imports in your Node applications, you will have to use the second option above.

Dynamic imports is going nowhere

It is worthy to note that dynamic imports are not under any threat from the incoming ES Modules implementation. Prior to this ESM era, dynamic imports provided us with the ability to dynamically import and use modules from different locations in our application.

Given that dynamic imports returns a promise for the module namespace object of the requested module, it's possible to use async/await and the .then()-based callback style to handle all module behaviours which makes it possible to load modules asynchronously. This is why we all love it and it’s just a good news to know that we won’t lose it to the new implementations.

What about importing JSON files?

For now, importing JSON modules are not supported out of the box yet in the module mode. It is however, supported in the commonjs mode and are loader with the CJS loader. That said, efforts are currently being made to ensure that JSON imports are supported in the module mode. For instance, at the moment, WHATWG JSON modules are currently being standardized, and are experimentally supported by including the additional flag --experimental-json-modules when running Node.js.

Even though JSON imports are supported out of the box in commonjs mode, when the --experimental-json-modules flag is included, both the commonjs and module mode will use the new experimental JSON loader.

There’s a bit of a drawback though, the imported JSON only exposes a default, so right now there is no support for named exports. Since the experimental JSON loader takes precedence over the commonjs mode too, a separate cache entry is created in the CommonJS cache, to avoid duplication. The same object will be returned in CommonJS if the JSON module has already been imported from the same path. Consider the example below:

import productsConfif from './package.json';

For this to actually work, we’ll need to update

node --experimental-modules index.js

to

node --experimental-modules --entry-type=module --experimental-json-modules index.js

The earlier will fail because of the unavailability of the --experimental-json-modules.

Final thoughts

There’s been so much talk around the new EcmaScript Modules coming to Node.js v12. In this post we took a closer look at it to understand what it offers as compared to the usual CommonJS alternative.

Like this article? Follow @codebeast on Twitter