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,279