Kicking Off

Continue here if you skipped the preface

Creating your Own Task Manager

Since we don’t want to rely on too many different technologies, we’re gonna drop grunt, gulp, and all those other task managers. Which leaves the question — how are we going to run all our different tasks? We still want a task for building, testing, serving and so on. The answer is we’re simply going to use npm scripts. Just to feel the hang of it, add this to your package.json -

and from the project’s root folder type -

npm run tryme

As you can see, npm scripts are just a fancy way to run command line code. And fortunately, webpack has a very strong CLI that allows you to perform very complicated tasks through the command line, or in our case — through the npm scripts. The problem with this approach is that those commands can also get too complicated, and become hard to maintain, not to mention debug.

Luckily, webpack also has a very strong node.js api. So as suggested in this great article, we can simply pass all the logic to a separate node file, and use npm script to run it. Like this -

The problem with this approach is that you might end up with multiple node.js files for each task (build.js, serve.js, etc.), many of which are doing very similar tasks. So I suggest taking this approach one step further, and removing all logic out of package.json and move it into a new file called tasks.js by implementing NPM lifecycle event. This nifty little variable lets you know exactly which npm command initiated the current process, so you no longer need package.json to point to the right file.

So your package.json file deprived of all logic will look something like this -

And this new tasks.js file will look something like this -

Building Tasks

So now that we’ve created our own personal task manager, it’s time to build the tasks themselves. mine looks something like this -

Here we can see two methods: serve and build . Both initiate a compiler by passing a webpack configuration file (lines #18 & #26). But then they diverge:

The serve method pass the compiler into an instance of a webpackDevServer (#19) and then starts it (#21).

method pass the compiler into an instance of a (#19) and then starts it (#21). The build method simply runs the compiler (#28).

Different Configurations for Different Tasks

Some of you may have noticed that both serve and build use the same webpack configuration file (#2). You don’t have to be a webpack expert the guess that these tasks require different configurations. Take minification and uglification for example: you wouldn’t want your development environment to have unreadable JS, nor would you want your production environment to download large un-minified JS. So how can we achieve this separation without complicating our tasks file too much?

Let’s take a look at ./webpack.config.js :

As you can see, this file actually doesn’t hold any configuration of its own, but rather proxies the appropriate configuration file from inside the conf folder, according to the current npm event. Actually this switch could easily reside inside tasks.js, but having a webpack.config.js file in your project’s root folder is a good convention that’s worth keeping: it lets people know your project uses webpack.

Modular Configuration

So we already established that different tasks require different configurations. But more importantly, even different configurations have a lot in common. Take transpiling JS files for example: both serve and build requires babel loader. So how can you share common code between different webpack configurations? This is where tools like webpack-config and webpack-merge come into play: they allow us to recycle some basic configuration, and reuse it by adding, removing or changing specific parts.

This practice allows us to have one file, let’s call it webpack.base.config.js, which holds configuration common to all tasks, and many other task-specific files, such as webpack.build.config.js and webpack.serve.config.js

Here are simplified versions of base and build using webpack-config (for complete setup checkout the sample project):

Base configuration

Build configuration

Notice how in webpack.build.config.js we call the base configuration (line #6) and merge it with specific tasks unique to the build configuration, such as:

Setting the output path (#9)

Adding hash for better cache support (#10)

Clean up destination folder (#13) and uglify the js (#14)

ProTips

Here are some tips for making the best out of this architecture -

Common Configuration File

When using a modular architecture for your build, many parameters may repeat themselves many times, such as folder paths or ports. For this reason it’s recommended to have a common configuration file that will hold all these constants for a more efficient and centralized control. Here is an example of such file -

Checkout the full version in the sample project.

ExtractTextPlugin

The ExtractTextPlugin requires a little different approach to make it work in a modular architecture. This plugin is used to extract the css into a separate file, instead of injecting it into the header. Since its syntax wraps the rule it refers to (usually .css or .scss), it’s a little harder to reuse. Luckily, it has an on/off switch and a fallback rule, which combined make it completely modular. So the base configuration can look like this -

And now, if we want to disable this module for development server (because it doesn’t work well with live reload), we can simply add these lines to the serve configuration -

While in the distribution build we definitely want to switch it on, so in the build configuration we could add this::

Summary

Webpack is a great tool, but configuring it can become very complicated very fast. Instead of trying to maintain a huge blob of configurations, or using a third party task manager, simply implement the architecture described here, and enjoy all the free time you suddenly have to -

Go to the beach

Work on your side project

Write Medium articles

Let me know in the comments what you decided, plus any other questions or ideas you might have. Enjoy!