Modern approach of JavaScript bundling with Webpack

Published: Feb 13, 2017

As a Front-End developer I’ve worked on various e-commerce web sites. In the beginning I included every JavaScript file using separate script tags. But after some hard lessons learned about Front-End performance I started to combine and minify web site’s scripts. Also referencing vendor and framework scripts from CDN servers became a common practice.

I must admit it worked well for some time. But while transforming from pure Front-End to Full-Stack-ish developer. And learning the good parts of C# and PHP back-end development. Especialy how the dependencies are included in Back-End code. I started to face the cons of this approach more often. The one thing I don’t particularly like is the magic behind the scenes. When you include some library e.g jQuery in markup as separate <script>. Then write some web site’s JavasScript code depending on jQuery API. And 'voilà', even there’s no include of jQuery in the code it just works “automagically”.

The “old” way

An outdated approach of JavaScript file inclusion

The image above illustrates the effect of “automagic”. But as we know there’s nothing magic. It’s the way we used to reference dependencies necessary to make our web sites work.

From my point of view this approach has two main flaws. First the global namespace gets polluted with a lot of global variables. Starting from the 3rd party libraries like jQuery, Angular, lodash as well as 3rd party plugins. And ending with our own utility, helper, service etc. scripts. Because they're added globally the order of loading and execution is important.

Secondly we are hiding the dependencies from our main script. We must assume the necessary scripts have been loaded before and we are safe to use their API. And time by time we have to switch context to make sure if script is included and available to us or add extra scripts in the markup file. Finally to see if the application, website or view is working we must launch it and verify everything is loaded and running correctly.

A more modern approach

With the rise of the web apps and modern Front-End frameworks. Eventually JavaScript development process started to shift and we've seen great changes in the way we're developing our code. One of the processes also has seen some change is the static file bundling.

A task once done by server side technologies is taken away from the Back-End frameworks. And now performed using tools built on modern front-end technologies. For example, one can bundle all project's JavaScript using Microsoft.AspNet.Optimization package in ASP.NET prior version 5

// App_StartBundleConfig.cs
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle(\"~/Scripts/scripts.js\").Include(
\"~/Scripts/lib/jquery-3.1.1.js\",
\"~/Scripts/lib/timepicker/jquery.timepicker.js\",
\"~/Scripts/lib/jquery.datetimepicker.js\",
\"~/Scripts/textExtensions.js\",
\"~/Scripts/numberExtensions.js\",
\"~/Scripts/app.js\"));
}
<!-- And then you just render the bundle in _Layout.cshtml -->
@Scripts.Render(“~/Scripts/scripts.js”)
</body>

You can do the same in ASP.NET 5 using <environment> tag from Microsoft.AspNet.Mvc.TagHelpers

<!-- _Layout.cshtml -->
<environment names=\"Development\">
<script src=\"~/Scripts/lib/jquery-3.1.1.js\"></script>
<script src=\"~/Scripts/lib/timepicker/jquery.timepicker.js\"></script>
<script src=\"~/Scripts/lib/jquery.datetimepicker.js\"></script>
<script src=\"~/Scripts/textExtensions.js\"></script>
<script src=\"~/Scripts/numberExtensions.js\"></script>
<script src=\"~/Scripts/app.js\"></script>
</environment>
<environment names=\"Staging,Production\">
<script src=\"~/Scripts/scripts.js\" asp-append-version=\"true\"></script>
</environment>

In examples above we can see how a certain back-end framework handles the bundling. But eventually everything that can be done using JavaScript will be done by using JavaScript. And as there are already many tools to achieve this, let's take a look on one of them called Webpack. This is one of the tools which has gained a lot of popularity lately

Webpack

https://webpack.js.org/

Webpack is a modern static file bundling tool. In case of JavaScript file bundling it helps to eliminate the implicit dependency on sorted <script> tags in our markup. Instead of including many separate scripts we include single or few bundles using the same <script> tag.

To do so we should first create those bundles. And to create bundle we need to configure Webpack. Of course Webpack should be installed on the machine for example by command npm install webpack --save-dev.

In it's bare minimum all that Webpack needs is an entry point and output. It can then be called simply by executing command

./node_modules/.bin/webpack app/entry.js dist/output.js

