What is difference between Module Loader and Module Bundler in JavaScript?
Module loaders and bundlers both make it more actionable to write modular JavaScript applications.
Module loaders
A module loader is typically some library that can load, interpret and execute JavaScript modules you defined using a certain module format/syntax, such as AMD or CommonJS.
When you write modular JavaScript applications, you usually end up having one file per module. So when writing an application that consist of hundreds of modules it could get quite painful to make sure all files are included and in the correct order. So basically a loader will take care of the dependency management for you, by making sure all modules are loaded when the application is executed. Checkout some popular module loaders such as RequireJS and SystemJS to get an idea.
Module bundlers
Module bundlers are an alternative to module loaders. Basically they do the same thing (manage and load interdependent modules), but do it as part of the application build rather than at runtime. So instead of loading dependencies as they appear when your code is executed, a bundler stitches together all modules into a single file (a bundle) before the execution. Take a look at Webpack and Browserify as two popular options.
When to use what?
Which one is better simply depends on your application’s structure and size.
The primary advantage of a bundler is that it leaves you with far fewer files that the browser has to download. This can give your application a performance advantage, as it may decrease the amount of time it takes to load. However, depending on the number of modules your application has, this doesn’t always have to be the case. Especially for big apps a module loader can sometimes provide the better performance, as loading one huge monolithic file can also block starting your app at the beginning. So that is something you have to simply test and find out.
ES6/ES2015 Update : Note that ECMAScript 2015 (or ES6) comes with it’s own, native implementation of modules.
Webpack is a module bundler, but that entails far more than putting files together.
webpack is a module bundler for modern JavaScript applications. When webpack processes your application, it recursively builds a dependency graph that includes every module your application needs, then packages all of those modules into a small number of bundles – often only one – to be loaded by the browser.
When you bundle your application, webpack needs to figure out what it must include in that bundle. Different kinds of module APIs have been used in JavaScript, so webpack uses them to determine which modules are being used (see Module API – Methods for the supported APIs). The most well known is CommonJS, which is what Node.js uses (require).
A big merit of webpack is that it works in the browser (hence the name webpack). The problem is that browsers don’t support CommonJS (require doesn’t exist), which means that you couldn’t use the rich Node ecosystem. To make it work, webpack transforms any import syntax alongside including the needed source, to which they refer.
- https://webpack.js.org/concepts/
https://webpack.js.org/concepts/modules/
https://nodejs.org/api/modules.html
https://webpack.js.org/loaders/
https://webpack.js.org/api/module-methods/
What does Babel do? The Babel website says:
Babel is a JavaScript compiler.
JavaScript is continuously evolving and new features are being added to the language. The issue is that browsers need some time to implement these features and the features need to reach a certain maturity, because once they are in the spec, you can’t remove or change them dramatically any more. The TC39 process for ECMAScript features explains the process of including a new feature.
This is where Babel comes in. It allows you to use these features before any browsers even start implementing them (some of these feature might not even make it into the spec). Babel transforms these features into a semantically equivalent code that can be run in today’s JavaScript engines. There are a lot more features than just import/export, which Babel could be used for (e.g. Object Rest/Spread Properties for ECMAScript), especially if you want to support older (but still used) browser versions.
Since webpack 2, ES modules work out of the box and they don’t need to be transpiled by Babel and you should leave them to webpack because it enables Tree Shaking. If you want to use JavaScript features that are not yet supported, you will need babel-loader to transpile them. To clarify, loaders are modules that transform a given input file to valid JavaScript, which can be used by webpack (this could be anything as long as you have a loader that handles it, for example css-loader allows you to import CSS files). Loaders are specific to webpack, but Babel is a standalone tool which is widely used outside of webpack, and babel-loader combines them by using Babel under the hood and feeding it to webpack in the expected form. You could use them individually by running Babel first and then webpack on the resulting files, but loaders bridge the gap, which gives a much nicer development experience.
https://github.com/tc39/proposal-object-rest-spread
https://webpack.js.org/guides/tree-shaking/
NPM, Yarn – Package Manager
First, there’s a variety of module formats out there in use:
- CommonJS
- AMD
- UMD
- ES6 Modules
Tools for bundling assets come in a variety of shapes and sizes:
- Browserify
- Webpack
- jspm
- Rollup
- Brunch / Brocolli
- Sprockets
- Build your own with Gulp / Grunt
Then there’s transpilers that you may want to use:
- Babel(Compiler) for ES6
- CoffeeScript
- Typescript
There are various libraries that allow dynamic loading of modules:
- Require.js
- System.js
When we run npm run <NAME>, NPM executes the command associated with the name you pass, located in the “scripts” section of your package.json. In this case, np run serve fires up Beefy.
Open up package.json again, and add to your serve script:
“serve” : “beefy main.js –browserify -t caching-coffeeify –live”
For Browserify there are plugins coffeeify, tsify and babelify to transpile and bundle.
For Webpack there are loaders coffee-loader, ts-loader and babel-loader to require modules in different languages.
Browserify vs Webpack
The religious wars between users of Angular and Ember, Grunt and Gulp, Browserify and Webpack, all prove the point: Choosing your development tools is serious business.
Understanding JavaScript Modules: Bundling & Transpiling
javascript-modules-bundling-transpiling
https://www.sitepoint.com/javascript-modules-bundling-transpiling/
Most folks consider modules, dependency management and dynamic loading a basic requirement for any modern programming language—these are some of the most important features added to JavaScript in 2015.
Modules are used extensively in Node but our focus here will be on how we can use modules inside of the browser. We’ll explore a little history, navigate through the hazardous current landscape with the end goal of having a clear path forward and appreciation for the most important module bundlers for JavaScript today: Browserify, Webpack and jspm.
Finally we’ll look at how to use these tools with transpilers like CoffeeScript, TypeScript and Babel.
Modules through the Ages
JavaScript has existed since 1995 and to this day no browser supports modules natively. Node and CommonJS were created in 2009 and the vast majority of packages in npm use CommonJS modules.
Browserify was released in 2011 and brought CommonJS modules to the browser allowing client-side JavaScript to require npm packages. The tool bundles up all of the required dependencies into a single JavaScript file.
The Past
A library such as jQuery adds $ to the global scope or window.
window.$ = function() { ... };
We include a script to a library and use the global objects it exposes.
<script src="jquery.js"></script>
<script>
$(function() { ... });
</script>
Your own application code was typically namespaced under a global like App to prevent polluting the global scope. Without this it’s only so long before you have name collisions and things fall apart.
var App = {};
App.Models = {};
App.Models.Note = function() {};
The Future
Libraries export objects in a common module format (ES6 modules).
export default function $() { ... }
We import a module into a local scope and use it.
import $ from 'jquery';
$(function() { ... });
No globals required 👍
Source order independence
Access to npm
No need to namespace your own application code
Dynamically load modules at any time as required
The Present
It’s really really complicated. First, there’s a variety of module formats out there in use:
CommonJS AMD UMD ES6 Modules
Tools for bundling assets come in a variety of shapes and sizes:
Browserify jspm Webpack Rollup Brunch / Brocolli Sprockets Build your own with Gulp / Grunt
Then there’s transpilers that you may want to use:
Babel for ES6 CoffeeScript Typescript
In addition, there are various libraries that allow dynamic loading of modules:
Require.js System.js
These are shortened lists of popular tools currently in use—it’s a minefield for beginners and experts alike. The cost of transpiling also highlights that you can mix and match a lot of these tools and get different results.
Let’s Consolidate Tooling in 2016
Front-end developers have been using build tools for a very long time but it’s only in the last few years that we’ve seen a build step become the norm. Tools like Sass and CoffeeScript helped make pre-processing mainstream but the momentum around ES6 has now got everyone on board.
Gulp and Grunt have been very popular in the past few years, these tools allow you to write a series of transforms to pipe your assets through. They’ve been used to great effect and are still popular, though a lot of people choose to use the tools directly through npm – see Why I left Gulp and Grunt for npm Scripts and Guide to using npm as a Build Tool.
Personally, I don’t care for building asset pipelines any longer, what I’m looking for is minimal config tools that allow me to use modern tooling as needed: Things like Sass, Autoprefixer, Babel and Coffeescript, a proper module system and loader without having to worry about the implementation, configuration and ongoing maintenance. In essence, every developer has been investing time into creating asset pipelines over the last few years, that’s a lot of wheel re-invention going on and a lot of wasted hours.
The community is divided across tools like Browserify, Webpack, jspm, Sprockets and Gulp. That’s not really a problem, it’s just confusing for everyone trying to understand a clear path forward.
Clear Starting Points
There’s a few things we can agree on:
-ES2015 modules are the one true future module format for JavaScript. -Babel is the ES2015 compiler of choice today. -Native loaders are still a while away from being available in browsers, a report on the Future of JavaScript by Telerik suggests that complete ES2015 support could take over two years given the module loading hurdle. -If you want to use modules now, that will very likely involve CommonJS at some point.
Let’s look at what minimal configuration setups look like using Browserify, Webpack and jspm, these are the most important JavaScript bundlers to know about today.
A New Project
mkdir modules-app cd modules-app npm init -y npm install --save-dev browserify webpack jspm mkdir src touch src/{entry,lib}.js index.html
Update index.html in your favourite text editor
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Modules!</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
We’ll also need a server to run the code—for example live-server which is a great little zero-config HTTP server with live reload capability. Install it globally with npm install -g live-server and run live-server from the project root to start.
Browserify
Browserify lets you require('modules') in the browser by bundling up all of your dependencies.
Open up src/lib.js and add our very first module.
var double = function(number) {
return number * 2;
}
module.exports = {
double: double
}
Open up src/entry.js and we’ll require our module and use it.
var lib = require('./lib.js');
console.log(lib.double(2));
Update the scripts section in package.json
"scripts": {
"browserify": "browserify ./src/entry.js -o ./bundle.js"
},
Run this script with npm run browserify
Browserify will create bundle.js in the project root and you should see a most exiting 4 output to the console. To learn more about what Browserify is doing and how this bundle gets created I recommend watching Introduction to Browserify at egghead.io
Congratulations! We now have modules in the browser! 🎉
Another key benefit of Browserify is that it gives you access not only to modules that you author, but to npm modules as well, let’s install lodash to see.
npm install lodash --save-dev
Edit src/lib.js
var sum = require('lodash/sum');
var double = function(number) {
return number * 2;
}
var addFive = function(number) {
return sum([number, 5]);
}
module.exports = {
double: double,
addFive: addFive
}
Edit src/entry.js and call our new addFive function
var lib = require('./lib.js');
console.log(lib.double(2));
console.log(lib.addFive(2));
Create the bundle again with npm run browserify and in the browser you should see a 4 and a 7 which shows that we’ve successfully imported and used lodash’s sum function.
If you’ve followed along this far you now know everything you need to get started using modules in the browser today, this brings many benefits that we outlined at the start.
No globals required 👍 Source order independence Access to npm No need for namespacing your own application code We’ll look at dynamic loading of modules at runtime later.
Webpack
Webpack is a module bundler. Webpack takes modules with dependencies and generates static assets representing those modules.
Let’s add a new script to package.json for calling webpack
"webpack": "webpack ./src/entry.js bundle.js"
Run it with npm run webpack
Webpack will have rewritten bundle.js and the output in the browser should be exactly the same.
Try running npm run browserify and npm run webpack and examining the differences in the compiled bundle.js file. It’s not really important to understand how these tools work internally, the important thing to note is that whilst the implementations are different they are essentially doing the same task of compiling the same code with CommonJS modules into standard browser-friendly JavaScript. Each module is put inside a function within bundle.js and assigned an ID so that it can be loaded as required.
There’s far more to Webpack than this though! It truly is the swiss army knife of module bundlers. Webpack also comes with great tools for development out of the box, things like hot module replacement which will automatically reload individual modules in the browser as they are changed—similar to LiveReload but without the page refresh.
There is a growing list of loaders for different asset types too, even CSS with the css-loader and style-loader—loaders which can compile CSS into the JavaScript bundle and inject it into the page at runtime. This is outside of the scope of this article but more can be found on this at getting started with Webpack.
JavaScript Transpilers
These are three of the most popular transpilers used today, you may also want to use another from the very long list of languages that compile to JS.
Before looking at how we can use them with our module bundlers let’s look at how to use the tools directly first.
npm install --save-dev coffee-script typescript babel-cli babel-preset-es2015
touch src/{coffee-lib.coffee,ts-lib.ts,es6-lib.js}
CoffeeScript
Edit coffee-lib.coffee
sum = require 'lodash/sum'
double = (number)-> number * 2
addFive = (number)-> sum([number, 5])
module.exports =
double: double
addFive: addFive
Note: CoffeeScript uses the CommonJS syntax for modules
Add a script to package.json to run the coffee executable
"coffee": "coffee --output ./dist ./src/coffee-lib.coffee"
Run it with npm run coffee
TypeScript
Edit ts-lib.ts
/// <reference path="lodash.d.ts" />
import * as _ from 'lodash';
const double = (value: number)=> value * 2
const addFive = (value: number)=> _.sum([value, 5])
export = {
double,
addFive
}
Note: TypeScript has its own syntax for modules that look like a mix of ES2015 module syntax and CommonJS.
Add a script to package.json to run the tsc executable
"tsc": "tsc --outDir ./dist ./src/ts-lib.ts"
Run it with npm run tsc
The compiler will complain about not being able to find lodash as it requires a type definition to know how to work with external modules that aren’t TypeScript files. You can fetch a definition file with:
cd src
curl -O https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/master/lodash/lodash.d.ts
cd ..
npm run tsc
Babel
Edit es6-lib.js
import sum from 'lodash/sum';
const double = (number)=> number * 2
const addFive = (number)=> sum([number, 5])
export {
double,
addFive
}
Note: Babel understands the lovely new ES2015 module syntax.
Babel requires a config file for specifying which presets to use
echo '{ "presets": ["es2015"] }' > .babelrc
Add a script to package.json to run the babel cli
"babel": "babel ./src/es6-lib.js -o ./dist/es6-lib.js"
Run it with npm run babel
The files in /dist now contain ES5 code in CommonJS module format that will work perfectly with Browserify or Webpack as we used previously. You can either transpile down to ES5 with CommonJS first and then bundle, or you can use other packages to do both in a single step.
For Browserify there are plugins coffeeify, tsify and babelify to transpile and bundle.
For Webpack there are loaders coffee-loader, ts-loader and babel-loader to require modules in different languages.
jspm
jspm is a package manager for the SystemJS universal module loader, built on top of the dynamic ES6 module loader
jspm takes a different approach and starts with the module loader System.js. System.js is a project that will follow the loader spec as it’s developed.
Install and initialize a jspm project
npm install -g jspm jspm init
Accept all of the defaults and ensure that Babel is used as the transpiler, that will configure System.js to use Babel when it runs into ES6 style modules.
Update index.html to load and configure System.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Modules!</title>
</head>
<body>
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<!--<script src="bundle.js"></script>-->
<script>
System.import('src/entry.js');
</script>
</body>
</html>
In the browser you’ll see a few requests being made and a 404 for lodash, this is because jspm loads packages from the jspm_packages directory by default.
Run jspm install lodash to install lodash in that directory and you should see the expected output in the console, a 4 and a 7, here’s what’s happening:
Our entry.js file is being dynamically loaded with System.import(‘src/entry.js’);.
System.js loads entry.js, sees that it requires our lib module so fetches it at runtime.
System.js loads lib.js, sees that it requires lodash/sum and fetches it too.
System.js also knows how to work directly with ES6, update entry.js to dynamically require our ES6 module and compile on the fly.
import lib from './es6-lib';
// import lib from '../dist/coffee-lib';
// import lib from '../dist/ts-lib';
console.log(lib.double(2));
console.log(lib.addFive(2));
You can also try loading the ES5 compiled versions of our CoffeeScript or TypeScript modules by uncommenting those lines one at a time. Another option is to use System.js plugins to transpile the code, rather than requiring precompiled ES5 code.
Add a final script to package.json for creating a bundle with jspm
"jspm": "jspm bundle src/entry bundle.js"
Run it with npm run jspm
Finally uncomment the script tag for bundle.js in index.html and the browser should load a production-ready bundle without any extra http requests.
<script src="bundle.js"></script>
Revisiting Webpack
Our Webpack example earlier was the simplest example using the default options, it compiled entry.js with CommonJS modules down into a single bundle. When doing more fancy things with Webpack you’ll want to create a custom config file for all of the loader configuration.
Create webpack.config.js in the root of the project
module.exports = {
context: __dirname + "/src",
entry: "./entry",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
},{
test: /\.coffee$/,
loader: 'coffee-loader'
},{
test: /\.ts$/,
loader: 'ts-loader'
}]
}
}
Update index.html to load only the bundled file again.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Modules!</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
Install the loaders for transpiling with Babel, CoffeeScript and TypeScript
npm install --save-dev babel-loader coffee-loader ts-loader
Install webpack globally and run without arguments to create the bundle from our config file.
npm install -g webpack
webpack
Now that webpack knows to use these loaders for these file extensions we’re free to use ES6, CoffeeScript or TypeScript from entry.js, give it a try by uncommenting these one by one.
import lib from './es6-lib.js';
// import lib from './coffee-lib.coffee';
// import lib from './ts-lib.ts';
There is so much more to Webpack than I’ve covered here, but these simple setups are a great starting point.
There and Back Again
And so we end our exploration of modules, they do solve a lot of problems and can really reduce the complexity of our applications—if the tooling doesn’t get in our way. If you’re not already using modules, now is the time. No need to spend unnecessary hours building asset pipelines, instead use simple tools that Just Work™.
Webpack is the current juggernaut and you’ll be able to configure it to do almost anything. jspm is a great tool for all your bundling needs and works with a variety of formats and has a nice developer experience. Browserify is still a solid option, the grandfather of modern module bundlers—it’s ecosystem has grown to include some of Webpack’s much loved features (such as bundle splitting and hot reloading). Finally System.js is perfect for when you need to be able to load extra modules at runtime.
You won’t want to use all of the above tools in one project but it’s important to have an understanding of these three popular options, as well as how you can use transpilers when the need arises. If you just want to use modules, then Browserify, jspm or Webpack with the default options will do the job.
Keep the tooling simple and the configuration light. Happy Bundling.