ECMAScript modules are the official standard format to package JavaScript code for reuse. Modules are defined using a variety of import and export statements.
Node.js fully supports ECMAScript modules as they are currently specified and provides limited interoperability between them and the existing module format, CommonJS.
Node.js contains support for ES Modules based upon the Node.js EP for ES Modules and the ECMAScript-modules implementation.
Expect major changes in the implementation including interoperability support, specifier resolution, and default behavior.
In this article, we’ll explore How to use ECMAScript modules with Node.js. We’ll create a couple examples to give you a closer look at how you can migrate your codebase to make use the power of ESM.
If you’re a beginner in ES modules, let’s take a closer look at how to use them. If you’ve ever used React or Vue.js, you’ve probably seen something like this:
import React, {Fragment} from 'react'; // or import Vue from './vue.mjs';
The first example, in particular, is a good one because it expresses the nature of ES modules for what is a default module or not. Consider the following code snippet:
export default React;
We can only have one default module exported by file. That’s why Fragment
has to be imported into the { }
s once it is not included as a default. Its exportation would look like:
export const Fragment = … ;
And you can, obviously, create your own, like so:
export const itsMine = 'It is my module';
Go and save this code into an mjs
extension file, and just as we saw in the React example, you can import it to another file:
import { itsMine } from './myESTest.mjs' alert(itsMine); // it'll alert 'It is my module' text
The mjs
extension can lead to some confusion when we compare its use against js
files. For JavaScript specification, there are differences between them. For example, modules are, by definition, strict (as in 'use strict'
), so it means that a lot of checks are made and “unsafe” actions are prohibited when implementing your JavaScript modules.
The js
vs. mjs
fight extends to the fact that JavaScript needs to know if it’s dealing with a module or a script, but the spec doesn’t provide it so far. If you get a CommonJS script, for example, you’re not allowed to use 'import from'
in it (just require
), so they can force each extension to import the appropriate, respective one:
mjs import from mjs
js require js
So, what happens to the following scenario?
mjs import from js
js require mjs
When it comes to ES modules, it’s well known that they’re static — i.e, you can only “go to” them at compilation time, not runtime. That’s why we have to import
them in the beginning of the file.
The first thing to notice here is that you cannot use require
in a mjs
file. Instead, we must use the import syntax we’ve previously seen:
import itsMine from './myESTest.js'
But only if the default import (module.exports
) has been exported into the CommonJS file (myESTest.js
). Simple, isn’t it?
However, when the opposite takes place, we can’t simply use:
const itsMine require('./myESTest.mjs')
Remember, ESM can’t be imported via the require
function. On the other side, if you try the import from
syntax, we’ll get an error because CommonJS files are not allowed to use it:
import { itsMine } from './myESTest.mjs' // will err
Domenic Denicola proposed a process to dynamically import ES modules via the import()
function in various ways. Please refer to the link to read a bit more about it. With it, our code will look like this:
async function myFunc() { const { itsMine } = await import('./myESTest.mjs') } myFunc()
Note, however, that this approach will lead us to make use of an async function. You can also implement this via callbacks, promises, and other techniques described in more detail here.
Note: This type of importing is only available from Node 10+.
There are two main ways for you to run Node.js along with ES modules:
--experimental-modules
, which stores the MVP for average usageIn the Node GitHub repo, you can find a page called “Plan for New Modules Implementation,” where you can follow the official plan to support ECMAScript modules in Node.js. The effort is split up into four phases, and at the time of writing, it is now in the last one, with hopes it will be mature enough to no longer require use of --experimental-modules
.
Let’s start with the first (and official) way provided by Node.js ito use ES modules in your Node environment.
First, as previously mentioned, make sure to have a version of Node higher than 10 on your machine. You can use the power of NVM to manage and upgrade your current version.
Then, we’re going to create a single example, just to give you a taste of how the modules work out. Create the following structure:
The first file, hi.mjs
, will host the code for a single function that’ll concat a string param and return a hello message:
// Code of hi.mjs export function sayHi(name) { return "Hi, " + name + "!" }
Note that we’re making use of the export
feature. The second file, runner.mjs
, will take care of importing our function and printing the message to the console:
// Code of runner.mjs import { sayHi } from './hi.mjs' console.log(sayHi('LogRocket'))
To run our code, just issue the following command:
node --experimental-modules runner.mjs
And this will be the output:
Note that Node will advise you about the ESM experimental nature of this feature.
When it comes to the use of Babel, webpack, or any other tool that would help us to use ES modules wherever we want, we have another solution for Node.js specifically that is much more succinct: it is the @std/esm package.
It basically consists of a module loader that dispenses Babel or other bundle-like tools. No dependencies are required; it allows you to use ES modules in Node.js v4+ super quickly. And, of course, it is totally compliant with the Node ESM specification.
Let’s now consider a different hello world
, this time on the web, with Express.js. We’ll make a CJS file to talk with an ESM one.
But first, in the root folder of our project, run the following commands:
npm init -y npm install --save @std/esm npm install --save express
Follow the steps, intuitively, to set up your package.json
structure. Once finished, create two new files:
Runner.js
will be the starting point of execution, but now as a single JavaScript filehi-web.mjs
will store the code for Express to access the hello functionLet’s start with the hi-web.mjs
source code:
import express from "express"; import { sayHi } from "./hi.mjs"; const app = express(); app.get("/", (req, res) => res.json({ "message": sayHi("LogRocket") })); app.listen(8080, () => console.log("Hello ESM with @std/esm !!"));
Note that, here, we’re making use of the previous mjs
file that hosts the sayHi()
function. That’s no big news once we’ve seen that we can perfectly import mjs
files from another one. Take a look at how we import this file to our start script:
// runner.js code require = require("@std/esm")(module); module.exports = require("./hi-web.mjs").default;
Once we’re not using the dynamic import, the default
must be used. The @std/esm rewrites require
and also adds functionality to the Node version module being used. It does some inline and on-demand transformations, processing and caching to the executions in real time.
Before you run the example, make sure to adapt your package.json
to understand which file will be the starting point:
... "scripts": { "start": "node runner.js" },
After running the npm start
command, that’ll be the output at the browser:
Happy Coding !
#javascript #nodejs #node #web_development