Tuesday, December 10, 2019

A Guide to Dependency Management in Front-End Development

A Guide to Dependency Management in Front-End Development

Package managers, task runners, and module bundlers are build tools that you can use for dependency management. In this article, we’ll look into how they are different from each other and what you can achieve with them in front-end development.

What is Dependency Management?

Dependency management is the process of automating the installation, updating, configuration, and deletion of libraries, packages, and tools that an application depends on. Each programming language has its own dependency management tools; from Java, to PHP, to JavaScript. 

We’re going to look into the high-level landscape of dependency management in front-end development. Respectively, we’ll talk about the following groups of build tools:

  1. Package managers (npm, Yarn, pnpm).
  2. Task runners (Grunt, Gulp)–Although these are not dependency management tools, we’ll see in short how they differ from them.
  3. JavaScript module loaders (RequireJS, Browserify).
  4. Static module bundlers (Webpack, Parcel).

Please note that this article doesn’t provide detailed reviews of the specific tools but rather intends to show how the different types of dependency management tools relate to each other.

Dependency Management on the Front-End

On the front-end, we use JavaScript dependency management tools to keep dependencies under control. Most of these tools are Node.js modules that can be installed with npm—the default package manager for Node.js, a runtime environment that lets you execute scripts outside of the web browser.

With the progress of front-end development, dependency management has become increasingly complicated. There are many competing tools in the market, that perform similar tasks but also differ in some ways. Sometimes, dependency management tools also overlap in functionality with other build tools such as automated task runners.

Bower (R.I.P.)

Let’s start with some sad news that shows how fast dependency management is changing. If you follow front-end trends, you have surely heard about the deprecation of Bower, the once-popular front-end package manager. Although Bower is still maintained (so you don’t need to worry about your existing projects), it’s not recommended for new projects. 

In short, Bower lost the competition against other popular dependency management solutions such as: 

  • the powerful combination of npm and Browserify, 
  • performance-optimized package managers such as Yarn and pnpm, 
  • and automated static module bundlers such as Webpack and Parcel. 

As a result, now the Bower team also recommends Yarn, Webpack, and Parcel, and even helps in migrating away from Bower.

RIP Bower

Package Managers

Package managers allow you to load all dependencies (packages, libraries, modules) your app relies on in the right order. They also let you manage, update, modify, and delete packages as the needs of your application are changing. Most package managers keep track of dependencies with the help of a dependency tree.

npm

The npm package manager serves as the basis of most other front-end dependency management tools. This is because most of them are Node.js packages that you can install using npm. 

It’s easy to get started with npm, as it’s packaged with Node.js that you can install on your operating system by downloading the appropriate installer (Windows, macOS, Linux) from Node.js’ Download page. Once Node.js is installed on your system, you can run npm from your command line interface.

Besides being a package manager, npm is also a registry of open-source Node packages. Developers from all over the world can upload npm packages that you can add to your application with a couple of commands. This is how front-end developers have easy access to advanced tools such as PostCSS and the Grunt task runner.

Grunt package manager

Another important thing to know about npm is that it’s tied to the Node.js ecosystem. As a result, you can use it to add only Node.js packages to your application. 

As npm builds up its dependency tree outside of the web browser, you need to add all the dependencies to your application in the node_modules folder. Because this folder can contain hundreds or even thousands of files, npm is frequently used together with a JavaScript module bundler such as Browserify that bundles all dependencies and serves them to the browser as a static file. 

To make package management more efficient, npm3 introduced flat dependency trees that replaced nested trees npm used before. The new feature leads to less redundancy and a sleeker node_modules folder. This was the dependency structure Bower used—by npm adopting the feature, Bower became obsolete.

Yarn and pnpm

However, npm still does have strong competition. Most alternative package managers, such as Yarn and pnpm, mainly aim for better performance, as npm still has room for improvement in that regard. Yarn and pnpm are also Node.js packages, so you can install them with the npm install command. They achieve a faster completion time using different techniques. 

While Yarn caches packages and parallelizes operations, pnpm returns to nested dependencies but in a different way. It uses the combination of hard links and symlinks to save only one version of each module, which results in a much smaller node_modules folder.

