npm-scripts as a Task Runner - Part 3

In the previous article (Part 2), we implemented a task that executes together CSS and Build tasks, and an image compression task.

Here in Part 3, we will implement functionality for processing tasks automatically when a file is changed, starting up a local server, and making browsers reload automatically after a file is changed. We will also add functionality for validating CSS and JavaScript syntax.

Monitoring File Changes

In order to perform our development work seamlessly, let's implement the automatic execution of the build tasks that we have created so far, every time a file is changed.

First of all, add the following task for detecting changes in CSS files:

"scripts": {
    "watch:css": "postcss src/css/main.pcss -o dist/css/main.css -w"
},

We create a task called watch:css so that we can execute all the watch tasks together, just as we did using build:*. Unlike build:css, we add the -w(--watch) option to enable watch mode, and remove the --no-map option so that it generates the inline source map. During development, you can use the browser development tool to look at the source map.

Next, let's handle the JavaScript. As there is no watch mode in browserify, let's use a tool called watchify.

watchify
A tool that executes the browserify build task when a file is changed. It can be configured to process only the new changes.
watchify - npm

Install watchify.

$ npm i -D watchify

Implement the task as follows:

"scripts": {
    "watch:js": "watchify -t babelify src/js/main.js -o 'uglifyjs -c -m > dist/js/main.js' -v"
},

watchify does not seem able to use pipes for outputs, so you need to use the -o (--output) option. You can use the -v (--verbose) option to measure the amount of processing time.

Let's use onchange to monitor the image files.

onchange
A tool that receives a file change notification, and then executes a command.
onchange - npm

Install onchange.

$ npm i -D onchange

Implement the task as shown below:

"watch:images": "onchange 'src/images' -e '**/*.DS_Store' -- npm run images"

With onchange, you use a glob pattern to specify the files to be monitored, and the command to be executed is specified after -- In the code above, the images task is executed when a change is made in the src/images directory. The -e (--exclude) option makes it ignore the .DS_Store file that is generated under OS X.

BrowserSync for Auto-Sync the Local Server and Browsers

The build tasks are now executed automatically every time a file is changed. Now, let's implement functionality for reloading browsers and synchronizing displays on multiple browsers, once those build tasks have been completed.

Let's install the standard BrowserSync.

browser-sync
A tool that allows you to synchronize multiple environments automatically, and verify file updates.
browser-sync - npm
Browsersync - Time-saving synchronised browser testing

Install browser-sync.

$ npm i -D browser-sync

Now, let's implement the task called watch:server.

"scripts": {
    "watch:server": "browser-sync start -s -f 'dist, **/*.html, !node_modules/**/*'"
},

After browser-sync start, specify the -s(--server) option to start up the local server (in the current directory), and the -f(--files) option to specify the directory or files to be monitored. This time, we have decided to monitor all asset files under the dist folder, and all the HTML files, except those under the node_modules folder.

Let's use npm-run-all, which was introduced in the previous article, to execute all the monitoring tasks in one go.

"scripts": {
    "watch": "npm-run-all -p watch:*"
},

In this way, we can just use the npm run watch command to do our development work, while checking for changes across multiple environments.

As shown below, it is useful to make sure that the build task and watch task are included automatically when doing a package install:

"scripts": {
    "postinstall": "npm run build && npm run watch"
},

The postinstall command is executed after npm install.

Implementing a Syntax Validator

Syntax validators are essential for writing beautiful code that is highly readable and reliable. Although some people may use syntax validation functionality through editor extensions, incorporating this functionality as part of the task runners of a project allows you to maintain a consistent coding style when working as a group.

Let's use the following CSS and JavaScript syntax validators.

  • stylelint
  • eslint
stylelint
Let's use the following CSS and JavaScript syntax validators.
stylelint - npm
stylelint
eslint
A JavaScript and JSX syntax validator.
eslint - npm
ESLint - Pluggable JavaScript linter

Let's install these all together.

$ npm i -D stylelint eslint

CSS Syntax Validation

You can include stylelint's configuration in package.json; however, it is better to have a separate configuration file, so that these settings can be shared across different projects.

