Categories


Archives


Recent Posts


Categories


Build Watchers, and NPM as a Build Tool

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

This entry is part 4 of 4 in the series Modern Javascript for PHP Developers. Earlier posts include Modern Javascript for PHP Developers, Express and NPM for PHP Developers, and Client Side Javascript, Modules, and Webpack. This is the most recent post in the series.

In our last article we covered the webpack project, and used it to demonstrate the modern javascript workflow of

  1. Download modular source code packages using npm
  2. Write a modular program
  3. Compile a modular program into a single file
  4. Include compiled program via a <script/> tag
  5. Reload the page

While this workflow may seem familiar to software developers who work in compiled languages, it can be anathema to web developers who got into client-side work due to its immediacy. i.e. the workflow of

  1. Include a file via a <script/> tag
  2. Edit that file
  3. Reload the page

Modern front end developers have a solution for this called build watchers.

Build watchers are a bit clumsy and random, but they do offer a workflow that’s similar to the one from our more elegant age. Today we’ll take a look at webpack’s --watch option, and also explore some of the other tools npm gives developers for building their projects.

A Simple Program

Let’s quickly recreate our program from last time. First, we’ll start our project off with the jquery and webpack packages. We’re moving fast here — if you need a refresher checkout the previous articles in this series.

First, we’ll create a folder for our project, create a package.json file, and then add the webpack and jquery packages to our project

$ mkdir project-watcher
$ cd project-watcher
$ echo '{}' > package.json
$ npm install webpack jquery --save

Then, we’ll create a folder for our source files

$ mkdir src-client   

and create a source file

//#File: src-client/entry-point.js
var jQuery = require('jQuery');
jQuery(function($){
    $('body').html('Who Watches the Watchers');
});         

After that we’ll compile/transpile (or “build”) our bundle.js file

$ ./node_modules/webpack/bin/webpack.js src-client/entry-point.js pub/bundle.js
Hash: c4da55c3d4f5637f14ea
Version: webpack 2.5.1
Time: 321ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  271 kB       0  [emitted]  [big]  main
   [0] ./~/jQuery/dist/jquery.js 268 kB {0} [built]
   [1] ./src-client/entry-point.js 112 bytes {0} [built]

Finally, we’ll create an HTML file that loads our bundle.js file.

<!-- #File: pub/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>

<script src="bundle.js"></script>
</body>
</html>

With the above in place, we should be able to open index.html and see the Who Watches the Watchers text in our browser. Here’s a complete list of all the files created so far (minus the node_modules folder).

./package.json
./pub
./pub/bundle.js
./src-client
./src-client/entry-point.js

What is a “Build”

Above, we ran the following command to create our bundje.js file

$ ./node_modules/webpack/bin/webpack.js src-client/entry-point.js pub/bundle.js

By running the above, we’re telling webpack to transpile (or compile, or trans-compile — the terms are used here interchangeably) the code in entry-point.js into code that will work in web browsers. The new code will be put in the bundle.js file specified as the second argument to the above command.

If we change our entry point to use a module

//#File: src-client/entry-point.js
var jQuery = require('jQuery');
var theQuestion = require('./the-question');
jQuery(function($){
    theQuestion.ask($);    
});              

and then define that module

//#File: src-client/the-question.js
var toExport = {};
toExport.ask = function($, question){
    question = question ? question : 'Who Watches the Watchers';
    $('body').html(question);
}
module.exports = toExport;

when we run

$ ./node_modules/webpack/bin/webpack.js src-client/entry-point.js pub/bundle.js

everything still works. That’s because webpack will look for and resolve any module dependencies in your project’s javascript files.

This process of turning your source code into something that’s deliverable is colloquially known as a build process. You’re taking the smaller parts of your program, and combining them into a deliverable whole.

Builds used to be an unavoidable part of software development — but with early web based technologies (perl/CGI, ASP, PHP, HTML, javascript, etc) the source file you were editing and the deliverable product were often one in the same. Modern javascript undoes this a bit, and trades the convenience of no-build development for the benefits a package system (and ecosystem) like NPM.

If you don’t know what we mean by no-build development — let’s edit one of our javascript files.

//#File: src-client/the-question.js
var toExport = {};
toExport.ask = function($, question){
    question = question ? question : 'Who Builds the Builders?';
    $('body').html(question);
}
module.exports = toExport;

After editing this file, if we reload index.html, we will not see our changes. That’s because the only file loaded by the browser is bundle.js. If we want to see our change, we need to re-build the project

$ ./node_modules/webpack/bin/webpack.js src-client/entry-point.js pub/bundle.js

With a new bundle.js, we should now see our Who builds the Builders text in our web browser.

In traditional javascript development, you don’t need this build step. i.e. it’s a no-build workflow.

Build Watchers

This workflow can be a little frustrating for developers used to the quick feedback cycle of “Edit a file, refresh the page” style development. To help with this transition, modern javascript programmers have embraced the idea of build watchers.

A build watcher tells your computer to monitor the files in your project for any changes. When/if the build watcher detects a change, it will automatically rebuild your project.

If that didn’t make sense, a quick demonstration should. The webpack project comes with a built-in build watcher — all we need to do is add the --watch option to our command line. Try running the following