If you are interested you can read a good npm vs Yarn vs pnpm comparison on Niccolo Borgioli’s blog and find some related performance benchmarks on pnpm’s GitHub page as well. We also have an article about the advantages of Yarn authored by Gigi Sayfan.

Task Runners (Not for Dependency Management)

Although task runners such as Grunt and Gulp can’t handle dependencies, they are frequently confused with dependency managers—so let’s quickly see how they are different. Task runners and dependency managers are both build tools but they solve a different set of problems. While dependency managers build the dependency tree of an application and load (and sometimes bundle) the dependencies, task runners automate routine tasks. 

Build processes typically consist of the same repetitive tasks that you need to perform regularly, such as minification, image optimization, CSS auto prefixing, linting, unit testing, and others. Task runners automate this process and let you create a set of automated tasks to save you manual work.

The Root of Confusion

The confusion with dependency manager tools mainly stems from the fact that task runners also require you to define your dependencies in some way. For instance, if you use Grunt you need to add your dependencies to the package.json file in the following way:

However, the package.json file doesn’t manage dependencies, it just loads the Node.js modules that Grunt will need in order to perform the task list defined in the Gruntfile. 

If you want to better understand what you can achieve with task runners, have a look at our tutorial about how to customize Bootstrap’s Sass files with Grunt, too.

JavaScript Module Loaders 

Now, let’s get back to dependency management tools—specifically JavaScript module loaders. Node.js has the handy require() method that lets you easily add a module to your application. However, as Node.js runs on the server-side, you can’t require Node modules directly in the browser. JavaScript module loaders such as RequireJS and Browserify provide a solution to this problem.

JavaScript module loaders let you load your JavaScript dependencies on the client-side. This means a huge performance gain, as the user’s browser is served by a static JavaScript file that includes all the dependencies and loads quickly. 

RequireJS and Browserify

It’s interesting to note that RequireJS existed before Node.js. It brought the require() method to the browser following the AMD module specification. Node.js implements the require() functionality following CommonJS which is a different JavaScript module specification. 

With the appearance of npm, the usage of RequireJS has begun to drop, however it’s still under development. It’s also important that by default RequireJS is not a Node module (although it has an implementation for Node.js), so you can also use it in other JavaScript environments such as Rhino.

Similarly to Node.js, Browserify is also an implementation of the CommonJS API. Being a Node module, Browserify uses Node.js’ require() method, which is why it’s frequently used together with npm. To provide your users with static dependencies, you can load your dependency with npm, bundle them with Browserify, then add the bundle as a <script> tag to your HTML page.

Static Module Bundlers

While JavaScript module loaders can load only JavaScript modules, static module bundlers like Webpack and Parcel can bundle any kind of frontend assets such as images, HTML, CSS, Sass, and other files. Their goal is to create static assets that run in the user’s browser like a breeze. 

In the screenshot below, you can see the logic of how Webpack works:

Webpack logic
Source: https://webpack.js.org/ 

Webpack and Parcel

Webpack inspects your entire project, generates a dependency graph, and creates one or more bundles that you can add to your HTML file. It comes with many advanced features such as dead code elimination, require() method for CSS files, code splitting, and more. Webpack replaces package managers, task runners, and JavaScript module loaders, as it provides all three functionalities.

In the past, Webpack’s biggest drawback was the amount of configuration required. It took significant time and knowledge to properly set up a Webpack project. Parcel, a newer static module bundler came up with a solution to this problem. It introduced a zero-configuration model that was later adopted by Webpack 4.0.0 as well. 

To use the zero-config feature, you only need to run a command (webpack or parcel build), pass the entry file, and the bundler automatically goes through the entire build process, which saves a lot of time and effort compared to any other dependency management solution.

Conclusion

Currently, static module bundlers such as Webpack and Parcel are the most advanced dependency management tools in frontend development, as they let you quickly bundle all the assets you’ll need. However, for a simpler app or website, you won’t necessarily need them. 

If your go-to dependency management tool solves everything you need, it can still be a good decision to keep using it rather than picking up a new tool!


No comments:

Post a Comment