Create a configuration file called .stylelintrc. You can specify the settings using the JSON format.

Configure rules and other settings for the project. Please refer to the official site for more details.

User guide - stylelint

Let's add a syntax validator to check the .pcss files in the src/css folder.

"scripts": {
    "lint:css": "stylelint src/css/**/*.pcss"
},

JavaScript Syntax Validation

Execute the command below to generate ESLint's configuration file.

$ ./node_modules/.bin/eslint --init

This displays an interactive dialog that will help you configure the initialization settings. You can also use Google's popular JavaScript style guide.

? How would you like to configure ESLint? Use a popular style guide
? Which style guide do you want to follow? (Use arrow keys)
❯ Google
  Airbnb
  Standard

Finally, select the format for the configuration file that you want to generate.

? How would you like to configure ESLint? Use a popular style guide
? Which style guide do you want to follow? Standard
? What format do you want your config file to be in? (Use arrow keys)
❯ JavaScript
  YAML
  JSON

Let's select Standard and JavaScript. Once the configuration is complete, a configuration file called .eslintrc.js is generated, and dependency modules are installed.

This article does not cover how to customize ESlint's configuration settings. However, this is done by adding settings to the .eslintrc.js file. Please refer to the links below for more details.

Configuring ESLint

List of available rules - ESLint

Add the task to scripts.

"scripts": {
    "lint:js": "eslint src/js/**/*.js"
},

This completes the syntax validator.

Completed Project

The file structure of the completed project now looks as follows:

├─ dist
├─ node_modules
├─ src
│   ├─ css
│   │   └─ main.pcss
│   ├─ images
│   └─ js
│         └─ main.js
├─ .eslintrc.js
├─ .stylelintrc
├─ index.html
├─ package-lock.json
├─ package.json
└─ postcss.config.js

package.json now looks like this: *Version as of July 31st, 2017

{
  "name": "sample",
  "version": "1.0.0",
  "scripts": {
    "clean": "rimraf dist/{css/*,js/*,images/*}",
    "build:css": "postcss src/css/main.pcss -o dist/css/main.css --no-map",
    "build:js": "mkdirp dist/js && browserify -t babelify src/js/main.js | uglifyjs -c -m > dist/js/main.js",
    "build:images": "imagemin src/images/* -o dist/images",
    "build": "npm run clean & npm-run-all -p build:*",
    "watch:css": "postcss src/css/main.pcss -o dist/css/main.css -w",
    "watch:js": "watchify -t babelify src/js/main.js -o 'uglifyjs -c -m > dist/js/main.js' -v",
    "watch:images": "onchange 'src/images' -e '**/*.DS_Store' -- npm run build:images",
    "watch:server": "browser-sync start -s -f 'dist, **/*.html, !node_modules/**/*'",
    "watch": "npm-run-all -p watch:*",
    "lint:css": "stylelint src/css/**/*.pcss",
    "lint:js": "eslint src/js/**/*.js",
    "postinstall": "npm run build && npm run watch"
  },
  "devDependencies": {
    "babelify": "^7.3.0",
    "browser-sync": "^2.18.13",
    "browserify": "^14.4.0",
    "cssnano": "^3.10.0",
    "eslint": "^4.3.0",
    "eslint-config-standard": "^10.2.1",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-node": "^5.1.1",
    "eslint-plugin-promise": "^3.5.0",
    "eslint-plugin-standard": "^3.0.1",
    "imagemin-cli": "^3.0.0",
    "mkdirp": "^0.5.1",
    "npm-run-all": "^4.0.2",
    "onchange": "^3.2.1",
    "postcss-cli": "^4.1.0",
    "postcss-cssnext": "^3.0.2",
    "postcss-import": "^10.0.0",
    "rimraf": "^2.6.1",
    "stylelint": "^8.0.0",
    "uglify-es": "^3.0.26",
    "watchify": "^3.9.0"
  }
}

In this article, across three parts, we have described how to build a task runner using npm-scripts. We hope this has helped you realize that npm-scripts is a tool that is extremely simple and easy to use, and which provides plenty of functionality for implementing task runners.

The sample code that we have created here is available on Github.