Battlefy
Published in

Battlefy

Demystifying Webpack

Webapp bundlers like Webpack are often categorized as dark magic as few have spent the time to understand it. The fact that Webpack has the ability to transform every aspect of a webapp, I would argue that if one doesn’t understand Webpack, then they don’t know the HTML/CSS/JavaScript they wrote is correct or not.

But doesn’t deploying the build to a staging environment and testing it there prove it is correct? Most of the time sure, but what happens when the Webpack build goes wrong? Do you even know what symptoms point to a failure with Webpack? If not, you’ll end up with hacks instead of fixing the root cause.

What is Webpack even doing? What problem does it solve?

Road to Webapps

Before Webapps, everything was just HTML pages directly linked to each other and served by an HTTP server. This worked well enough for small static sites, but it didn’t scale.

Then we moved to dynamically generated HTML, which scaled a bit better as we could use a backend language and inject data as we generated the HTML. But this had its limits as well. We no longer could use a CDN and generating HTML was error-prone.

Webapps, in particular single-page applications, were invented to make things easier. We can now build real applications in the browser and have all the assets hosted on a CDN again!

Webapp problems

Early Webapps were pretty straightforward. One wrote JavaScript and loaded it into HTML with a <script> tag. If one had libraries like lodash, it would just be loaded before the main script and accessible via the window global.

<body>
<div id="root"></div>
<!-- lodash will assign itself to window._ -->
<script type="text/javascript" src="js/lodash.js"></script>

<!-- app uses window._ -->
<script type="text/javascript" src="js/app.js"></script>
</body>

If libraries have dependencies of their own, then one would need to carefully order the scripts to ensure everything loaded correctly. This gets complicated quickly as transitive dependencies form a tree and we can only define a linear load order.

This was manageable if the library was written for the browser, but what about npm packages written for Node.js? There are many useful libraries that would work perfectly fine in the browser if it weren’t for the peaky requires.

This is the primary problem Webpack solves. It allows one to use CommonJS or Modules to sort out the dependency tree and concatenates everything into a single bundle. Webpack maintains the isolation of modules using IIFEs.

Input source

Here is a simple example where our app.js imports is-even, but is-even depends on is-odd, which in turn depends on is-number.

// npm is-number
export default (n) => Number.isInteger(n);
// npm is-odd
import isNumber from 'is-number';
export default (n) => isNumber(n) && n % 2 === 1;
// npm is-even
import isOdd from 'is-odd';
export default (n) => !isOdd(n);
// app.js
import isEven from 'is-even';
if (isEven(42)) {
throw new Error('cant even');
}

Using Webpack, it can combine all these files into a single bundle.js, which we can then load into our HTML.

Output bundle.js

The raw Webpack output isn’t intended for human consumption. I’ve tried to clean it up a bit and highlighted the source from the input files. Notice the import/export has been replaced with plain functions.

But there is more to webapps than just dependencies. How does one ensure the JavaScript written is supported on the desired browsers/devices? Heck, how does one solve this problem when importing libraries that could be written in any flavour of JavaScript or even TypeScript?

Moar Webapps, moar problems

ECMAScript (the formal standard behind JavaScript), evolves over time. In fact, it has seen far more radical changes than most programming languages. In a short decade, JavaScript has added let, const, Promise, Arrow functions, async functions, Modules, WebAssembly and even standardized left-pad. While some deride the upkeep required to stay up-to-date, in my opinion, this is what makes JavaScript interesting and fresh.

But what about browser/device support? There’s just no way Apple, Google, Microsoft and Mozilla are going to perfectly implement and release all these features at the same time across all their browsers/devices. (Well, except they did that one time for the launch of WebAssembly.)

In general, feature support is all over the place. We constantly use https://caniuse.com to see if we can use certain JavaScript syntax/APIs natively. How do we manage this chaos?

This is where Babel comes into the picture. Babel is a transpiler and is usually used to transpile modern JavaScript down to early versions that are supported by more browsers/devices.

Webpack has been integrated with Babel, which allows one to configure the Webpack output bundle to be any JavaScript version. Typically the target JavaScript version is ES5, but that is now over a decade old! If one doesn’t need IE 11 support, then ES6 is much more suitable. In general, choosing a more modern version of JavaScript results in a smaller bundle (less code is rewritten by Babel) but at the cost of lower browser/device support.

But how does one even think about which browsers/devices should be supported? Ultimately this is a business decision that needs to be balanced with the cost of supporting older browsers/devices to the business value it returns.

Let’s look at a simpler example and narrow the topic to mobile Apple devices. Here is the global market share by iOS version from May to Oct 2021. We can see iOS 15 was released in October.

Source https://gs.statcounter.com/ios-version-market-share/mobile-tablet/worldwide/

There is a clear drop-off at iOS 11 and older. We can then look up which devices had their end-of-life at iOS 11, and interestingly there were none. This means all devices that ran on iOS 11 were upgradable to iOS 12. It is actually iOS 10 that had a cut-off with iPad 4 and iPhone 5.

This means if we target iOS 11 and newer, that will achieve a 98% market share. But shouldn’t we try to hit 100%? Isn’t it unfair to leave behind those who cannot afford to upgrade? That is a value judgement the business would need to make. It could be the case that supporting that last 2% eats up the vast majority of development cost and be even more unfair to 98% of customers. It could also be the case the majority of the profits are made in that last 2%. This is completely dependent on the business.

We’ll assume a decision has been made to support iOS 11, how do we implement this? We can see what JavaScript is natively supported with iOS 11, but what we really need to do is configure Babel to transpile everything newer and unsupported down to iOS 11.

Babel has a rich ecosystem of plugins that allows one to exactly tune what to transpile, but fortunately, we don’t need to bother with all that. Babel preset-env integrates with browserslist which allows us to target iOS 11 directly. This will load the appropriate Babel plugins needed to transpile down to iOS 11.

Now we have all the puzzles pieces to get Webpack to transpile our JavaScript source down to iOS 11. The webpack.config.js snippet to achieve this would be,

{
...
module: {
rules: [
{
resource: {
test: /.+\.js$/,
},
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: 'ios 11'
}]
]
}
}
}
]
}
...
}

Such wow, many cool

This is just a surface-level introduction to Webpack. The vast majority of webapps use Webpack to solve the two major problems of

  1. managing and bundling dependencies
  2. transpiling sources to increase browser/device support

But Webpack can do so much more. Webpack loaders support importing of CSS and even transforming CSS to add browser prefixes. And there are additional Babel presets to enable support for React with JSX.

Adding TypeScript support can be done either as a Webpack loader or Babel preset, which is simultaneously extremely frustrating having more than one way of doing things but also liberating being able to make the right tradeoff.

For production builds one needs to carefully configure the minification, code splitting and output filenames. But that is a topic for another time.

Do you want to truly understand how to build webapps simply? You’re in luck, Battlefy is hiring.

--

--

--

Battlefy is the simplest way to start, manage, and find esports tournaments.

Recommended from Medium

Add Animations to Shapes in PowerPoint using Java

#4. The World of Arrays

Should I learn React Or AngularJS?

Rallying Behind the Next Wave of Angular Developers

Optional Chaining — Using it already

Form Validation in a Vue 3 App with Vee-Validate 4 — Error Messages and Form Values

JavaScript Problems — Update the Address Bar, Running Command Line Programs

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ronald Chen

Ronald Chen

Principal developer at Battlefy

More from Medium

From TypeScript Hater to TypeScript Convinced User

How to think about TypeScript

Use These FP Techniques to Create Better Loops in JavaScript

Declarative Components, Imperative Objects