Tutorial

Introduction to New ECMAScript Modules in Node v12

Published on December 12, 2019
Default avatar

By Chris Nwamba

Introduction to New ECMAScript Modules in Node v12

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

###Introduction If you’re familiar with popular JavaScript frontend frameworks like React and Angular, 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 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 are provided for you to 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.

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.

  1. 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 new features in Node v12, feel free to dig deeper 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 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 two module types in Node.

  1. The CommonJS Module type
  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 its 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. 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 two 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 article we took a closer look at it to understand what it offers as compared to the usual CommonJS alternative.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Chris Nwamba

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel