Loading Modules Everywhere!

By Gion Kunz

Why ECMAScript modules are the way to go

 Gion Kunz

 Front End Developer

@GionKunz

gion.kunz@oddeven.ch

https://oddeven.ch

Modules, What?!

Some code Structuring history

Some code Structuring history

10,000 B.C.

The One Large File Approach

5,000 B.C.

Loading all Files in HTML

<!doctype html>
<title>Prepare to wait...</title>
<script src="script1.js"></script>
<script src="script2.js"></script>
<script src="script3.js"></script>
<script src="script4.js"></script>
<script src="script5.js"></script>
<script src="script6.js"></script>
<script src="script7.js"></script>
<script src="script8.js"></script>
<script src="script9.js"></script>
<script src="script10.js"></script>
<script src="script11.js"></script>
<script src="script12.js"></script>
<script src="script13.js"></script>
<script src="script14.js"></script>
<script src="script15.js"></script>
<script src="script16.js"></script>

1,200 A.D.

Namespaces and Concatenation

(function(g){
  // main.js
  g.Package = g.Package || {};

  // module-a.js
  g.Package.ModuleA = {
    sayHi: function() {
      console.log(g.Package.ModuleB.message);
    }
  };

  // module-b.js
  g.Package.ModuleB = {
    message: 'Hi there!'
  };

  // boot.js
  g.Package.ModuleA.sayHi();
  
}(window));

2,000 A.D.

Revealing Module Pattern

(function(g) {
  // module-a.js
  g.moduleA = (function() {
    // Private to module via closure
    var message = 'Hi There!';
    function sayHi() {
      console.log(message);
    }
  
    // Public interface
    return {
      sayHi: sayHi
    };
  }());

  // init.js
  g.moduleA.sayHi();
}(window))

By Addy Osmani

Present

ES MODULES

ES MODULES

Modules Help Organizing Your Code

import {doStuff} from 'B';

doStuff();

Module A

export function doStuff() {
  // All sorts of stuff
}

Module B

Classes for Structure Anti-pattern

class Util {
  static doStuff() {
    // You bet!
  }

  static doMoreStuff() {
    // More stuff...
  }
}

Util.doStuff();
Util.doMoreStuff();
export function doStuff() {
  // You bet!
}

export function doMoreStuff() {
  // More stuff...
}

Module Util

Singleton Anti-Pattern

export class Counter {
  constructor() {
    return Counter._i || 
      (Counter._i = Object.create(Counter.prototype), 
       Counter._i._count = 0, 
       Counter._i);
  }
  
  increment() {
    this._count++;
  }

  count() {
    return this._count;
  }
}

new Counter().increment();
new Counter().increment();
console.log(new Counter().count());
let _count = 0;

export function increment() {
  _count++;
}

export function count() {
  return _count;
}

Using Modules

Module FOrmats

AMD

Asynchronous Module Definition (Require.js)

define('A', ['B'], 
  function(B) {
    B.doSomething();
  }
);

Module A

define('B', [], 
  function() {
    return {
      doSomething() {
        // Something...
      }
    };
  }
);

Module B

Pro

  • Inversion of Control

  • Modules execute when dependencies are registered

  • Concatenate using named definition

Contra

  • Quite verbose format

  • Can't handle cyclic dependencies

  • No syntax support

  • No partial / named exports

CJS

CommonJS Modules (Node.js)

const B = require('B');

B.doSomething();

Module A

function doSomething() {
  // Something...
}

module.exports = {
  doSomething
};

Module B

Pro

  • Simple format

  • Relatively small runtime

  • Supports default export using module.exports

  • Great popularity (Node.js)

Contra

  • No Inversion of Control

  • Manually handle cyclic dependencies

  • Synchronous import only

  • No syntax support

ESM

ECMAScript Module

import {doSomething} from 'B';

doSomething();

Module A

export function doSomething() {
  // Something...
}

Module B

Pro

  • There's a spec!!!

  • Syntax support

  • Default exports

  • Named exports

  • Bindings

  • Static Analysis

  • Dynamic "import( )"

  • Tree-shaking

Contra

  • Needs transpiler

Tree- Shaking?!

Tree Shaking with Named Exports

import {a} from 'const';

console.log(a);
export const a = 42;
export const b = 23;
export const c = 1.618;

Module Main

Module Const

var a = 42;
console.log(a);

From Modules to the browser

Bundler

Loader

Webpack

Browserify

rollup.js

Require.js

SystemJS

ES Module Loader

The Battle of the Pokémon
Bundlers

Require.js

Webpack

Browserify

SystemJS

ES Module Loader

Specification for loading modules in the browser.

<!doctype html>
<script type="module" 
        src="module-a.js">
</script>

ES Module Loader Polyfill

By Guy Bedford

<!DOCTYPE html>
<html>
  <head>
    <script src="./babel-browser-build.js"></script>
    <script src="./browser-es-module-loader.js"></script>
  </head>

  <body>
    <script type="module" src="./module-a.js"></script>
  </body>
