Posted on Jul 14th 2016
Webpack is an open-source JavaScript module bundler developed by Tobias Koppers and Webpack contributors. The initial release was March 10th, 2012 and the current stable version is 1.13.
Webpack takes modules with dependencies and generates static assets representing those modules. All your assets are considered as modules and packaged in a final bundle with the rest of your modules. In order for this to work, you must register each loader in the webpack configuration, telling it what it must do when finding a specified file. Loaders are little plugins that transpile files (e.g., Babel for ES6 code to vanilla ES5; style, css, sass, html, and typescript are some other loaders).
In big projects it is not recommended, nor efficient, to put all code in a single file. This is especially true when some blocks of code are only necessary in certain circumstances. Webpack includes a code splitting feature that splits your base code in chunks or fragments that are loaded on demand in your app. This keeps a small initial load, and downloads code on demand when requested for the app. You don’t have to think about what to merge, when or where. If a chunk starts having more dependencies, the chunk is moved to an async chunk instead of being merged; and if these chunks start looking too similar to be worth loading separately, they are merged. You just set up the rules, and from then on, Webpack will automatically optimize your application in the best way possible.
Installing Webpack
Webpack requires Node.js. To install it we are going to need NPM. Install it with the following command:
npm install webpack -g
The –global or -g option installs this module globally so that you can run it as a command in your terminal.
Verification
Create a simple sample project to verify everything is working fine. First, create a folder. I called mine webpack-sample. Use the following command to initialize the project:
npm init -y
It creates a package.json file:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Note: You might want to to add the private attr true to the package.json so this module can never be uploaded to a public NPM module.
Next, let’s add the Webpack dependency:
npm install webpack --save-dev
–save-dev indicates that library will be added as dependency on our package.json.
Let’s say this is our app’s code:
<html>
<head>
<script type="text/javascript" src="jf1.js">
</script>
<script type="text/javascript" src="f2.js">
</script>
<script type="text/javascript" src="f3.js">
</script>
<script type="text/javascript">
f1();
f2();
f3();
</script>
</head>
f1.js
var f1= function(){
console.log("hi 1");
}
f2.js
var f1= function(){
console.log("hi 2");
}
f3.js
var f1= function(){
console.log("hi 3");
}
So the first requirement to pack our files is to modify them to meet the Common.js specification:
f1.js
module.exports= {
f1: function(){
console.log("hi 1");
}
}
After that we create a new app.js file that will import the others following the Common.js specification:
var module1=require("./f1.js");
module1.f1();
var module2=require("./f2.js");
module2.f2();
var module3=require("./f3.js");
module3.f3();
The final step is to create the config webpack file, webpack.config:
module.exports= {
entry:["./app.js"],
output:{
filename:"bundle.js"
}
}
Run the webpack
command to generate the bundle file:
octavio@pc:~$: webpack
Refactor the index.html file and open it to see it does the same:
<html>
<head>
<script type="text/javascript" src="bundle.js">
</script>
</head>
<body>
</body>
</html>
Loaders: A Practical Example
Let’s say we’re building our Genuitec site and adding to main sections of our MyEclipse and SDC products. We want all of our pages to share the same header but the menu below will change depending on the section. How is Webpack going to help us here? Well, consider that after adding many pages, are we going to paste the code of our <header>…</header> in all of them? And if we make a typo? The best option is for all pages to use the same code.
Also, Webpack will help us to load only the stylesheets needed by the current page, as well as the corresponding JavaScript files. This might sound confusing, but after finishing the example it will be clear.
Creating the Project
The first step is to create a new folder and initialize our project.
npm init -y
This creates the /package.json file with our npm package config and dependencies.
Then we proceed to install the webpack module and jquery as we’re going to use it. The –save-dev option tells Webpack that it must even include the library as development dependencies in the package.json file.
npm install jquery --save-dev
npm install webpack --save-dev
Now we create a /webpack.config.js to specify our input files and our output files and path. Also, we need to add which modules will be used. Our config file should look like this:
module.exports = {
entry: './src',
output: {
path: 'builds',
filename: 'bundle.js',
},
module: {
loaders: [
{
test: /\.js/,
loader: 'babel',
exclude: /node_modules/,
},
{
test: /\.html/,
loader: 'html',
},
],
}
Note in module property we have a loaders objects array that will process our files in src directory. Notice that the HTML loader, as the name says, will process the HTML files and the Babel loader will take care of our JS files written in ES6.
Before continuing, we must install these modules or we’ll have trouble compiling our project with Webpack.
HTML loader
npm install html-loader --save-dev
Babel
npm install babel-loader --save-dev
Babel Core
npm install babel-core babel-preset-es2015 --save-dev
We need to create a .babelrc file to process our ES6 code with the following code:
{ "presets": ["es2015"] }
Now we’re going to create our main js file in the src folder.
index.js
import $ from 'jquery';
const Header = require('./Components/Header').default;
const header = new Header();
header.render();
Header Component
The next step is creating the template of our Header “component”:
src/Components/Header.html
<header>
<div class="logo">
<a>Genuitec</a>
</div>
</header>
And our component JavaScript file:
src/Components/Header.js
import $ from 'jquery';
import template from './Header.html';
import Mustache from 'mustache';
export default class Header {
constructor() {
}
render() {
// Add our component to the body
$('body').append(
Mustache.render(template)
);
}
}
We can see that a Mustache function was called to render our template. So we install this plugin/loader as well:
Mustache
npm install mustache --save-dev
The final step is creating our index.html file that will load our generated bundle.js file in the project root folder:
/index.html
<!DOCTYPE html>
<html>
<body>
<script src="builds/bundle.js"></script>
</body>
</html>
And we’re ready to see how our first component is packed. Run the webpack
command and you should see something similar to this:
Open the index file in the browser and you will see the Header was appended to body:
But, what if we want to add an image to our header and a little CSS magic? Well, that’s easy by adding a couple of loaders.
CSS and style loader
npm install css-loader style-loader --save-dev
Url and file loaders
npm install url-loader file-loader --save-dev
And then we need to include these in our webpack.config.js:
loaders: [
{
test: /\.js/,
loader: 'babel',
exclude: /node_modules/,
},
{
test: /\.css/,
loaders: ['style', 'css'],
},
{
test: /\.html/,
loader: 'html',
},
{
test: /\.(png|gif|jpe?g|svg)$/i,
loader: 'url?limit=10000',
},
],
Our final config should be like this:
module.exports = {
entry: './src',
output: {
path: 'builds',
filename: 'bundle.js',
publicPath: 'builds/',
},
module: {
loaders: [
{
test: /\.js/,
loader: 'babel',
exclude: /node_modules/,
},
{
test: /\.css/,
loaders: ['style', 'css'],
},
{
test: /\.html/,
loader: 'html',
},
{
test: /\.(png|gif|jpe?g|svg)$/i,
loader: 'url?limit=10000',
},
],
}
};
Note that also was added a property publicPath
to the output property.
Now we can modify our Header component to include an image and add some CSS.
src/Components/Header.js
import $ from 'jquery';
import template from './Header.html';
import Mustache from 'mustache';
import './Header.css';
export default class Header {
…
src/Components/Header.css
header {
background: white;
}
.logo {
padding: 20px;
border-bottom: 1px solid #e7e7e7;
}
.logo a {
display: inline-block;
background: url('../../logo.png') no-repeat;
background-color: #fff;
height: 45px;
width: 240px;
}
Then we need to modify a little bit our index file, deleting the content in the anchor, so our Header.html file should look like this:
src/Components/Header.html
<header>
<div class="logo">
<a></a>
</div>
</header>
Running the webpack
command again we’ll see the result:
Now, we’re going to add other pages for the SDC and ME products. Each one of these pages has its own menu and we don’t want to load the resources of SDC when the ME page is loaded and vice versa.
The first thing we need to do is to create the main file for each product so these can be called from the browser.
/sdc.html
<!DOCTYPE html>
<html>
<body class="sdc_product">
<script src="builds/bundle.js"></script>
</body>
</html>
/me.html
<!DOCTYPE html>
<html>
<body class="me_product">
<script src="builds/bundle.js"></script>
</body>
</html>
As we can see, there is a specific class in the body label for each product page that allows it to recognize the page where each menu is displayed.
Now, let’s create three little files for each product page, one for CSS code, another for Javascript code and one more for a partial template code.
Let’s start with the SDC product page and create the HTML template in a file named SDC_Menu.html in the Components folder, as shown below:
scr/Components/SDC_Menu.html
<div class="sdc_header">
<ul>
<li><a href="http://www.genuitec.com/products/myeclipse/features/">Features</a></li>
<li><a href="http://www.genuitec.com/products/myeclipse/buy/">Buy</a></li>
<li><a href="http://www.genuitec.com/products/myeclipse/download/">Download</a></li>
<li><a href="http://www.genuitec.com/products/myeclipse/learning-center/">Learning Center</a></li>
<li><a href="https://www.genuitec.com/forums/topics/myeclipse/">Forums</a></li>
</ul>
</div>
This file contains an unsorted list with some links–it will be a menu for the SDC product with a little help from CSS.
src/Components/SDC_Menu.css
.sdc_header {
background: #808080;
padding: 10px 0px;
}
.sdc_header ul {
margin: 0;
}
.sdc_header ul > li{
display: inline;
padding: 10px 10px;
border-right: 1px solid #6E6E6E;
}
.sdc_header ul > li > a {
text-decoration: none;
color: #FFF7AA;
}
And now, our JavaScript file to export the component:
src/Components/SDC_Menu.js
import $ from 'jquery';
import template from './SDC_Menu.html';
import Mustache from 'mustache';
import './SDC_Menu.css';
export default class SDC_Menu {
constructor() {
}
render() {
// Render our button
$('header').append(
Mustache.render(template)
);
}
}
Now we can add the code needed to render the SDC menu when the “sdc_product” class is present.
/index.js
import $ from 'jquery';
const Header = require('./Components/Header').default;
const header = new Header();
header.render();
if (document.querySelectorAll('.sdc_product').length) {
require.ensure([], () => {
const SDC_Menu = require('./Components/SDC_Menu').default;
const sdc_header = new SDC_Menu();
sdc_header.render();
});
}
Everything in the require.ensure callback would be split into a chunk – a separate bundle that Webpack will load only when needed, through an AJAX request. So if we execute the webpack
command to apply the changes, we will see something like this:
The main bundle, bundle.js, contains the header with the company logo and 1.bundle.js is a chunk with the code needed to render the SDC menu. It is only loaded in the sdc.html page but not in the index.html page, as we can see below:
index.html
sdc.html
Now, we just need to repeat the same process to add the ME page with its own menu.
scr/Components/ME_Menu.html
<div class="me_header">
<ul>
<li class="questions"><a href="http://www.genuitec.com/products/myeclipse/learning-center/#contactus">Questions?</a></li>
<li class="logoProduct"><a href="http://www.genuitec.com/products/myeclipse/">SDC IDE</a></li>
<li><a href="http://www.genuitec.com/products/myeclipse/features/">Features</a></li>
<li><a href="http://www.genuitec.com/products/myeclipse/buy/">Buy</a></li>
<li><a href="http://www.genuitec.com/products/myeclipse/download/">Download</a></li>
<li><a href="http://www.genuitec.com/products/myeclipse/learning-center/">Learning Center</a></li>
<li><a href="https://www.genuitec.com/forums/topics/myeclipse/">Forums</a></li>
</ul>
</div>
src/Components/ME_Menu.css
.me_header {
background: #8ECFF9;
padding: 10px 0px;
}
.me_header ul {
margin: 0;
}
.me_header ul > li{
display: inline;
padding: 10px 10px;
border-right: 1px solid #498DB9;
}
.me_header ul > li > a {
text-decoration: none;
color: #333;
}
src/Components/ME_Menu.js
import $ from 'jquery';
import template from './ME_Menu.html';
import Mustache from 'mustache';
import './ME_Menu.css';
export default class ME_Menu {
constructor() {
}
render() {
// Render our button
$('header').append(
Mustache.render(template)
);
}
}
And append a new require.ensure
in the index.js file to create a chunk for the resources of this page:
/index.js
import $ from 'jquery';
const Header = require('./Components/Header').default;
const header = new Header();
header.render();
if (document.querySelectorAll('.sdc_product').length) {
require.ensure([], () => {
const SDC_Menu = require('./Components/SDC_Menu').default;
const sdc_header = new SDC_Menu();
sdc_header.render();
});
}
if (document.querySelectorAll('.me_product').length) {
require.ensure([], () => {
const ME_Menu = require('./Components/ME_Menu').default;
const me_header = new ME_Menu();
me_header.render();
});
}
Now when we execute webpack we see a new bundle named 2.bundle.js that will be loaded in the me.html page:
me.html
Profiler
Although our project is small, it will grow as we add more functionality. There is a tool to analyze our build and give us hints about how to optimize its size and performance.
We can generate the required JSON file by running:
webpack --profile --json > stats.json
and then import in the tool.
Attachment
project.zip—Sample project
Let Us Hear from You!
If you have any comments or questions, we would love to hear from you @MyEclipseIDE on twitter or via the MyEclipse forum. Happy coding!
If you’re not already subscribing to our blogs, why not do it today?