$ ./node_modules/webpack/bin/webpack.js --watch src-client/entry-point.js pub/bundle.js

Webpack is watching the files…

Hash: a388b2e24b7b25ff801f
Version: webpack 2.5.1
Time: 333ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  271 kB       0  [emitted]  [big]  main
   [0] ./~/jQuery/dist/jquery.js 268 kB {0} [built]
   [1] ./src-client/the-question.js 181 bytes {0} [built]
   [2] ./src-client/entry-point.js 137 bytes {0} [built]
▒

Readers with eagle-eyes will notice two small differences. First, webpack let us know that it’s watching the files

Webpack is watching the files

Second — webpack didn’t exit. Instead, the program is still running and waiting for something to happen. If we wanted to, we could halt this program by typing Ctrl-C. However, lets leave this running for a moment.

Leaving our terminal window, let’s go back to our program and change its text again. Edit the-question.js file so it matches the following.

//#File: src-client/the-question.js
var toExport = {};
toExport.ask = function($, question){
    question = question ? question : 'How does this even work?';
    $('body').html(question);
};
module.exports = toExport;

Save your file, and then go back to the terminal

Hash: a388b2e24b7b25ff801f
Version: webpack 2.5.1
Time: 333ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  271 kB       0  [emitted]  [big]  main
   [0] ./~/jQuery/dist/jquery.js 268 kB {0} [built]
   [1] ./src-client/the-question.js 181 bytes {0} [built]
   [2] ./src-client/entry-point.js 137 bytes {0} [built]

Hash: da48cf2dc1590baa8cd8
Version: webpack 2.5.1
Time: 14ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  271 kB       0  [emitted]  [big]  main
   [1] ./src-client/the-question.js 181 bytes {0} [built]
    + 2 hidden modules

While that wall of text may seem confusing, you should notice that webpack has automatically kicked off a second build.

Hash: da48cf2dc1590baa8cd8
Version: webpack 2.5.1
Time: 14ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  271 kB       0  [emitted]  [big]  main
   [1] ./src-client/the-question.js 181 bytes {0} [built]
    + 2 hidden modules

This happened when we saved our file. Behind the scenes, webpack used the operating system level APIs for file-watching and kicked off a new build. If you reload index.html, you should see the updated text from our module.

In theory, watchers can give the developer the experience of the Edit/Reload cycle they’re used to. In practice, this will be a mixed bag. If your transpile times are fast webpack will finish its compilation before you have time to switch to your browser and reload. If, however, your project is large and the build takes some time, you may be left refreshing multiple times before you see your changes.

While they won’t give you everything you’re used to as a classic javascript developer, watchers are a big part of modern javascript development. Its best to become familiar with how your particular project uses them.

NPM as a Build Tool

If we look at our current build command — we’re starting to enter stereotypical unix-complexity land.

$ ./node_modules/webpack/bin/webpack.js --watch src-client/entry-point.js pub/bundle.js

It’s one thing to remember this command while you’re reading this tutorial — but if you’re coming back to your project after a bit it’s easy enough to forget a flag or command line argument. This is where npm’s secret second-job as a build system can help you and your team out.

In addition to downloading our packages and resolving SemVer dependencies, npm can also run arbitrary unix commands for us. For example, add the following to your package.json file

//#File: package.json
{
  /* ... */,
  "scripts":{
    "hello-world":"echo 'Hello right back at you'"
  }
}

After adding this scripts object, try running the following from any folder in your project

$ npm run hello-world

> @ hello-world /path/to/projects/node/project-watcher
> echo 'Hello right back at you'

Hello right back at you

Congratulations — you just ran your first npm script.

We added a scripts key to package.json.

"scripts":{
    "hello-world":"echo 'Hello right back at you'"
  }

This scripts key is, itself, an object with a list of key/value pairs. The key (hello-world above) is our script name. The value is a unix shell script (echo 'Hello right back at you') that npm will execute.

Using this scripts section, we can create easy shortcuts for our build

//#File: package.json
{
  /* ... */,
  "scripts":{
    "build-client":"./node_modules/webpack/bin/webpack.js src-client/entry-point.js pub/bundle.js",
    "build-client-watch":"./node_modules/webpack/bin/webpack.js --watch src-client/entry-point.js pub/bundle.js"
  }
}

With the above in place, our build is just a quick

$ npm run build-client

away. The watcher version of our build is similarly close at hand.

$ npm run build-client-watch

More important than reducing the amount of typing we need to do, the real value of these npm scripts is they document a project’s build scripts, or other important processes (running tests, building server-side javascript, starting the node web-server, etc.). In this article we’ve been focused on webpack — but many projects use a different transpiler. By including a scripts section, a javascript developer can let the world know how to build or run their project.

Wrap Up

While we’ve only scratched the surface, we hope these four articles have given you an entry point into the always complicated world of modern javascript.

Next time we’ll take a look at some real-world javascript projects and see how this introduction faces up with what we’ll find in the real world. Along the way we’ll talk about npm’s build life-cycle scripts, as well as installing npm packages globally.

Series Navigation<< Client Side Javascript, Modules, and Webpack

Copyright © Alan Storm 1975 – 2017 All Rights Reserved

Originally Posted: 5th June 2017