Javascript Modules

Elior Boukhobza @mallowigi
Front End Developer at Dynamic Yield

Table of contents

  • Introduction
  • Module pattern
  • AMD
  • CommonJS
  • ES Modules
  • Module bundlers
  • Conclusion

Why modules?

yes

function correctPNG(){
  // correct transparence png
  ...
}

function nw_links_simul(){
  // simule attribut target pour ouvrir nouvel onglet
  ...
}

function labels_for_IE(){
  // outline blue woohoo
}

function focus_fields(){
    // focus les champs
}

function ie5_homePage(obj) {
    // support for ie5
}

function mover_newsicon(n,soft_mode){
  ... 
}

© My first website, all rights reserved

Problems:

  • Everything is defined in a big single file, hard to understand
  • Everything is defined under the global namespace
  • Variable hoisting mess
  • Variable shadowing mess

BUT

...this is usually okay for simple portfolio websites, landing pages with little to no interaction

What about script files?

  • They are loaded asynchronously
  • They still share the same global namespace
  • Hard to maintain and debug
  • Unresolved dependencies
    • If file2 depends on file1 and file1 wasn't loaded yet, file2 will fail

Solutions

  • They are loaded asynchronously
    • Run functions when DOM is ready
  • They still share the same global namespace
    • Use namespacing
  • Hard to maintain and debug
    • Divide into smaller pieces of code
  • Unresolved dependencies
    • Specifically require the components that you need on

Example: IIFE

// Enclose the script in a local scope (AKA IIFE)
(function ($) {
    // Wait until DOM is loaded
    $(document).ready(function () { 
        // 'app' namespace
        window.app = window.app || {};

        // Declares a new module
        window.app.myModule = {};
    });

    
}(window.jQuery)); // Specifically require the dependency jQuery

This is great!

BUT

There is no privacy!

Revealing module pattern

Revealing module pattern

  • Encloses the module inside a closure (IIFE) but return a value: the module itself.
  • Declares private variables and functions inside the closure and exports a "public API"  to be used by others

Revealing module pattern

var basketModule = (function () {
    // Private functions and variables
    var basket = [1, 2, 3]; 

    function getSum() {
      return basket.reduce(function(acc, el) { return acc + el }, 0);
    }

    // This is the module that is 'revealed'
    return { 
        addItem: function(values) {
            basket.push(values);
        },
        getItemCount: function() {
            return basket.length;
        },
        getTotal: function(){
            var newBasket = basket.map(function(el) { return Number(el) });
            return getSum(newBasket); //private function
        }
    }
    
}());

console.log(basketModule.basket); //undefined
console.log(basketModule.getTotal()); // 6
console.log(getSum()); // ReferenceError

Revealing module pattern

// Usage
var app = (function (root) {
    // Import the dependencies from inside the root namespace
    var basketModule = root.basketModule,
        cartModule = root.cartModule;

    return {
        run: function () { ... }
    }
    
}(this)); // this can be window (browser), global (nodeJS/browserify)...

// ensure that all deps are loaded before running
setTimeout(function() {
    app.run();
}, 0);

Conclusion

  • Advantages:
    • Out of the box, no libraries required
    • Cross browser compatible
  • Disadvantages:
    • Hard to maintain for big projects
    • Namespacing with other libraries

Better: AMD

  • AMD: Asynchronous Module Definition
  • Concepts:
    • Do not load all dependencies at start, lazy loading
    • Declare before each module which deps they need
    • Parallel loading and concurrency

Libraries

RequireJS

AngularJS modules

RequireJS

// Create a "requireable module" that requires two dependencies
define('myModule', ['foo', 'bar'], function ( foo, bar ) {
  // The last parameter of the define must be the function that creates the module
  
  // The function will be "injected" the dependencies as parameters 
  // in the order they are required

  var myModule = {
    doStuff: function(){
      console.log(foo.doStuff());
    }
  }  
  
  return myModule;
});

// Require is for using the deps without creating a new module
require(['foo', 'bar'], function (foo, bar) {
  console.log(foo.doStuff());

// Finally, require can also be used within a require/define block
// For example, for dynamic dependencies
  var dep = require(bar.getDep());
});

Conclusion

  • Advantages:
    • Only one script to declare (the main.js)
    • Lazy module loading means better performance
    • Real encapsulation and namespacing
    • Dependency injection
    • Server side optimization
  • Disavantadges
    • Require a lot of overhead
    • Code can be less readable
    • Need extra work (shimming) to make it compatible with third-party libraries
    • Hard to integrate with tests
// Export jQuery for requireJS
define.amd = {
    //supports multiple jQuery versions
    jQuery: true
};

