JavaScript Modules «
June 16, 2016
- Popular Module Patterns
- Tools
- Module Patterns in ES5
- Module Formats
- Module Loaders
- Modules in ES2015
- Module Bundlers
Popular Module Patterns
- AMD
- CommonJS
- Client-side module loaders
- ES2015 (aka Harmonky, aka ES6)
- Module bundlers
Module
A group of code an data related to a particular piece of functionality. It encapsulates implementation details, exposes a public API, and is combined with other modules to build a larger application.
- Modules let us create higher-level abstractions.
- Encapsulation (we can define an interface and hide the rest, so it’s more maintainable).
- Reusability
- Simplify dependency management
Tools
- Any editor
- Node.js
- npm
- npm packages
Module Patterns in ES5
IIFE
IIFE: Immediately Invoked Function Expression.
They provide encapsulation and reduce global scope pollution.
But No dependency management.
(function (name){
console.log('Hello '+name);
})('Pere Pages');
Revealing Module pattern
- Function scoping provides encapsulation
- Adds one value to global scopoe per module
- Clear delineation between private implementation and public API
- NO dependency management
- Puere javascript that works in modern browsers
- Comes in two popular flavors
- Singleton
- Constructor function
Singleton
var scoreboard = function() {
// private variables
var whatever = 'whatever';
return {
showMessage: showMessage
};
function showMessage(message) {
console.log(message);
}
}();
scoreboard.showMessage('Hello me!!');
Constructor Function
var Scoreboard = function () {
// private members
var message = 'Welcome to the game!';
function printMessage() {
console.log(message);
}
return {
showMessage: printMessage
}
};
(function() {
var scoreboard1 = new Scoreboard();
var scoreboard2 = new Scoreboard();
scoreboard1.showMessage();
scoreboard2.showMessage();
})();
Module Formats
Formats vs Loaders
Module Format –> Syntax
- AMD
- COmmonJS
- UMD
Loader –> Execution
AMD (Asynchronous Module Definition)
Loaders
- RequireJS
- Curl.js
- SystemJS
The AMD syntax is defined for modules that will be loaded in a browser.
The define function is part from the loader.
// we have the dependencies and the module inside the function
// the dependencies are relative to the file
// the dependencies are injected as parameters
define(['./player'],function(player) {
return {
calculateScore: calculateScore
};
function calculateScore() {
// calculate the score here
}
});
CommonJS
CommonJS syntax is used for Server-side development, but we can use in the browser with module loaders like SystemJS.
UMD (Universal Module Definition)
It is supposed to be used for Browser and Server-side modules. But usually it is only used by transpilers. If we are using Typescript, for instance, we can tell the compiler/transpiler to use this syntax.
System.register
System.register can be considered as a new module format designed to support the exact semantics of ES6 modules within ES5.
ES2015 module format
Built-in support for modules. We need to transpile so we get the code in any of the previous syntaxes (CommonJS, UMD, System.register)
Module Loaders
- RequireJS (AMD)
- SystemJS (AMD, CommonJS, UMD, System.register)
RequireJS
npm install requirejs --save
define([],function() {
// private members
var playerName = '';
return {
logPlayer: logPlayer,
setName: setName,
getName: getName
}
function logPlayer() {
console.log('The current player is ' + playerName + '.');
}
function setName(newName) {
playerName = newName;
}
function getName() {
return playerName;
}
});
// game.js
define(['./player','./game'], function(Player,Game) {
// private members
var player = new Player();
var game = new Game();
return {
getPlayer: getPlayer,
getGame: getGame
}
function getPlayer() {
return player;
}
function getGame() {
return game;
}
});
In the index.html
<html>
<header></header>
<body>
<script data-main="js/app" src="node_modules/requirejs/require.js"></script>
</body>
</html>
CommonJS
Each file is a module. We don’t need to wrap the file inside a function.
Export Syntax
module.exports === exports
// exports it's just a shortcut
exports.calculateScore = calculateScore;
// is equivalent to
module.exports.calculateScore = calculateScore;
We cannot do
// we cannot do
exports = {};
exports = function() {};
// but we can do
module.exports = {};
module.exports = function () {};
Defining an API
module.exports = {
addResult: addResult,
updateScoreboard: updateScoreboard
};
function addResult() {
// code...
}
function updateScoreboard() {
// code ...
}
SystemJS in the index.html
The format: 'cjs'
stands for commonJS.
<html>
<header></header>
<body>
<script src="node_modules/systemjs/dist/system.js"></script>
<script>
System.config({
meta: {
format: 'cjs'
}
});
System.import('js/app.js');
</script>
<div id="main"></div>
</body>
</html>
Modules in ES2015
They are Native modules, but we currenty need to transpile them.
- Support for dependency management
- Encapsulate implementation details
- Explicitly expose public API
Module Workflow
ES2015 Modules -> Transpile (Babel) -> AMD, CommonJS, etc. -> RequireJS, SystemJS, etc.
Importing and Exporting
Importing
- Imported items are dependencies
- May import an entire module or just part of it
- May create an alias for imported items
Exporting
- Exposes the API of the module
- May export items at declaration or all at once as a list
- May specify a default export
Exporting
export function addResult(newResult) {
// add new result to the list
}
export function updateScoreboard() {
// update the scoreboard here
}
function somePrivateFunction() {
// not part of the API
}
export var homeTeam = 'Tigers';
export {addResult, updateScoreboard as show, homeTeam};
function default addResult(newResult) {
// add new result to the list
}
function updateScoreboard() {
// update the scoreboard here
}
function somePrivateFunction() {
// not part of the API
}
var homeTeam = 'Tigers';
Importing
// importing everything
import * as scoreboard from './scoreboard.js';
scoreboard.updateScoreboard();
// importing what's needed
import {addResult, updateScoreboard} from './scoreboard.js';
// importing with alias
import {updateScoreboard as update} from './scoreboard.js';
// importing the default
import newResult from './scoreboard.js';
// importing default and what's needed
import newResult, { updateScoreboard } from './scoreboard.js';
Babel
Babel is a Transpiler
- Transpiler
- Supports most ES2015 features
- Executed as a build step
- Produces clean, readable JavaScript
- Highly configurable
- Supports all of the popular module formats
Babel will transpile the code from ES6 to ES5, but then we will still need a Module Loader!
npm install --save-dev babel-cli babel-preset-es2015
Running Babel
We can run it from the shell
./node_modules/.bin/babel js --presets es2015 --out-dir build
Or we can create a script in package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "./node_modules/.bin/babel js --presets es2015 --out-dir build"
}
In the example Babel transpiles to CommonJS modules. I use SystemJS as Module Loader.
Module Bundlers
They do the same as Module Loaders but at build time instead of run time.
- Browserify
- Webpack
The Role of a Module Bundler
- Alternative to module loaders
- Follow module dependencies
- Correctly order dependencies
- Combine modules into fewer files
- May decrease application startup line
Workflow
Build step.
AMD, CommonJS, ES2015 Modules -> Bundler -> bundle.js -> Browser
Browserify
Attemps to make Node.js modules available for browser apps.
- Bundles CommonJS modules
- Easy to use
npm install browserify --save
# we have to manually create the build folder
./node_modules/.bin/browserify js/app.js --outfile build/bundle.js
Webpack
- Bundles AMD, CommonJS, and ES2015 modules
- Code splitting
- Bundles more than just Javascript modules
- Uses “loaders” for transformation before bundling
npm install webpack --save-dev
./node_modules/.bin/webpack js/app.js build/bundle.js
npm install --save-dev babel-cli babel-core babel-loader
webpack.config.js
The file is pertty autodescriptive.
module.exports = {
entry: './js/app.js',
output: {
path: './build',
filename: 'bundle.js'
},
module: {
loaders: [{
test: /\.js$/,
exlcude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
}]
}
};