Webpack

Param Singh
Agenda
-
Why Webpack?
- Installation
- Webpack Internals
- Configuration Walkthrough
- Authoring Loaders & Plugins
- Prod Optimisation
- Chunking & Lazy Loading
- Caching using Hashing
- Dead code elimination using Tree Shaking
- Offline first PWA using Service Workers
- Dev Environment Setup
- Webpack Dev Server
- Hot Module Replacement (HMR)
- Publishing as library
Webpack?
-
Webpack is a bundling tool that puts all of your assets, including Javascript, images, fonts, and CSS, in a dependency graph.

History
-
In the early days, we "managed" Javascript dependencies by including files in a specific order:
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="dist/lib/timepicker/jquery.timepicker.js"></script>
<script src="dist/lib/jquery.datepicker.js"></script>
<script src="main.js"></script>
- Too Slow - Excess HTTP requests
// build-script.js
var scripts = [
'jquery.min.js',
'jquery.some.plugin.js',
'main.js'
].concat().uglify().writeTo('bundle.js');
// Everything our app needs!
<script src="bundle.js"></script>
- Still relies on manual ordering of concatenated files
- Even worse, the code could only communicate through global variables
Yuck! 😖
How NodeJS solves it?
- Now we use CommonJS or ES6 modules to put our Javascript in a true dependency graph.
- We make small files that explicitly describe what they need. It's a Good Thing!
//math.js
module.exports = {
sum: function(a, b) {
return a + b;
},
cube: function(a) {
return a * a * a;
},
multiple: function(a, b) {
return a * b;
}
};
//index.js
const { cube, sum } = require('./math.js');
console.log(cube(3), sum(3, 2));
-
C#
using static System.Console;
using static System.Math;
-
PHP
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
-
Java
import java.util.Calendar;
import java.util.Date;
-
TypeScript
import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();
-
Need this modular goodness for UI development
- But sadly, browsers don't understand require or import statements.😔

- <script type="module"> is a way out
- But Browser support is poor
- Only relative imports are allowed
- Too many HTTP requests
- Can't import other static files like images, fonts etc
- UglifyJS Minification doesn't work with ES6


Webpack to the rescue
Not sold yet?
- Bundling is not going away any time sooner.
- Dead code elimination using Tree shaking can't be abandoned.
- Can't ship thousands of tiny files to the client.
But what about Browserify (and Friends...)?
- Browserify was born to make your Node code in the browser.
- Till date it only supports the node flavour of commons
- Separate packages for watching and bundle splitting
- Can't load static assets in JS

import gulp from "gulp";
import browserify from "browserify";
import uglify from "gulp-uglify";
import source from "vinyl-source-stream";
import buffer from "vinyl-buffer";
import sourcemaps from 'gulp-sourcemaps';
gulp.task('default', () => {
browserify({
entries: 'src/utils.js',
debug: true
})
.bundle()
.pipe(source('utils.min.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('./maps'))
.pipe(gulp.dest('./dist'));
});


- Configuration-based systems are preferable to imperative systems if they make the right assumptions about your goals up front.
- Build tooling shouldn’t require a custom build from the ground up.
- It should provide customization points that allow you to handle the few things that make you truly unique.
So many choices
In the land of JavaScript, no one is king for long.

#SurvivalOfTheFittest ~ Charles Darwin
Webpack Features
- Write future ready code today
- Zero Configuration Bundling with reasonable defaults
- Build code for multiple targets - Web, Node, WebWorker
- Support for all popular module systems - ES6, AMD, CommonJS, CommonJS2, UMD.
- Development tools like Hot reloading, dev server
- Code splitting and on demand loading of chunks.
- Caching modules access internally using Hashing.
- Lot of prod ready optimisations.
- Flexibility over bundling using Plugins and Loaders.
- Import files other than JavaScript into your code.
import logo from 'assets/logo.png';
img.src = logo;
Installation
- To install the latest release or a specific version, run one of the following commands:
npm install --save-dev webpack
npm install --save-dev webpack@<version>
- If you're using webpack v4 or later, you'll need to install a CLI.
npm install --save-dev webpack-cli
Internals - How it works ⚙️
1. Starts from the entry point './src/index.js'.
2. Parses the file/modules recursively and wraps them in a function body as




index.js
chunkA.js
3. Hacks the exports object and have it populated with the exported members from that file.
4. Assign every module and id as it's path and uses it to import other modules.
__webpack_require__ function ⚙️
1. It's called for every module while traversing the dependency tree.
2. Keeps a cached registry as installedModules and returns from there if the module of the moduleId passed is already loaded.
3. If not, it creates a module object with keys as id, loaded flag and its exported members.
4. Calls the wrapper function of that module with the params passed.
5. Flag the given module as loaded.
6. Return all the exported members of that module.

Webpack Configuration
Basic Webpack configuration
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
target: 'web',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['babel/preset-env']
}
}
]
},
]
},
plugins: [
new HtmlWebpackPlugin()
]
};
Entry
entry: string | [string] | object { <key>: string | [string] }
entry: './src/index.js'
// or
entry: {
home: "./home.js",
about: "./about.js",
contact: "./contact.js"
}
//or
entry: {
vendor: [
'moment',
'lodash',
'react',
'react-dom'
],
main: './client.js'
}
// or
entry: [
'polyfills',
'./src/index.js'
]
Context
context: path.resolve(__dirname, 'src')
// Use this
entry: './index.js',
// Instead of
entry: './src/index.js',
The base directory, an absolute path, for resolving entry points and loaders from configuration.
Output
path: path.resolve(__dirname, 'dist')
Contains set of options instructing webpack on how and where it should output your bundles, assets and anything else.
output.path
filename: '[name].bundle.js' | '[name]-[id].bundle.[hash].js'
output.filename
libraryTarget: 'umd' | 'amd' | 'commonjs' | 'var' | 'global', 'window',
output.libraryTarget
library: 'Dandelion'
output.library
publicPath: "https://cdn.example.com/assets/" | "/assets/"
output.publicPath
Module & Loaders
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react']
}
}
},
{
test: /\.scss$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "sass-loader" // compiles Sass to CSS
}]
}
]
}
These options determine how the different types of modules within a project will be treated.
Out of the box, webpack only understands JavaScript files. Loaders allow webpack to process other types of files and converting them into valid modules that can be consumed by your application and added to the dependency graph.
Writing a Loader
module.exports = function (content) {
const result = content.replace(/\/\/.*\n/g, '');
this.callback(null, content);
}
- A loader is just a JavaScript module that exports a function
- It's called by the webpack loader runner.
- Result of the previous loader is passed to the next.
- this context is filled by useful methods and properties for use.
module.exports = function(content, map, meta) {
var callback = this.async();
someAsyncOperation(content, function(err, result) {
if (err) return callback(err);
callback(null, result, map, meta);
});
};
Async Loader
Loader for removing comments
Plugins
plugins: [
new HtmlWebpackPlugin({ title: 'My Page Title' }),
new ManifestPlugin(),
new CleanWebpackPlugin(['dist']),
]
- The plugins option is used to customize the webpack build process in a variety of ways
- While loaders operate transforms on single files, plugins operate across larger chunks of code.
- We can tap into the webpack compilation process.
- A plugin is able to hook into key events that are fired throughout each compilation.
Writing a Plugin
- Create a function with an apply method on it's prototype.
- Specify the event hook on which to bind itself.
- Invoke the webpack provided callback after the functionality is complete.


