New ECMAScript Modules in Node v12
Publikováno: 9.5.2019
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
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:
- 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.
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.
- The CommonJS Module type and
- 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.
node index.js --experimental-modules
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.