Perpetuum module

IIFE, CommonJS, AMD, ES6

by Kiril Knysh

Why modules?

  • Web sites become Web apps  => Complex architecture
  • Hard app assembly
  • Code / performance optimizations

Why modules?

JS modules alternatives

  • IIFE (Immediately Invoking Function Expressions)
  • CommonJS (mainly server JS code)
  • AMD (Asynchronous Module Definition) (mainly browsers)
  • ECMAScript 6 modules

IIFE

Single file module

utils.js
App.UTILS = (function () {

  var utils = {};

  var STATE = {
    NAME_PREFIX: 'Sir ',
    SAL_PREFIX: '€'
  };
  

  utils.getName = function (actor) {
    return STATE.NAME_PREFIX + $.trim(actor.name);
  }
  utils.getSalary = function (actor) {
    return STATE.SAL_PREFIX + actor.salary;
  }

  
  return utils;

})();

IIFE

Multi files module

utils/name.js
(function (utils, $) {

  var STATE = {
    PREFIX: 'Sir '
  };

  utils.getName = function (actor) {
    return STATE.PREFIX + $.trim(actor.name);
  }

})(App.UTILS = App.UTILS || {}, window.jQuery);
utils/salary.js
(function (utils, $) {

  var STATE = {
    PREFIX: '€'
  };

  utils.getSalary = function (actor) {
    return STATE.PREFIX + actor.salary;
  }

})(App.UTILS = App.UTILS || {}, window.jQuery);

CommonJS

utils.js
var $ = require('jQuery');

var STATE = {
  NAME_PREFIX: 'Sir ',
  SAL_PREFIX: '€'
};
  

function getName(actor) {
  return STATE.NAME_PREFIX + $.trim(actor.name);
}
function getSalary(actor) {
  return STATE.SAL_PREFIX + actor.salary;
}

  
module.exports.getName = getName;
module.exports.getSalary = getSalary;
app.js
var UTILS = require('utils');

  
UTILS.getName(actor);

CommonJS Loaders

AMD

utils.js
define(['jquery'] , function ($) {

  var STATE = {
    NAME_PREFIX: 'Sir ',
    SAL_PREFIX: '€'
  };
  

  return {
    getName: function (actor) {
      return STATE.NAME_PREFIX + $.trim(actor.name);
    },
    getSalary: function (actor) {
      return STATE.SAL_PREFIX + actor.salary;
    }
  };

});
app.js
require(['utils'], function (UTILS) {

  UTILS.getName(actor);

});

AMD Loaders

AMD Loaders (RequireJS)

demo/requirejs
index.html
<body>
    ...
    <script data-main="main" src="bower_components/requirejs/require.js"></script>
</body>
main.js
requirejs.config({
  paths: {
    jquery: 'bower_components/jquery/dist/jquery'
  }
});

requirejs(['scripts/app'], function(app) {
  app.run();
});

AMD Loaders (r.js optimizer)

demo/requirejs
build.js
({
  baseUrl: ".",
  name: "main",
  out: "./build/main.js",
  paths: {
    jquery: "bower_components/jquery/dist/jquery.min"
  }
})

ECMAScript 6 modules

utils.js
import $ from 'jquery';
  

const STATE = {
  NAME_PREFIX: 'Sir ',
  SAL_PREFIX: '€'
};
  

export function getName(actor) {
  return STATE.NAME_PREFIX + $.trim(actor.name);
}
export function getSalary(actor) {
  return STATE.SAL_PREFIX + actor.salary;
}
app.js
import { getName, getSalary } from 'utils';

  
getName(actor);

ECMAScript 6 modules

Browsers support

Thank you for attention!

ES6 modules loaders

ES6 modules loaders (webpack)

demo/es6-webpack
webpack.config.js
var path = require('path');

module.exports = {
  entry: './scripts/app.js',
  output: {
    path: __dirname,
    filename: 'app.bundle.js'
  },
  module: {
    loaders: [

      { test: path.join(__dirname, 'scripts'), loader: 'babel-loader' }

    ]
  }
};

