Build tools have become a necessary component in the workflow for modern web applications. I have previously covered the basics of what build tools can do for you to show how builds help with scripting, automation, and eliminating complexity. Taking those into consideration, I’m going to provide a closer look at some of the more popular build tools and how they might make sense for your projects.
This post isn’t meant to be an exhaustive list. Rather, it’s meant to provide suggestions to help you get started in researching how different build tools improve the development workflow. While reading through this comparison on build tools you will gain some knowledge to better equip yourself for comparing how these tools line up with your specific needs.
What are we comparing again?
This guide will only look at build tools that have the ability to perform project module bundling, which is the process of stitching dynamic content into modules and bundles into static assets files. This process can be enhanced using scripting, automation, and minification. Not all build tools are created equal and not all build tools can do all of the above. Some tools require more configuration and some are drop-in solutions that do most of what you need out of the box.
It’s likely you’ll have (or develop) a preference for how much you’d like a build tool to do. So rather than choosing one build tool to rule them, this post will cover the advantages, gotchas, and an ideal project profile for each tool.
Tool | Advantages | Gotchas | Ideal project |
---|---|---|---|
Browserify | Simplicity | Development is driven from community plugins only | Small project or prototype |
Webpack | Flexible Configuration and Code Splitting | Config Readability | Medium-Large Web projects |
Rollup.js | Bundling for Shareable Libraries | Modules bundling is already built into browsers | Library or Plugin |
Gulp | Task Runner | Companion tool for a module bundler | Project in need of extra scripting |
npm | Built in to most JavaScript projects | Manual set up | Works with most projects |
Parcel | No config needed | Less documentation available | Medium-Large Web projects |
Microbundle | Small footprint | Less documentation available | Size conscious Library or Plugin |
Browserify
Advantage: Drop dead simple
The catch: Development on the project has slowed down a lot
Ideal project: Projects that are looking to move away heavy usage of script tags and move CommonJS requires instead.
Browserify focuses on the simplicity of getting started and it is a great introduction to module bundling in JavaScript development. Browserify originally came into existence as a way to allow front-end developers to use CommonJS (require statements) in the browser the same way you would in the server render node application. Previously web development used multiple script tags on the page to build modern web apps. This tool browserifies all your JavaScript files into a concatenated (combined and merged) file that can easily be included on the page as a single script tag.
Using Browserify starts with the installation of the CLI. I recommend using npm from the command line.
npm install browserify
Once installed, you can point your JavaScript entry point of your application (most likely your index.js) to a location to start the Browserifying process.
browserify index.js > bundle.js
The result is a bundled version of your JavaScript that can be included in your index.html.
<script src="bundle.js"></script>
Browserify implementation is feature complete and focuses on JavaScript improvements out of the box. To support the bundling of non-JavaScript assets, like CSS or images, there is a healthy list community-created transforms (all named with ify endings, how clever) to source for those solutions. I’m a huge fan of enlisting the open source community to take a project further, but if you’re giving Browserify a try take heed: some transforms haven’t received new updates in more than six months. That being said there is plenty of areas to contribute back to the community by providing plugins to your project’s solutions.
Unlike some other build tools, Browserify doesn’t have a standard config file to maintain. however you can leverage the node package.json to handle more advance configurations. The work of Browserify is inferred through the plugins and what is inside your JavaScript files. For projects that do not constantly need to be updated, this can be a beautiful thing. For projects in need of a lot of tools and optimizations, the missing config file can become a burden because there is no source of truth or place to expose the witchcraft of the build tool.
Consult the Browserify documentation as well as the list of transforms to see it includes everything you need to make your development workflow happy. You can also use this tutorial on how to build a React app with Browserify to see it in action. If simple is what you need then Browserify is something I would consider for your next project.
Webpack
Advantage: Actively-supported project with tons of features out of the box
The catch: Takes a bit of custom configuration to get right
Ideal project: Projects that are looking to stay up to date with latest and greatest changes. Projects also looking to do code splitting should consider webpack as well.
Webpack is a build tool that is built on 4 main concepts: Entry, Output, Plugins, and Loaders. Once you understand the ideas around these concepts, you can get Webpack up and running on a project. Webpack took can feel similar to Browserify in some ways with enhanced features through a community of plugins. Webpack however, comes with more features out of the box with plans to continue to add more and continually rethinking the design of the project.
I wrote previously wrote a guide for getting started with Webpack from scratch and it focusing on the leveraging the Webpack CLI to build a React application. Webpack requires you create a separate config file to support your Webpack build efforts. This file is nothing more than a JavaScript object that Webpack uses to enable and disable features during the build process based on keys and values within the config object.
// example of a webpack.config.js
module.exports = {
entry:'./index.js',
output: {
filename: 'bundle.js'
}
}
Within the config you can identify the entry point of your project as well as the location of where you would like to place your bundle. This makes running the Webpack build simpler since do not need remember specific commands, you just webpack
to create you build.
npm install webpack
webpack
The Webpack config can be a sweet way to approach adding new features and tools to enhance your build process, but like most sweets things, a few additions here and there can cause your config to bloat into to a form that unmanageable. A config that looks unmanageable can be a form where the development team on a project avoids changing or updating the Webpack config for fear of breaking the build due to one too many added to the Webpack config file.
The React team has solved this Webpack problem by abstracting the config away into a hidden script beneath the create-react-app CLI tool. If you take a look at the hidden config, the file has some of the best laid out comments you may ever have seen in a config, but the fact that it needs so many comments makes you question if there is a better way to have such fine tuned configuration without the needed walls of comments to support each decision.
// excerpt from the creat-react-app's webpack config
module.exports = {
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
devtool: 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
entry: [
// We ship a few polyfills by default:
require.resolve('./polyfills'),
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
],
output: {
// Add /* filename */ comments to generated require()s in the output.
pathinfo: true,
// This does not produce a real file. It's just the virtual path that is
// served by WebpackDevServer in development. This is the JS bundle
// containing code from all our entry points, and the Webpack runtime.
filename: 'static/js/bundle.js',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: 'static/js/[name].chunk.js',
// This is the URL that app is served from. We use "/" in development.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
},
// ... there is so much more to this
The Webpack team is actively developing this project and doing their part to clear up the confusion around the config. A lot of the missing features that once needed a Webpack plugin are now included in the library itself, including tree-shaking, uglifying, and even web assembly (WASM) support. The well-written through documentation also helps to make this Webpack as a build tool more approachable and has been consistently maintained since the launch of Webpack 2 (Fall of 2016).
Webpack not only has a focus on module bundling, it includes code splitting as a built-in feature Code splitting is the practice of loading only the content that’s needed, when it is needed by leveraging separate page split bundles base usually on routing. This has the potentially to improve page load speed and the overall browsing experience. Code splitting does however come with a learning curver, one that I have personally not fully learned, but the Webpack team members are trying their best to flatten that curve with webpack.academy.
There are lots of community-built Webpack config boilerplates, including a pretty neat tool called Webpackbin. Webpackbin is a sandbox to build and configure Webpack examples. You can generate links from here which is nice when researching Webpack configurations, as authors tend to post their configs in the sandbox and provide the URL to share.
Webpack is working towards being the batteries included, but some parts sold separately build tool. Webpack can handle almost every concern you have when b web applications these days, but you will also likely need to read the manual (documentation) a lot to get it your build up and running to your liking.
Rollup
Advantage: Built-in features for package management
The catch: You’ll need to make sure your project has implemented the ES6 syntax
Ideal project: Projects looking to use slightly less configuration for the build process and already using the latest ESNext features like ES modules
Rollup is a module bundler for JavaScript which compiles small pieces of code into something larger and more complex. It uses the new the new version of JavaScript’s ES6 module system, instead of previous idiosyncratic solutions such as CommonJS and AMD, to perform the rollup (bundling) of your project. ES6 modules let you freely and seamlessly combine the most useful individual functions from your favorite libraries.
Getting started with Rollup can be done via the command line. Just point your index.js and provide a name for your bundled ouput.
npm install -D rollup
rollup index.js --o bundle.js --f iife
To save us from needing to constantly repeat the same commands, you have the option to add a rollup.config.js file, similar to what we saw in webpack. The same risks about config are jus as valid in ght e
// rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'bundle.js',
format: 'cjs'
}
};
Rollup has gained more popularity with package and open source maintainers, because of built in features for package management as oppose web applications. Some of the features mirror what you could do with Universal Module Definition’s(UMD) and make Rollup a great bridge between the JavaScript’s UMD need and ES6 modules. Because ES6 is the hotness Rollup does not work with CommonJS required files without a plugin. This is only a limitation for older projects that have not yet implemented the ES6 syntax into their workflow. However if you are starting a new project, there is not much in the way of limitation for you.
As of the the Spring of 2017 all major browser support ES6 modules natively, which has Rollup now looking to claim a new competitive advantage as well. Rollup does come with native support native support for tree-shaking, with is the ability to remove unused code out of your project bundle, which you can see in this example from the rollup repl. This is valuable for projects looking for assistance where they can get it with page optimizations.
Though tree-shaking seems like a small feature, consider projects like Momentjs or lodash which are massive projects to begin with. Tree-shaking provides the ability to exclude all the parts of the library out of your bundle and only include the portion of the library you are using.
There is a lot more you can rollup besides trees, so I encourage you consult the Rollup guides for more information how you can leverage tree-shaking and other features in your next project.
Gulp
Advantage: A great tool for connecting the dots between other build tools
The catch: If you need module bundling, this isn’t the build tool for you
Ideal project: Projects that need extra help with scripting features into their build process
Gulp is a tool that brings scripting as a feature to your workflow idea. Of all the tools in the list, Gulp is one I would not use for module bundling, but as a tool to enhance my build process. Gulp is ideal for those developers looking to put the script in JavaScript. A lot of the features missing from Webpack and Browserify can be enhanced with Gulp commands by chaining them together. Gulp commands can either be handwritten in plain JavaScript or leveraged using Gulp’s expansive community of plugins.
You can chain multiple commands together to use Gulp to scripts to your build process. Some common things might be sending images to Cloudinary or compiling JSON files for proving your search with Algolia. I truly believe that this is where Gulp shines and has a number of community built plugins to handles simple to complex scripts for handling things like CVS generation or Image manipulation during the build process.
// example gulp build
gulp.task("build", function(callback) {
runSequence(["css", "js"]);
});
gulp.task("css", () => (
gulp.src("./src/css/*.css")
.pipe(postcss([
cssImport({from: "./src/css/main.css"}),
]))
.pipe(gulp.dest("./dist"))
));
gulp.task("js", (cb) => {
// using webpack ;)
const myConfig = Object.assign({}, webpackConfig);
webpack(myConfig, (err, stats) => {
if (err) throw new gutil.PluginError("webpack", err);
gutil.log("[webpack]", stats.toString({
colors: true,
progress: true
}));
cb();
});
});
Gulp does very well with synchronous scripting as well asynchronous (with plugin) scripting during the build process, but it very likely you will need to use another build tool for optimizations and transforming. I tend to use Gulp for quick scripting but tend to include a separate bundler like Webpack or Browserify to handle the weight of the bundling. This is my personal preference and my attempt to keep Gulp around to do what is great at as a task runner prior to the module bundling. Gulp scripts can get have the same problem Webpack configs suffer from, which is long config with walls of comments, which is what I am avoiding. I recommend trying Gulp for yourself and skimming the API documentation for examples of how you could use it.
npm
Advantage: No extra dependencies needed
The catch: Scripts will need to be manually written or collect
Ideal project: Small projects and prototypes
I cannot mention Gulp scripting as a build tool and not mention npm (Node Package Manager) as a build tool. npm is mostly thought of as a package manager and intermediary to hosting your JavaScript libraries on a CDN. Every build tool I have mentioned so far has leveraged npm to install via the command line. There is definitely more to npm than just package management. One feature that comes with npm, is the ability to run scripts.
The npm-scripts feature can do a lot of what modern build tools can do with less package dependencies and less maintenance overhead. If we took a closer look, every tool we have discussed contain a collection of plain JavaScript files. These same files can be connected using npm scripts and hand written without the need for dependencies.
For small projects and prototypes, starting with npm could be enough and I encourage to you take a look at npm before during your build tool evaluation. As npm Cofounder Laurie Voss explained, “npm is here to get out of your way. It’s not meant to add a bunch of ceremony to your build process. Watch Laurie’s talk from a previous JSConf EU.
The npm project's documentation along with general blog posts on how to use npm as a build tool are great resources for learning more.
Parcel
Advantage: Zero config needed
The catch: Newer project with less documentation to reference
Ideal project: Small projects and prototypes looking to get started quickly
Parcel came out at the end 2017 and gets the privilege of wrapping all the JavaScript points of configuration fatigue in a tiny little package. Parcel removes the complication of build tools and works out of the box with the most popular plugins in the JavaScript space, including babel transforms.
Similar to Browserify there’s also no config file, but there are also no Parcel-specific plugins. Parcel relies on existing JavaScript ecosystem projects like Babel to do the work. Parcel is only the orchestrator. You can also include Babel transforms and plugins in your package.json or .babelrc and Parcel will know to include it in the build process. There is no extra configuration needed which is a very intriguing feature. There is also no need to learn one more library to maintain your project (a.k.a the dreaded JavaScript fatigue).
Getting started with Parcel is similar to the others, but instead of providing an input and and output for the bundle, you just provide the entry in the script.
npm install parcel-bundler
parcel build index.js
Any other feature can be found in the documentation, but spoiler they require you to write modern JavaScript to do so. There is really no magic under the hood of this project. Take a look at this example for getting a React application running using Parcel. As mentioned this project is still fairly new but seems promising. There is already some great documentation available and a
Microbundle
Advantage: Zero config needed with an extremely small footprint
The catch: Newer project with less documentation to reference
Ideal project: Size conscious project looking to be shared as a plugin or addon for other projects
If you have not heard of Parcel, there is a chance you have not heard of Microbundle, the zero-configuration bundler for tiny modules. Microbundle is powered by the before-mentioned Rollup project and aims to take their module bundling to next level by removing the configuration step. Similar to Parcel, it will bundle your project using nothing but the package.json. So be sure to include all the necessary dependencies needed to run your JavaScript and bundle your assets.
npm install microbundle
microbundle
Microbundle will assume you have an index.js if no entry point is provided as an option. It will also create a bundle and minify that same file if no output is provided as well. Not only is bundled version created, a UMD version is also provided as part of the bundling process.
// index.js
function () {
console.log("Hello World")
}
// microbundled into index.m.js
function O(){console.log("FOOOOOOOOOO")}O();
// microbundled into index.umd.js
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){console.log("FOOOOOOOOOO")});
This could is useful for small shareable projects to be embedded into others. Thanks to this post I only now discovering this Microbundle but could see this being useful for the Netlify Identity Widget, which is a project meant to be embedded into larger projects and already being bundled manually into a UMD.
Now go build something
No matter your programming style there is a solution for you and choosing a build tool comes down to what kind of control you want. My attempt was to provide a progression from no configuration to a lot configuration. Despite the amount of choices, all build tools work with Netlify and can be a useful part of maintaining your application, so try a few and see which one works for you.
If you’re a big fan of a tool that was not listed here, please leave a comment and let me know.