</html>
import {message} from 'module-b.js';

document.body.textContent = message;

module-a.js

export const message = 'Modules are awesome! :-)';

module-b.js

Browser Support

SystemJS

SystemJS
=
ES Module Loader
+
More

Universal dynamic module loader - loads ES6 modules, AMD, CommonJS and global scripts in the browser and NodeJS.

Loading CommonJS ES5 Modules

<!doctype html>
<html>
  <head>
    <title>SystemJS CJS Modules</title>
    <script src="//unpkg.com/systemjs"></script>
  </head>
  <body>
    <script>
      System
        .import('module-a.js')
        .catch(console.error.bind(window));
    </script>
  </body>
</html>
var message = require('module-b.js').message;

document.body.innerText = message;
exports.message = 'Modules are awesome!';

module-a.js

module-b.js

Loading Modules directly from NPMCDN!

<!doctype html>
<html>
  <head>
    <title>SystemJS path to NPMCDN</title>
    <script src="//unpkg.com/systemjs"></script>
    <script src="//unpkg.com/babel-core/lib/api/browser.js"></script>
  </head>
  <body>
    <script>
      System.config({
        transpiler: 'babel',
        paths: {
          'npm:*': '//unpkg.com/*'
        }
      });
    
      System
        .import('main.js')
        .catch(console.error.bind(window));
    </script>
  </body>
</html>
import {merge} from 'npm:lodash';
import formatJson from 'npm:json-markup';

const obj1 = {
  name: 'Test',
  props: {
    prop1: 1,
    prop2: 2
  }
};

const obj2 = {
  name: 'Test2',
  props: {
    prop2: 22,
    prop3: 3
  }
};

const merged = merge({}, obj1, obj2);
document.body.innerHTML = formatJson(merged);

main.js

Systemjs Loader Plugins

ES Module Loader Hooks

Resolve

Fetch

Translate

Instantiate

Writing a Simple Text Loading Plugin

exports.fetch = function(load) {
  return fetch(load.address)
    .then(function(response) {
      return response.text();
    });
};

exports.translate = function(load) {
  return 'module.exports = \'' + 
    load.source
      .replace(/\n/g, '\\n')
      .replace(/\r/g, '\\r')
      .replace(/'/g, '\\\'')
    + '\'';
};
import content from 'content.html!text.js';
document.body.innerHTML = content;

main.js

text.js

Universal Graphics Loader

export function fetch(data) {
  return loadBinaryData(data.address)
    .then((binaryData: IBinaryData) => {
      if (this.builder) {
        return `data:${binaryData.contentType};base64,${
          btoa(new Uint8Array(binaryData.data)
                .reduce((data, byte) => 
                  data + String.fromCharCode(byte), '')
          )
        }`;
      } else {
        return '';
      }
    });
}
export function translate(data) {
  if (this.builder) {
    return `
      var image = document.createElement('img');
      image.src = '${data.source}';
      module.exports = {
        address: '${data.address}',
        loadedImage: new Promise(function(resolve) {
          image.onload = resolve(image);
        })
      };
    `;
  } else {
    return '';
  }
}
export function instantiate(data) {
  const loadedImage = new Promise((resolve) => {
    const image = document.createElement('img');
    image.src = data.address;
    image.onload = () => resolve(image);
  });
  return Promise.resolve({
    address: data.address,
    loadedImage
  });
}

Loading MP3 with Universal ArrayBuffer Loader

export function fetch(data: any) {
  return loadBinaryData(data.address)
    .then((binaryData: IBinaryData) => {
      if (this.builder) {
        return btoa(
          new Uint8Array(binaryData.data)
            .reduce((data, byte) => 
              data + String.fromCharCode(byte), '')
        );
      } else {
        data.metadata.binaryData = binaryData;
        return '';
      }
    });
}
export function translate(data) {
  if (this.builder) {
    return `
      module.exports = {
        url: '${data.address}', 
        buffer: Uint8Array.from(
          atob('${data.source}'), 
          function(c) { return c.charCodeAt(0) }
        ).buffer
      }
    `;
  } else {
    return '';
  }
}
export function instantiate(data) {
  return {
    url: data.address,
    buffer: data.metadata.binaryData.data
  };
}

JSPM

JSPM (JavaScript Package Manager), is a Broker between Packages and SystemJS

Build Single SFX Bundle using JSPM

  • Uses SystemJS Builder

  • Includes minimal runtime

  • Uses Rollup.js to do tree-shaking

Thank You!

 Gion Kunz

 Front End Developer

@GionKunz

gion.kunz@oddeven.ch

https://oddeven.ch

Angular WorkshopS

FRIDAY, JULY 23 / MONDAY, JULY 26

10% Discount Code "PANTALK"

Sources and Credits

  • Pokémon images & names © 1995-2017 Nintendo/Game Freak.

  • Animated background images http://giphy.com/