Skip to content

Instantly share code, notes, and snippets.

@dphilipson
Last active May 20, 2022 19:39
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dphilipson/77872c24350a2e1b591917ae746d3438 to your computer and use it in GitHub Desktop.
Save dphilipson/77872c24350a2e1b591917ae746d3438 to your computer and use it in GitHub Desktop.
JavaScript Import Syntax - Why??

JavaScript Import Syntax - Why??

In the early days of the web, JavaScript had no notion of imports or modules. You would load several .js files onto your page and each of them would run in sequence. If you wanted to write a library that other scripts could use, you would put stuff onto a global variable and then other scripts would read it there. For example, you might have something like this in your HTML:

<script src="library.js"></script>
<script src="main.js"></script>

where the files might have content like

// library.js
const MyLibrary = {
    printFoo: () => console.log("foo"),
};
// main.js
MyLibrary.printFoo();

Note that the second file just assumed that a variable named MyLibrary existed without explicitly declaring that dependency anywhere, but it will work just fine as long as the first script ran first.

This works alright, but it has some problems:

  • When you have a lot of scripts that depend on each other, you have to manually keep track of what order to run them in.
  • You have to hope that you don't run into naming collisions by two libraries choosing the same global variable.
  • If two of your libraries depend on different versions of the same library, you're out of luck.

Then came NodeJS, a means of running JavaScript outside the browser. The NodeJS people decided to create a system for declaring modules which could import from each other. It looks like this:

// library.js
module.exports = {
  printFoo: () => console.log("foo"),
};
// main.js
const MyLibrary = require("./library");
MyLibrary.printFoo();

This syntax of require/module.exports is sometimes called CommonJS. Technically, CommonJS is a little different than what NodeJS implemented, but it's close enough that you'll often hear people just call this CommonJS.

This is a lot better. Files declare their dependencies via their require statements and declare their public APIs by setting module.exports.

This worked so well for NodeJS that people who write code for webpages wanted to be able to do this too. But browsers don't know anything about require or module.exports, so webpage authors needed a tool to take module files and convert them into something that browsers understand. The most common tool of this sort is called Webpack, and it takes multiple module files and combines them into a single file which can be run in the browser. For example, if you gave the two files from the last example to Webpack, then it would output a single file with contents roughly equivalent to the following:

const MyLibrary = {
  printFoo: () => console.log("foo"),
};
MyLibrary.printFoo();

After a while, committees for the future of JavaScript decided they wanted to introduce native modules to the web, so you could have a module system built into the browser instead of requiring everyone to learn a third-party tool like Webpack. Just to be different, they proposed the following syntax:

// library.js
export function printFoo() {
  console.log("foo");
}
// main.js
import { printFoo } from "./library";
printFoo();

These are called ES6 modules (ES6 stands for ECMAScript 6, where ECMAScript is a fancy name for what everyone thinks of as JavaScript).

According to spec, ES6 modules have subtly different behavior relating to how they are loaded and parsed compared to CommonJS. But in most practical situations, CommonJS and ES6 are equivalent. They're so close that the Webpack developers decided they'd let people start using the proposed ES6 module syntax early, and implemented it by treating it as though it had exactly the same behavior as CommonJS. On top of that, the ES6 syntax allows for certain types of static analysis that allows Webpack to make some optimizations like treeshaking when using ES6 modules. In summary, when using Webpack to build for the browser, you can use import/export, and doing so provides advantages.

However, NodeJS decided they wanted to adhere tightly to the ES6 spec, and to do this strictly is an awkward technical problem for them. You can read about it in their blog, but long story short is that raw NodeJS does not support import/export, and even when they do support it in future versions it will have tricky caveats. You have to keep using require/module.exports when writing raw Node code.

However, many Node developers aren't actually writing raw Node code. Instead, they write code that they run through a transpiler first, such as when using TypeScript. Common transpilers can convert code using import/export to code using require/module.exports, so if you use TypeScript or another transpiler like Babel, then you can probably use import/export in Node if you want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment