MODULAR JAVASCRIPT: PUT THINGS IN THE RIGHT PLACE

Andrei Yemialyanchik

limurezzz@gmail.com

What is module?

A module is simply a JavaScript file written with module syntax.

 

Modules export values, which can then be imported by other modules.

Existing module systems

Immediately Invoked Function Expression (IIFE)

var MODULE = (function () {
	var my = {},
		privateVariable = 1;

	function privateMethod() {
		// ...
	}

	my.moduleProperty = 1;
	my.moduleMethod = function () {
		// ...
	};

	return my;
}());

AMD

The Asynchronous Module Definition

NOT A JAVASCRIPT FRAMEWORK

NOT RELATED TO THE AMD THE COMPANY

The Asynchronous Module Definition API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. ​

Designed for Browsers!

DEFINITION

 define('counterModule', function(){
    var count = 0;
    return {
        add: function(){
            return count++;
        },
        reset: function(){
            count = 0;
        },
        get: function(){
            return count;
        }
    }
 });

DEFINE A MODULE

module id is usually left empty

 define('viewModule', ['counterModule'], function(counter){
    
    var views = {};

    return {
        register: function(viewName, viewData){
           counter.add();
            views[viewName] = viewData;
        },
        count: function(){
             return counter.get();
        }
        ....
    };
});

USING OTHER MODULES

CommonJs

//loading module
var myModule = require(‘../myModule’);

//declaring module
module.exports = myModule;

Still not a framework

Not for Browsers!

by default in NODE.JS

UNIFIED MODULE DEFINITION

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define('myModule', ['pkg/A', 'pkg/B'], function (A, B) {
      return factory(A, B);
    });
  } else if (typeof exports === 'object') {
  // Node.js
    module.exports = factory(require('pkg/A'), require('pkg/B'));
  } else {
    // Browser globals
    root.myModule = factory(root.A, root.B);
  }
}(this, function (B, B) {
  var myModule = {};
  return myModule;
})); 

UMD

ES6 Modules

// default export
export default MyModule;

import MyModule from './MyModule';

/* named exports */

export subModule;

import {subModule} from './MyModule';

This is standard!

ES6 modules

Still no support from browsers

No native loader!!

Just use transpilers

VS

WIN

Tracuer

export

import

// correct
export let one = 1;
// also correct
let two = 2;
export {two};
// also correct
export {one, two};
// also correct
export {one as once, two as twice};

// class export
export class User {
  constructor(name) {
    this.name = name;
  }
};

// function export
export function sayHi() {
  alert("Hello!");
};

// or separately from definition
export {User, sayHi}

// no name - error!
export function() { alert("Error"); };
// nums.js
export let one = 1;
export let two = 2;

// module.js
import {one, two} from "./nums";
alert( `${one} and ${two}` ); // 1 and 2

// import one with name item1, 
// two – with name item2
import {one as item1, two as item2} from "./nums";
alert( `${item1} and ${item2}` ); // 1 and 2

// import everything from nums.js
import * as numbers from "./nums";

// exported values are part of numbers
alert( `${numbers.one} and ${numbers.two}` ); 
// 1 and 2

default export

// default class export
export default class User {
  constructor(name) {
    this.name = name;
  }
};

import User from './user';

new User("Andrei");

// if user.js had
export class User { ... }

// then you would use curly brackets:
import {User} from './user';

new User("Andrei");

Babel

export const foo = 'bar'

/*
 * import {foo} from './foo'
 * console.assert(foo === 'bar')
 */
'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
var foo = exports.foo = 'bar';

/*
 * import {foo} from './foo'
 * console.assert(foo === 'bar')
 */

OK, how to load modules in browser?

Module loaders

NodeJS (2009)

AMD (2011)

Browserify (2014)

2014

July 2013

SystemJS

AMD with Require.js

siteroot/
  js/
    app.js
    require.js
    jquery.js
    mymodule.js
  index.html
<script data-main="/js/app" src="/js/require.js"></script>

That's it!

require.config.js

<script src="scripts/require.js"></script>
<script>
  require.config({
    baseUrl: "/another/path",
    paths: {
        "some": "some/v1.0"
    },
    waitSeconds: 15
  });
  require( ["some/module", "my/module", "a.js", "b.js"],
    function(someModule,    myModule) {
        //This function will be called when all the dependencies
        //listed above are loaded. Note that this function could
        //be called before the page is loaded.
        //This callback is optional.
    }
  );
</script>

Want to use npm packages?

You have couple options:

node r.js -convert path/to/commonjs/modules/ path/to/output

You can use r.js to run AMD-based projects in Node

It is part of the RequireJS project, and works with the RequireJS implementation of AMD.

Who use Require.js now?

In the past?

Browserify

npm install -g browserify

You can load CommonJS modules in browser

Use npm modules in Browser

Build your code

browserify js/main.js -o js/bundle.js -d
var _ = require('underscore'),
  names = ['Bruce Wayne', 'Wally West', 'John Jones', 'Kyle Rayner', 'Arthur Curry', 'Clark Kent'],
  otherNames = ['Barry Allen', 'Hal Jordan', 'Kara Kent', 'Diana Prince', 'Ray Palmer', 'Oliver Queen'];
 
_.each([names, otherNames], function(nameGroup) {
  findSuperman(nameGroup);
});

function findSuperman(values) {
  _.find(values, function(name) {
    if (name === 'Clark Kent') {
      console.log('It\'s Superman!');
    } else {
      console.log('... No superman!');
    }
  });
}

Result

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){
var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);
if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");
throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};
t[o][0].call(l.exports,function(e){var n=t[o][1][e];
return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}
var i=typeof require=="function"&&require;
for(var o=0;o<r.length;o++)s(r[o]);
return s})({1:[function(require,module,exports){
var _ = require('underscore'),
  names = ['Bruce Wayne', 'Wally West', 'John Jones', 'Kyle Rayner', 'Arthur Curry', 'Clark Kent'],
  otherNames = ['Barry Allen', 'Hal Jordan', 'Kara Kent', 'Diana Prince', 'Ray Palmer', 'Oliver Queen'];
 
_.each([names, otherNames], function(nameGroup) {
  findSuperman(nameGroup);
});

function findSuperman(values) {
  _.find(values, function(name) {
    if (name === 'Clark Kent') {
      console.log('It\'s Superman!');
    } else {
      console.log('... No superman!');
    }
  });
}

On the edge of the technologies

"devDependencies": {
  "browserify": "latest",
  "watchify": "latest"
},
"scripts": {
  "build-js": "browserify js/main.js > js/findem.js",
  "watch-js": "watchify js/main.js -o js/findem.js"
}

package.json

Of course you can use file config

Because life is too short for CLI commands

({
    paths: {
        requireLib: '../third-party/almond'
    },
    include: ['requireLib', 'router/front-page-router', 'router/challenge-page-router'],
    baseUrl: '.',
    mainConfigFile: 'loader.js',
    name: 'loader',
    out: 'loader-min.js',
})

Browserify can give you browser versions of node core libraries

'events', 'stream', 'path', 'url', 'assert', 'buffer', 'util', 
'querystring', 'http', 'vm', and 'crypto' 

when you require() them

Who use Browserify now?

In the past?

SystemJS loader

<!-- bundles:js -->
<script src="node_modules/systemjs/dist/system.js"></script>
<script src="system.config.js"></script>
<script>System.import('./index')</script>
<!-- endinject -->

'./index' here can be any kind of known module

How it works

System.import('./src/codeFile.js').then(function(m) {
    // do something with 'm'
});
  1. normalize path (absolute, relative, and aliased)

  2. check its internal registry to see if that module has already been loaded

  3. perform additional modification of the file’s contents if desired

  4. execute the module, add it to the registry, and then resolve the promise with the module

Config

System.config({
    defaultJSExtensions: true,
    meta: {
        "angular-ui-router": { deps: ['angular'] },
        "angular-translate": { deps: ['angular'] },
        "angular-touch": { deps: ['angular'] },
        "angular-cookies": { deps: ['angular'] },
        "angular-animate": { deps: ['angular'] },
        "angular-ui-bootstrap": { deps: ['angular'] }
    },
    paths: {
        "angular": "node_modules/angular/index.js",
        "angular-ui-router": "node_modules/angular-ui-router/release/angular-ui-router.js",
        "angular-translate": "node_modules/angular-translate/dist/angular-translate.js",
        "angular-sanitize": "node_modules/angular-sanitize/angular-sanitize.js",
        "angular-cookies": "node_modules/angular-cookies/angular-cookies.js",
        "angular-ui-bootstrap": "node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js",
        "angular-ui-tree": "node_modules/angular-ui-tree/dist/angular-ui-tree.js",
        "angular-tree-control": "node_modules/angular-tree-control/angular-tree-control.js",
        "angularModalService": "node_modules/angular-modal-service/dst/angular-modal-service.min.js",
        "es6-promise": "node_modules/es6-promise/dist/es6-promise.js",
        "fetch": "node_modules/whatwg-fetch/fetch",
        "ckeditor": "node_modules/ckeditor/ckeditor.js",
        "reflect-metadata": "node_modules/reflect-metadata/Reflect.js",
        "querystringify": "node_modules/querystringify/index.js",
        "lodash": "node_modules/lodash/lodash.js",
        "lodash/*": "node_modules/lodash/*.js"
    }
});

Also SystemJS provide its own modules. See API

  1. Register module

  2. Import module

  3. Check whether it exists

  4. some other useful things

It can:

Example

System.register(['./config/main.module', 
'./config/main.module.config', 
'angular-translate'], 
function(exports_1, context_1) {
    'use strict';
    var __moduleName = context_1 && context_1.id;
    var main_module_1;
    function bootstrap() {
        angular.bootstrap(document, 
        ['pascalprecht.translate', main_module_1.tmModule.name]);
    }
    exports_1("bootstrap", bootstrap);
    return {
        setters:[
            function (main_module_1_1) {
                main_module_1 = main_module_1_1;
            },
            function (_1) {},
            function (_2) {}],
        execute: function() {
        }
    }
});

Wonderful support from TypeScript

{
  "compilerOptions": {
    "target": "es5",
    "module": "system",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "noEmitHelpers" : true,
    "rootDir" : "./",
    "outDir" : "dist"
  }
}

.tsconfig

TypeScript config documentation

Angular team use SystemJS in their tutorial

But they say:

"Although we use SystemJS for illustrative purposes here, it's only one option for loading modules. Use the module loader that you prefer. For Webpack and Angular, see Webpack: an Introduction. Or, learn more about SystemJS configuration in general here."

Who use SystemJS?

Monster!!!

npm install webpack -g

Install

webpack entry.js bundle.js

Use

Tons of plugins!

plugins: [
  new webpack.optimize.UglifyJsPlugin({
	compress: {
	  warnings: false
	}
  }),

  new webpack.ProvidePlugin({
	$: "jquery",
	jQuery: "jquery",
	"window.jQuery": "jquery"
  }),

  new WebpackNotifierPlugin(),
]
module.exports = {
    entry: './main',
    output: {
        filename: 'bundle.js',
        libraryTarget: "amd"
    },
    module: {
        loaders: [
            {
                test: /\.js$/, loader: "babel-loader",
                query: {
            	    presets: ['es2015']
	        },
            }
        ]
    }
};

Config

Which format to export the library:

"var" - Export by setting a variable: var Library = xxx (default)

"this" - Export by setting a property of this: this["Library"] = xxx

"commonjs" - Export by setting a property of exports: exports["Library"] = xxx

"commonjs2" - Export by setting module.exports: module.exports = xxx

"amd" - Export to AMD (optionally named - set the name via the library option)

"umd" - Export to AMD, CommonJS2 or as property in root

you can now require('file') instead of  require('file.coffee')

resolve: {
  extensions: ['', '.js', '.json', '.coffee']
}

Here's how you can teach webpack to load CoffeeScript and ES6 support:

module: {
  loaders: [
    { test: /\.coffee$/, loader: 'coffee-loader' },
    { test: /\.js$/, loader: 'babel-loader' }
  ]
}

Want to use CSS? 

loaders: [
  { test: /\.css$/, loader: "style-loader!css-loader" },
]
require("./stylesheet.css");

Preloaders

preLoaders: [
    {
        test: /\.(es6|js)$/,
        exclude: /node_modules/,
        loaders: ['eslint']
    }
]

WHY WEBPACK?

It's like browserify but can split your app into multiple files.

WHY WEBPACK?

It often replaces grunt or gulp

WHY WEBPACK?

  • Bundle splitting

  • Async loading

  • Packaging static assets like images and CSS

Who use webpack?

MODULAR JAVASCRIPT

By Andrei Yemialyanchik

MODULAR JAVASCRIPT

JS Module Formats, Module Loaders: RequireJS, Browserify, System.js, WebPack. How to find the way through all of those and don't get lost.

  • 1,226