How javascript modules work – from past to present

JavaScript is an interesting language, for the past 5 years or so, I’ve witnessed its accelerated evolution in both the front-end and back-end.  The topic of module loading had been through numerous stages of evolution.  There were countless confusions among beginners and pros alike about this topic, so in this tutorial I will attempt to summarize how we’ve been loading external modules from past to present.


JavaScript was created in 10 days by Brendan Eich, the original intent of the language was to do simple scripting and DOM manipulation on the browser.  Due to it being THE language of the web browser and the rapid growth of the web, the language evolved over time to accomodate modern technologies.  Module loading became a necesscity to write large scalable, maintainable web applications, and out of the box, JavaScript wasn’t cutting it.  The Javascript community came up with numerous solutions to help developers all over the world with this problem and that directly influenced the growth in popularity of JavaScript today.


Most people are familiar with the classic way of loading external javascripts:

<!DOCTYPE html>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>My Website</title>
  <link rel="stylesheet" type="text/css" href="main.css" media="all">


<script src="./js/jquery-3.1.0.min.js"></script>
<script src="./js/slick.min.js"></script>
<script src="./js/main.js"></script>

You place individual JavaScript files before the closing <body> tag and IN ORDER, so for this example, we want jquery-3.1.0.min.js to load first, follow by slick.min.js and finally main.js

Pros: Easy to reason about, scripts are separated by what they do, works across all browsers.

Cons: Clutters up html file, no concept of selective import so all code are imported whether you need it or not, may block the page if the script is not loaded properly.

Though you can fix blockage by defering or loading the script asynchronously, for example the classic way of loading an async script is to create the tag dynamically:

(function(d, t) {
    var g = d.createElement(t),
        s = d.getElementsByTagName(t)[0];
    g.src = './myScript.js';
    s.parentNode.insertBefore(g, s);
}(document, 'script'));

A more modern way of doing it is to do it via the async keyword which prevents blocking of DOM render:

<script async src="./myScript.js"></script>

so the above example can be re-written like this:

<!DOCTYPE html>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>My Website</title>
  <link rel="stylesheet" type="text/css" href="main.css" media="all">


<script async src="./js/jquery-3.1.0.min.js"></script>
<script async src="./js/slick.min.js"></script>
<script async src="./js/main.js"></script>

You can read more about Async here: Thinking Async from CSSTricks


RequireJS was one of the most popular ways to bundle your scripts and manage dependencies starting around 2012.  It’s main attraction was its power to make it easier to structure your dependencies by setting a single entry point to all your scripts:

<!DOCTYPE html>
        <title>My Sample Project</title>
        <!-- data-main attribute tells require.js to load
             scripts/main.js after require.js loads. -->
        <script data-main="scripts/main" src="scripts/require.js"></script>
        <h1>My Website</h1>

Then in your main.js you can simply create dependency tree by:

], function(_, $, Tracker, Analytics, Dog) { // Now I have access to these things! });

What’s good about Require JS is that it will automatically take care of dependency order for you, for example, if inside of component/tracker.js does a define() that loads in something.js then main.js will not load in the rest of components on its list (analytics.js and dog.js) until something.js is loaded in first for tracker.js

RequireJS implements Asynchronous Module Definition pattern and it does get quite complicated when defining things such as Globals inside the main entry point, I suggest you read their documentation to be more familiar with it.


CommonJS is a module pattern proposed to help bring a more familiar syntax to dependency management.  The most popular software package that implements CommonJS Module style code is NodeJS.

The following should be very familiar to you if you’ve worked with Node before:


module.exports.add = function(a, b) {
    return a + b;


var add = require('./math').add;

module.exports = function(name, age) {
  var ageIn5Years = add(age, 5);
  return 'Hello my name is: ' + name + ' and I am ' + ageIn5Years + ' old';


var speak = require('speak');
speak('zhi', 25);

Here’s a run down of what this does:

  1. In math.js we define a function to be exported as add, the function simply adds 2 values from parameter together.
  2. In speak.js we import that function from math.js and use it within the function by invoking add() and we export the function as the default function we export out of speak.js
  3. In main.js we simply bring in speak.js by requiring it and then invoke it with 2 parameters.  CommonJS pattern will make sure to automatically bring in the needed modules when it looks up the dependency tree.

Pretty darn amazing!  Compare to RequireJS this feels much more natural and easier to comprenhend.  In my opinion it may even be the reason why Node is so popular.  A whole ecosystem of NPM was created via modules built this way.  If you would like to set up a project with configs all set up for you to use CommonJS on the client side, check out my tutorial on Getting started with WebPack Dev Server.

ES6/ES2015 Modules

ES6 modules are available through transpilers like Babel.  It’s a more simpler, easier to understand way of importing/exporting your code. There’s a few caveats you need to understand before you can fully utilize the power of ES6 modules.

Default Export


export default function foo() {};

This will export the function foo, so you can use it like this


import foo from './foo';
import whateverIWant from './foo';
import nameDoesntMatter from './foo';

As you can see, you can name it whatever you want because the compiler will know to pick the default function from foo.js

Named Export

Named export comes in handy when you have multiple modules you want to export from a single module.


export function getBreakfast() {};
export function getLunch() {};
export const baz = 'baz';
export const letters = ['a', 'b', 'c'];
export default function getDinner() {};


import {getBreakfast} from './foo';
import {letters} from './foo';

Here we’re using destructors from ES6 to make importing easier, if you’re confused, read our tutorial on ES6 Destructors

You can also chain named imports like so:

import {getBreakfast, getLunch} from './foo';
import dinnerTime, {baz, letters} from './foo';

Note: We imported dinnerTime from the default export function of foo.js, you can combine imports that way, because if you don’t use destructors, it will attempt to import the default exported function.

Aliased Import

Sometimes you want to import a module with a different name than what was exported, in that case you can use the as keyword:


import {getBreakfast as MyOwnGetBreakfast} from './foo';

nice, clean and powerful.  ES6 modules are used all over modern frontend/backend javascript projects, it is the unofficial standard way of loading javascript.  I highly suggest you read more about it from this excellent article from 2ality.

I hope you enjoy this article and learned something new about how to load Javascript modules from the past to present.  Will we see something new emerge any time soon? Not likely, but if it does, be sure to count on us to give you a run down.

If you enjoyed this tutorial, make sure to subscribe to our Youtube Channel and follow us on Twitter @pentacodevids for latest updates!

Related Posts

Webdev News

    More News