Why choose ES2015 modules, based on the state of the art of JavaScript modularization

Juil 26, 2016

In this article, we will see why it is important to migrate right now to the ES2015 modules for the whole community.

State of the art

To understand why it is important, we first need to describe a big picture of the actual context. The JavaScript ecosystem has so growth this last five years, that lot of developers did not notice actually there is five manners to create modules of JavaScript script or application !

  • The ancestral IIFE () : This approach is the oldest and the more simple way to modularize a JavaScript chunk. You can see below a basic example of how to wrap a JavaScript chunck in a IIFE :
    const myModule = (function (...deps){
       // JavaScript chunk
       return {hello : () => console.log(‘hello from myModule’)};
    })(dependencies);
    

    This code should not surprise you, it is a common pattern to scope variables or/and functions and the only one solution to be usable natively. Its problem, is that it does not manage dependency for us.

  • requirejsAMD (Asynchronous Module Dependency) : Strongly popularized by Require.js, this module format has been created to easily inject the modules between them (aka Injection Of Dependency). An another advantage is to be able to load JavaScript chunk dynamically.
    <pre>define(‘myModule’, [‘dep1’, ‘dep2’], function (dep1, dep2){
        // JavaScript chunk, with a potential deferred loading
        return {hello: () => console.log(‘hello from myModule’)};
    });
    // anywhere else
    require([‘myModule’], function (myModule) {
        myModule.hello() // display ‘hello form myModule’
    });
    

    Efficient, but a little bit verbose and it does not work on Node.js natively.

  • CommonJs : Default format of the Node.js plateform. The common use case looks like that :
    // file1.js
    modules.export = {
        hello : () => console.log(‘hello from myModule’)
    }
    
    // file2;
    const myModule = require('./file1.js');
    myModule.hello();
    

    nodejsThis format sounds pretty cool, because it is able to scope variables and define the dependencies between the modules. But it was designed for Node, so it does not work in a browser, it is not able to load asynchronously modules. Nevertheless, you can use it in a front app thanks to Browserify or others to use it in a browser.

  • UMD (Universal Module Dependency) : At this point we see that it does not exist solution to create a module compatible for browser and Node server in the same time. AMD for browsers, CommonJS for Node.
    UMD try to resolve that by combining AMD and CommonJS in one format able to be integratable in all targets, that looks like that :

    (function (global, factory) {
        typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
        typeof define === 'function' && define.amd ? define(factory) :
    (factory());
    }(this, function () {
        // JavaScript chunk
        return {
           hello : () => console.log(‘hello from myModule’)
        }
    });
    

    As you see, this format makes verification about its context to choose if it uses AMD or CommonJS then. It is a perfect fit for packaging multi-environment library like lodash or moment.js.

So, why add a new module type ?

First of all, no one of this solution is a standard, defined by the TC39 group I want to say. ECMA6 is now used by lot of developers, so why not use the standard solution of this version of JavaScript, the ES2015 modules ?

This standard offers a more flexible and powerful solution than the others ones. I invite you to see the full features of ECMA2015 modules here. Because in this article I want to focus on a special ability of ES2015 modules. Its capacity to more precisely defines what is exposed/imported in from/to each modules. Lets take a look to the piece of code below to demonstrate the concept :

// file1.js
const f1 = ()=> console.log(‘f1’);
const f2 = ()=> console.log(‘f2’);
const f3 = ()=> console.log(‘f3’);
export {f1, f2, f3};

// file2.js
import {f1, f2} from “./file1”;
f1(); // display ‘f1’
f2(); // display ‘f2’

We see above, that we can import easily just what we want with a static notation. In contrario of the others solutions, like CommonJS where we can call dynamically what we want. If we take the same example in CommonJS with a little difference.

// file1.js
const f1 = ()=> console.log(‘f1’);
const f2 = ()=> console.log(‘f2’);
const f3 = ()=> console.log(‘f3’);
modules.exports = {f1,f2,f3};

// file2.js
const file1 = require(‘./file1’);
file1.f1(); // display ‘f1’
file1.f2(); // display ‘f2’
file1[process.ENV.funcName]();

Obviously, this last line will never appears in a real code, but it demonstrates that some values are out of control, and so not predictable in a static analysis. Here, we can call potentially f3 because with CommonJS (and its the same with AMD or IIFE or UMD) we can’t restrains what it is imported.

 

tree-shackingAnd so ? Does it matter to know what is used with a static analysis of code ?
Of course it does !
Because with this control, the developper tools can detect some bugs and more interesting if you count on use WebPack 2 or Rollup.js, your transpiled assets will be more smaller with the Tree Shaking. The Tree Shaking is a functionality which removes the unused code by checking if an exposed resource (function, variable) is really used by another chunk (if it is imported).

 

 

We can see here an example of tree shaking :

Source file
//-------------
// main.js
import {cube} from './maths.js';
console.log( cube( 5 ) ); // 125

//-------------
// maths.js
export function square ( x ) {
   return x * x;
}

// This function gets included
export function cube ( x ) {
   return x * x * x;
}
Output file
function cube ( x ) {
   return x * x * x;
}

console.log( cube( 5 ) ); // 125

Mathieu Breton CTO chez JS-Republic

JS-REPUBLIC est une société de services spécialisée dans le développement JavaScript. Nous sommes centre de formation agréé. Retrouvez toutes nos formations techniques sur notre site partenaire dédié au Training

6 Comments. Leave new

Code formatting went a bit crazy. Can you fix it to improve code redability?

Répondre

Fixed. Thanks for the feedback 🙂

Répondre

`const {f1, f2} = require(‘./file1’)` will give the same as `import {f1, f2} from ‘./file1’` isn’t it?

Répondre

You’re basically saying, it’s new so it’s better. It isn’t.

[1] « was designed by a standards group, » with all do respect to the standards group who put in much effort to make the web better as well as engineers who contribute to them, this so called « standards group » is a bunch of incompetent lazy hippies.

First and most shameful at all, the group designed the « standard » with out consideration on how the heck this would ever actually work efficiently in the real world where there is such a thing as a network stack, projects have thoughsands of files, nor any consideration to existing technologies such as even simple gziping would work or could ever be applies. The clearly don’t understand the new h2 protocol’s advantages and limitations since they with out any shame just claimed it would be « faster » or « (wishful thinking) work out » which it’s clearly not gonna happen; and it’s been spelled out by anyone and everyone who actually works on production code or has done the legwork. The entire group should be dismissed on that alone.

Secondly, for all it’s fancy wording, and cherry picked « special » sauce that is honestly almost pointlessly complicated compared to a simple require, all the members of the group are clearly blind. The group designed a standard that doesn’t have the flexibility or extensibility of something that was already there. Can I enhance or overwrite like I can with the commonjs require? What about things like require.ensure functionality? There’s not even a good reason for this, other that they don’t care for anything but a application that consists of at most 5 modules.

[2] Concerning it’s special sauce,
> The Tree Shaking is a functionality which removes the unused code by checking if an exposed resource (function, variable) is really used by another chunk (if it is imported).

How is it everything else doesn’t need everyone to adopt an entire new standard that is blind to how the network works to actually do that?

The whole idea of tree shacking is not even a good idea in the context of the web, it’s much better to just only rely on at most cherry picking (ie. const now = require(‘timelib/now’)). Why? Because the most efficient way to send scripts is to send only whats needed (page1: bundle with certain functions, page2: bundle with diff between page1 and what it needs) so « clever » tree shacking just gets in the way (causing diffs between mangled code to happen) or potentially makes that impossible, because it really relies on sending everything at once.

Even a classic jquery infested site wouldn’t work very well with their approach, so I’m not even sure what super simplistic 5-file use case they were even thinking when they came up with this.

Much like « class »-es in js the entire thing is just a more inflexibile version of what was already there.

Répondre
La devOsphere en veille #1 – Webmaster Freelance Paris | Développement d’application mobile
7 octobre 2016 10 h 30 min

[…] choisir les modules d’ES2015 face aux autres […]

Répondre
ES Module | 黯羽轻扬
3 septembre 2017 9 h 03 min

[…] WHY CHOOSE ES2015 MODULES, BASED ON THE STATE OF THE ART OF JAVASCRIPT MODULARIZATION […]

Répondre

Répondre à sunpietro Annuler la réponse.

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *