Javascript build systems
Building an enterprise JavaScript application that deals with complex business logic may sound absurd to some. (Since JavaScript is an interpreted language and interpreters are readily available in all browsers.) However, in this day and age, focusing on building complex applications with transpiling tooling such as Babel and Typescript, linting (ESLint/TSLint), automated tasks, and compression/obfuscation routines, the demand to automate these tasks is there. Further, automation can help with rigorous testing, such as unit tests and end-to-end scenario tests that help catch potential production issues. It is reasonable to automate all of this and make it as quick and straightforward as possible. A variety of tooling has popped up to make your life easier. (Which is where the power of Webpack as a build system comes to play!)
Be cautious, though, as these extra tools add complexity to the system. Build systems should not be used as a “standard” but rather a means to make applications more maintainable when many engineers contribute.
A few popular JavaScript build tools are Grunt, Gulp, and Webpack. This article’s focus is on Webpack because it does an excellent job at everything you will likely need from a build system through available open-source plugins. Gulp and Grunt provide more capability and control over a build system. You can even use these to run Webpack on top of them, but in most cases, all you will need is Webpack. Webpack also has a dev server built-in that makes development relatively quick and easy to set up a local test server for deploying your web app.
Why understanding how bundling works matters
At the time of this writing, Webpack version 4 is the latest stable release. It is currently the most popular build system. (Not to say that Gulp and Grunt don’t still have active use cases.) Webpack’s primary purpose is to be a highly configurable, modular, and a bundle builder. Using Webpack to handle bundle optimization can be a great way to drive performance optimization in your application. (Especially if you want to utilize lazy loading.) By controlling the JavaScript interpreters when to download and parse your code, you will benefit from much faster load times.
For example, when you build your web front-end client, you will likely use third-party libraries combined with your source code. All of this code needs to be downloaded when a user visited your website. Some bits of the logic could be inlined in your index.html page, but you will typically break out the bulk of it to include statements that will download the scripts. This work is all synchronous, so the user must sit and wait for:
- The third-party libraries and your app code to download
- All this code to compile
- All this code to interpret
- Then, the JavaScript code begins to execute and render to the local browser document
This whole process could easily take 10+ seconds, leading the user to think twice about using this “slow application.” Let’s face it, JavaScript front ends are known for being slow because of this process.
Benefits of Webpack as a build system
Webpack empowers you to dig into your application’s bundle and “code split” like a maniac. In addition to all of the automated tasking, I mentioned before. You will eventually find yourself obsessed with splitting these bundles down to the bare minimum to optimize your initialization speeds. Webpack does this by building a dependency graph and pulling in the required resources based on the configuration set in the webpack.config.
Benefits of Webpack as a build system:
- Designating the bundles to “code split”
- Minification of code (saves bundle size)
- Tree shaking (or dead code removal)
- Moves common code to bundles into another bundle that can be shared (instead of duplicating across bundles)
- “And more,”… (Webpack has a highly configurable plugin environment that many developers are contributing to and expanding on)
Using Webpack to achieve performance goals
Start by identifying the various entry points to your app (such as the components/pages that require routing) and bundle those entry points together. Split out anything you don’t need to get to the initial first render and activity to the user. Lazy load everything else the user doesn’t need until they want to see it!
For example, any components or UI that takes user interaction to see can also be lazy-loaded (for instance, drop-downs, modals, popovers, and other clickable menus). Also, consider lazy loading all of your code that retrieves content for the UI. Instead, focus on just rendering a base UI without content. Then load the content in afterward. Get that first render, display a loading wheel, and then get your bundle for content retrieval.
If you continue to code split and lazy load, you will find yourself with many Webpack bundles. These bundles could then be pre-fetched in the background if you so desire. This way of loading allows the bundle to see “now” as small and streamlined as possible. That bundle will download, parse, interpret faster than ever!
How fast is fast?
I’d aim to get that first render in under a second. (Which will be challenging but doable with the aggressive trimming mentioned above.) I will discuss Webpack plugins and configuration (and various other toolings to analyze bundles) in future articles. Let me know if there is anything specific I can focus on, and I’ll prioritize blogs there.
A general rule of thumb for performance “speeds” is:
- 200 ms is fast
- 500 ms is tolerable
- anything above one sec+ should use a loading wheel
- Anything more than 5 seconds will likely lose user attention. One second is an excellent benchmark to strive for to start.
Some interesting supporting material:
- Here is a good article that strongly argues why you should aim for fast loading speeds.
- Here is an infographic that portrays how Walmart found increasing performance directly influenced user conversion rate success.
If you enjoyed this article, be sure to follow up on learning how to use Webpack effectively for performance gains.