JS modules were not a thing when Node was first built, and so it uses a module format called CommonJS. In CommonJS, you import modules like require('my-module')
.
Node has a built-in package manager called NPM. You can install modules via NPM, and those modules go into the node_modules
folder.
Node’s implementation of CommonJS is designed so that you don’t need to write require('./path/to/node_modules/some/path/to/my/module/index.js')
. Instead, the module is always a folder, and in that folder there will be an index.js
file (it can be any other name, but that is the default).
So then, require('my-module')
is telling Node to:
- look in
node_modules
…
- for a folder called
my-module
…
- then load the
index.js
file from there.
This means that there has to be some way to differentiate your own files from those in node_modules
: this is done by explicitly stating the path to the file: require('./my-file')
, or require('../some/file/in/another/folder')
.
Actual ES modules do not know about any of this (and say nothing about how to find and load the modules). They generally require a fully-qualified path, so import foo from "my-module"
will either break, or the parser will assume you want a function called foo that is exported from a file called my-module.js
that’s lives alongside the file you’re import
ing from, in the same folder.
What this means is that to import modules in a project that uses NPM, you’d need to write something like (for example), import React from "../../node_modules/react/index"
every time you needed to bring React into scope.
This is not anyone’s idea of a fun developer experience, so module bundlers (for example Webpack) will parse your code when they run, checking import
statements to allow you to use the Node/NPM/CommonJS style where:
- If the path is unqualified, like
import React from "react"
, it’s probably an NPM installed package, look in node_modules
like Node does and load the module’s main file.
- If the path is qualified, like
import MyComponent from "../components/MyComponent
, it’s a specific file, load that file.
This is a specific convenience allowed by module bundlers (or plugins for module bundlers) to handle the way Node deals with modules.
Very minor aside, but the way Node deals with modules may possibly have been a serious design error. Node’s original designer is currently building an alternative to Node (Deno), and that doesn’t use a package manager & sticks to the ES specifications for modules, using fully-qualified URL paths, e.g. import { assertEqual } from "https://deno.land/std/testing/asserts.ts";
(which actually works pretty well in practise).