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.
- AMD (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();
This 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.
And 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
[separator type=”” size=”” icon=”star”] [actionbox color=”default” title=”” description=”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” btn_label=”Nos formations” btn_link=”http://training.ux-republic.com” btn_color=”primary” btn_size=”big” btn_icon=”star” btn_external=”1″]