In this case Webpack will load entry.js and will look up for import or require keywords within it to determine whether it should load more dependencies. If dependencies are found it will load them recursively and also include them in the output file.

JavaScript modules

As noted in previous section the script file can include dependencies referenced by import or require.

// CommonJS
require ('lodash')
console.log(_);
// ECMA6
import 'lodash'
console.log(_);
// Notice currently you need to transpile ECMA6 import using for example Babel to make it run in browsers

Which is similar as referencing classes in .NET Framework by using

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;

or like in Symfony 2 framework

use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;

This is just to point the fact that one can use the feature of referencing dependencies within a JavaScript file. With default Webpack configs it will bundle an output script which won’t be compatible with current browsers. And ass noted in the comments we need to process the script files containing imports or requires. This can be done using Babel JavaScript transformer whose task is to take JavaScript written in ECMA6 and translate it to working ECMA5 JavaScript. You can find more about JavaScript import and export on Mozzila Developer Network.

Bundling JavaScript The Webpack way

You can bundle your JavaScript using the CLI command by providing an entry file and output path. Webpack will automatically resolve all dependencies from import and requite. And bundle them into single output together with your app’s script.

But that’s just the bare minimum it can do. To extend the functionality we can create a Webpack’s config file and configure various tasks the bundler performs when processing JavaScript.

Configuration

First we need to create a webpack.config.js file and require webpack from node_modules.

//webpack.config.js
var webpack = require('webpack');
module.exports = {
entry: {
entry: __dirname + '/entry.js'
},
output: {
filename: '[name].bundle.js'
}

}

Here we have provided the entry file as entry.js and prefixed the path of current directory where Webpack is executed. Also provided the output path where to put the content of bundled JavaScript. Note the [name] prefix of the file. This instructs Webpack to concatenate the entry file name with  .bundle.js ending. Now by executing a CLI command

./node_modules/.bin/webpack

it will create an entry.bundle.js file in the current module’s directory.

Loaders

As I wrote earlier if you use import to reference JavaScript dependencies in your script you have to transpile it from ECMA6 to ECMA5 script using Babel. Webpack can do this on the fly by utilizing it’s loaders feature. To make it work we must first install necessary node modules npm install babel-core babel-loader babel-preset-es2015 and update webpack.config.js with modules section.

// webpack.config.js
var webpack = require('webpack')
module.exports = {
entry: {
entry: __dirname + '/entry.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
}
]
},

}

In modules section we can set many loaders which are applied to JavaScript at processing time. Notice we have set that all files ending with *.js, excluding node_modules, should be processed through babel-loader. Thus it will generate ECMA5 compatible code as output. So now if we would add for example a dependency to helloWorld.js in the entry.js the Webpack would be able to generate working code.

// helloWorld.js
export const helloWorld = 'Hello world from imported JS!';
// entry.js
import {helloWorld} from './helloWorld'
console.log(helloWorld); // Will print 'Hello world from imported JS!' in browser's console

Plugins

So we have seen the entry, output and modules configuration in action. But there is one more section for base configuration which is worth look at. It’s plugins. Simply said a plugin is the same to bundled JavaScript as loader is to a single JavaScript dependency. Plugins allow us to perform a various tasks on bundles. For example extracting duplicate code chunks into separate files or uglify the output.

// webpack.config.js
var webpack = require('webpack')
module.exports = {
entry: {
entry: __dirname + '/entry.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin()
]

}

As you can see we have added a new section to the Webpack config plugins. It can contain many plugins to be executed on the bundled JavaScript. By itself Webpack provide a list of useful plugins. But you can also build your own for your specific reasons.

Conclusion

This post provides a short introduction and examples how to start to use Webpack. Thus improve the JavaScript development process and gain more control over dependencies your script is relying on. Before you go here are a short summary of pros and cons switching over to Webpack.

Cons:

  • A need to install an extra tool when there exists a feature in framework
  • Developer must define all entry points (one per page or view) in configuration
  • JavaScript must me modular both application's and 3rd party's

Pros:

  • More control over dependencies as view imports only scripts it needs to work
  • Usage of modern JavaScript (ES6 features)
  • Webpack can analyze the content of JavaScript on compile time. Thus providing more info for optimization
  • Referencing classes is similar like doing it in C# or PHP

I hope you will find this useful and if you’ve noticed some flaws don’t hesitate and let me know.