WittCode💻

Node require vs import

By

Learn require vs import in Node including what a Node module is, run time vs load time, static vs dynamic module loading, .mjs vs .cjs file extensions, and the package.json type key.

Table of Contents 📖

Node Modules

Both require and import are used to work with Node modules. A Node module is simply a piece of reusable JavaScript code consisting of one or more files. There are 3 types of Node modules.

  • Core - built into Node like fs and http.
  • 3rd Party - available for installation with npm, go under node_modules
  • Local - created locally on the user's machine

Why do Node Modules Exist?

Node modules are used to break up code into separate functional parts, making the development/debugging process quicker and easier. This is because each module has its own private scope. If modules didn't exist, then the global namespace of a Node program would be shared, leading to a lot of overwritten code.

Module Formatting Systems

Node modules have different formatting systems, or module formatting systems. This means that there are different syntaxes used to work with Node modules. One of these systems is CommonJS which uses require and module.exports, another is ECMAScript which uses import and export.

CommonJS require

CommonJS is the default module formatting system for Node, meaning that by default Node uses require to work with modules. To demonstrate, create a file called myCommonJS.js, require the core Node fs library, and write to a file called wittcode.txt.

const fs = require('fs');
fs.writeFileSync('wittcode.txt', 'WittCode is cool');

We can then simply run the file with the command node myCommonJS.js.

node myCommonJS.js

Clearly, we can work with CommonJS right out of the box.

ECMAScript import

However, we cannot work with ECMAScript right out of the box. To demonstrate, create a file called myECMAScript.js and import the core Node fs module with import fs from 'fs'.

import fs from 'fs';
fs.writeFileSync('wittcode.txt', 'WittCode is awesome');

Now if we run the program with node myECMAScript.js we will get a Syntax Error.

node myECMAScript.js
SyntaxError: Cannot use import statement outside a module

To use the import keyword, we have to inform Node that this is a module. One way to do this is to initialize the current folder as an npm project and specify it as a module. To create an npm project, run npm init -y.

npm init -y

This creates a package.json file that specifies the project name, version, dependencies, and the capability to use ECMAScript. To use ECMAScript, set the type key to module.

{
    "name": "node-require-vs-import",
    "version": "1.0.0",
    "description": "",
    "type": "module",
    "keywords": [],
    "author": "",
    "license": "ISC",
}

The type key tells Node what module system the project is using. Currently, the values can be either module or commonjs. If we use module then Node will enable the ECMAScript module system which allows us to use import. If we use commonjs then Node will stick to the default commonjs module system, using the require keyword. After setting type to module, if we run the node program we won't get a syntax error.

node myECMAScript.js

.cjs vs .mjs

We can use the import statement without altering package.json by setting our Node file to a .mjs extension as opposed to the standard .js. Create a file called myECMAExtension.mjs and import the fs module.

import fs from 'fs';
fs.writeFileSync('wittcode.txt', 'WittCode is really cool!');

Now run the file with Node by using node myECMAExtension.mjs.

node myECMAExtension.mjs

A .mjs file is the recommended file extension for ECMAScript modules, besides that it is essentially the same as a .js file. There is also a .cjs extension that tells Node it uses the CommonJS module system, which uses require. To demonstrate, create a file called myCommonJSExtension.cjs and try using the import keyword.

import fs from 'fs';
fs.writeFileSync('wittcode.txt', 'WittCode is really really cool!');

Run the file with Node and observe the Syntax error displayed in the console.

node myCommonJSExtension.cjs
SyntaxError: Cannot use import statement outside a module

require is a Function While import is a Keyword

A difference between import and require is that require is a function while import is a keyword. In fact, because require is a function it can be called anywhere throughout the file, even in block statements.

const wittCode = 'cool';
if (wittCode === 'cool') {
    require('fs'); // Works fine
    console.log('require called inside if block');
}

When this code is ran, the fs module will only be loaded if the if check passes. A downside to require being a function is that it can sometimes be hard to determine what the dependencies of a module really are without running the code. On the other hand, the import statement cannot be used inside blocks, this is because modules loaded with import are ran before a module's code is imported.

const wittCode = 'cool';
if (wittCode === 'cool') {
    // Error - an import declaration can only be used at the top level of a module
    import fs from 'fs';
}

require is Dynamic and import is Static

Another difference between require and import is that require is dynamic while import is static. What this means is that import loads modules when the program is starting up, or during load time. As a result, any import statement is hoisted to the top of the file. Alternatively, require loads a module when the program is running, or during run time. This is why require can load modules conditionally. However, ECMAScript does provide a dynamic version of import, meaning it can be used in block statements. The dynamic version of import is a function that returns the module wrapped in a promise.

const wittCode = 'cool';
if (wittCode === 'cool') {
    import('fs').then(fs => {
        fs.writeFileSync('pizza.txt', 'cheese michael jackson')
    });
}

Here, if the if condition passes, we import the fs library dynamically with ECMAScript. The dynamic version of import is evaluated when needed, leading to greater code flexibility.