ES6 modules

  • each module is a logically dedicated piece of code
  • by default all declarations are local (private) but some could be marked as exports
  • module can import other modules (or their parts)
  • modules are singletones

ES6 modules

exports

  • named
  • default (one per module)

ES6 modules

named exports

utils.js
export const SEC_IN_MINUTE = 60;

export function getName(actor) {
  return actor.name || '';
}

export class Actor(name, salary) {
  this.name = name;
  this.salary = salary;
}
app.js
import { SEC_IN_MINUTE, getName } from 'utils';

import * as UTILS from 'utils';
const actor = new UTILS.Actor('Will Smith', 2000000);

ES6 modules

default export

super-sort.js
export default function (array, predicate) {
  //super sort algorithm here
} //no semicolon
Actor.js
export default class (name, salary) {
  this.name = name;
  this.salary = salary;
} //no semicolon
app.js
import superSort from 'super-sort';
import SuperActor from 'Actor';

ES6 modules

named + default export

Actor.js
export default class (name, salary) {
  this.name = name;
  this.salary = salary;
}

export function getName(actor) {
  return actor.name || '';
}
app.js
import Actor, { getName as getActorName } from 'Actor';

ES6 modules

inline vs clause export

inline.js
export const SEC_IN_MINUTE = 60;
export function getName(actor) {
  return actor.name || '';
}
clause.js
const SEC_IN_MINUTE = 60;
function getName(actor) {
  return actor.name || '';
}
 
export { SEC_IN_MINUTE, getName as getActorName };

ES6 modules

imports

  • import name from "module-name";
  • import * as name from "module-name";
  • import { member } from "module-name";
  • import { member1 , member2 as alias2 , [...] } from "module-name";
  • import defaultMember, { member [ , [...] ] } from "module-name";
  • import defaultMember, * as alias from "module-name";
  • import defaultMember from "module-name";
  • import "module-name";

ES6 modules

named imports

utils.js
export function getSalary(actor) {
  return actor.salary || -1;
}
export default function(actor) {
  return actor.name || '';
}
app.js
import getName from 'utils';
import * as actorUtils from 'utils';
import { getSalary as getActorSalary } from 'utils';

ES6 modules

default imports

utils.js
export function getSalary(actor) {
  return actor.salary || -1;
}
export default function(actor) {
  return actor.name || '';
}
app.js
import getName from 'utils';
import getName, * as actorUtils from 'utils';
import getName, { getSalary as getActorSalary } from 'utils';

ES6 modules

empty import

utils.js
export function getSalary(actor) {
  return actor.salary || -1;
}
export default function(actor) {
  return actor.name || '';
}
app.js
import 'utils';

ES6 modules

Imports are hoisted

Actor.js
export default class (name, salary) {
  this.name = name;
  this.salary = salary;
}
app.js
const mFreeman = new Actor('Morgan Freeman', 3000000);

import Actor from 'Actor';

ES6 modules

Imports are read-only views on exports

visit-utils.js
export let visits = 0;
export function visitorDetected() {
  visits++;
}
app.js
import { visits, visitorDetected } from 'visit-utils';
console.log(visits); //0

visitorDetected();

console.log(visits); //1

ES6 modules

cyclic dependencies

demo/es6-webpack

bad cyclic dependencies

demo/es6-webpack-unresolved

ES6 modules

module loader API

JavaScript Loader Standard

ES6 modules

module loader API


System.import('some_module')
  .then(some_module => {
    // Use some_module
  })
  .catch(error => {
    ···
  });

ES6 modules

module loader API

  • Convert source code to module
    
    System.module(source, options?)
    
  • Register module
    
    System.set(name, module)
    
  • Evaluate module code and register it
    
    System.define(name, source, options?)
    

ES6 modules

module loader demo

demo/es6-module-loader

ES6 modules

native

demo/es6-native

Questions?