Externals
externals : {
react: 'react'
}
// or
externals : {
lodash : {
commonjs: "lodash",
amd: "lodash",
root: "_" // indicates global variable
}
}
// or
externals : {
subtract : {
root: ["math", "subtract"]
}
}
The externals configuration option provides a way of excluding dependencies from the output bundles.
Instead, the created bundle relies on that dependency to be present in the consumer's environment
Mode
- Mode configuration option tells webpack to use its built-in optimizations accordingly.
- Built in webpack plugins and optimisations are automatically applied based on the mode specified.
module.exports = {
mode: 'production' | 'development' | 'none'
};


Production Optimisations
Chunking & Lazy Loading
There are three general approaches to code splitting available:
- Entry Points: Manually split code using entry configuration.
- Prevent Duplication: Use the SplitChunks to dedupe and split chunks.
- Dynamic Imports: Split code via inline function calls within modules.




{
"plugins": ["syntax-dynamic-import"]
}
.babelrc
Caching & Hashing
- Browser caches the static resources.
- Hence, any changes to our app.bundle.js won't be taken up by the browser as it takes the cached copy of the resource.
- We have to resort to techniques like cache busting for it
- With webpack we can make a content based hash for a file as
application.js?build=1
application.css?build=1
// webpack.config.js
module.exports = {
...
output: {
...
filename: '[name].[chunkhash].js'
}
}
- Boilerplate across the chunks can be extracted into a common chunk called manifest
- Module hashes changes due to several reasons. Use HashedModuleIdsPlugin to keep the hashes consistent for long term caching.
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
optimization: {
runtimeChunk: {
name: "manifest",
},
},
Tree Shaking
- Dead code elimination.
- Code that is exported by some module which is not used in any other module is dropped by webpack.
- Configure mode to 'production'
- Disable the module transformation by babel
- ES6 modules can be statically analyzed by bundlers such as Webpack and Rollup.
- Converting them to CommonJS won't help webpack to analyze and remove the dead code.
Caveats

Dev Setup
Webpack Dev Server
Provides you with a simple web server and the ability to use live reloading

Hot Module Replacement (HMR)
Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running, without a full reload. This can significantly speed up development in a few ways:
- Retain application state which is lost during a full reload.
- Save valuable development time by only updating what's changed.
- Tweak styling faster -- almost comparable to changing styles in the browser's debugger.



Bundle Analysis


const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
if (process.env.WEBPACK_ANALYZE) {
config.plugins.push(new BundleAnalyzerPlugin());
}
Publishing Library
Publishing as an NPM Library



Questions?
Thank You!
@paramsingh_66174

Webpack
By Param Singh
Webpack
- 947