Alt: CommonJS

  • Widespread use in Node.JS
  • Syntax similar to other languages with imports at the top
  • File loading instead of Module loading
  • Cyclic dependency detection
  • Simple syntax: require

NODE.JS

// lib is a dependency installed via npm
var lib = require("lib");
 
// behaviour for our module
function foo(){
    lib.log( "hello world!" );
}

function bar() {
    lib.log("I am private!");
}
 
// Only export necessary members
exports.foo = foo;
// In another file
// Require a file from the current directory
var foo = require('./foo');

// Other syntax: module.exports
module.exports = {
    init: function() {
        foo();
    }
};

Conclusion

  • Advantages:
    • Syntax easier to use
    • File loading
    • Cyclic dependency checking
    • Synchronous loading
    • Do not need shimming
  • Disadvantages:
    • Do not work inside the browser
    • No Dependency Injection
    • Not parallel/concurrent

UMD

  • UMD: Universal Module Loading
  • Tries to accomodate both AMD and CommonJS modules
(function (root, Library) {
  // The square bracket notation is used to avoid property munging by the Closure Compiler.
  if (typeof define == "function" && typeof define["amd"] =="object" && define["amd"]) {
    // Export for asynchronous module loaders (e.g., RequireJS, `curl.js`).
    define(["exports"], Library);
  }
  else {
    // Export for CommonJS environments, web browsers, and JavaScript engines.
    Library = Library(typeof exports == "object" && exports|| (root["Library"] = {
      "noConflict": (function (original) {
        function noConflict() {
          root["Library"] = original;
          // `noConflict` can't be invoked more than once.
          delete Library.noConflict;
          return Library;
        }
        return noConflict;
      })(root["Library"])
    })); 
  }
})(this, function (exports) {
  // module code here
  return exports;
});

(not the University of Maryland)

UMD

Problem: Tries to define a standard by compromising between the two solutions, which means more overhead and more error-prone

Solution: Use ES Modules, the de facto standard

ES Modules

  • The native module system 
  • Resembles a lot like CommonJS
  • Lazy loading (do not use the module until requested)
  • Do not need code bundling (works better with HTTP/2)
  • Import bindings, not references

However

  • Needs a module loader (System.JS is the candidate)
  • No cyclic dependency management

(I have no joke for that)

ES Modules

// Export public members
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}

// import syntax: square is not loaded until use
import { square, diag } from 'lib';

console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

Export/Import

ES Modules

// Import default bindings
import React from 'react';
// Import all bindings
import * from 'material-ui';

// Like module.exports: default export
export default class ChannelList extends React.Component {
  constructor (props) {
    super(props);
  }
  // ...
}

Default export - Wildcard import

Conclusion

  • Advantages:
    • Accepted standard
    • Performance (lazy-loading, no parsing)
    • Works in every environment
  • Disadvantages
    • Still no native implementation
    • Require a module loader, such as SystemJS or Webpack

Module BundlerS

Motivation: Allow using modules in the browser

Module BundlerS

Module BundlerS

// Example using angular1
import angular from 'angular';
import dep1 from './services/dep1';
import dep2 from './services/dep2';
import MainController from './controllers/MainController';

angular.module('myApp')
    .service(['$http', dep1])
    .service(['dep1', dep2])
    .controller(['dep1', 'dep2', MainController]);

Example: Angular 1

// Example using commonJS
var angular = require('angular');
var _ = require('lodash');

Bonus: Webpack

  • Webpack extends the concept of modules to all files, not only JS modules
  • Allow on-the-fly transpilation
  • Module optimization, uglification, inline-loading, etc...

Bonus: Webpack

// Global imports
import {App, Platform} from 'ionic-framework/ionic';
import {IonicApp} from "ionic-framework/ionic";

// Component import
import {HomePage} from '../homepage/homepage';

// Here I'm importing html and scss
// The transformation is done inside webpack.config.js
const appView = require('./tabsPage.html');
const appStyles = require('./tabsPage.scss');

@Page({
    template: appView,
    styles: [appStyles]
})
export class TabsPage {
    public tab1Root;    
    public app:IonicApp;

    constructor(app:IonicApp) {
        // this tells the tabs component which Pages
        // should be each tab's root Page
        this.tab1Root = HomePage;
        this.app = app;
    }

}

Example: Angular 2

Links

  • Javascript design patterns:
    http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/

  • ESNext Modules:
    http://exploringjs.com/es6/ch_modules.html

  • Require.JS:
    http://javascriptplayground.com/blog/2012/07/requirejs-amd-tutorial-introduction/

  • Browserify:
    https://scotch.io/tutorials/getting-started-with-browserify

  • Webpack and Angular2:
    https://angularclass.github.io/angular2-webpack-starter/

Javascript modules

By Elior Boukhobza

Javascript modules

  • 1,187