npm-scripts as a Task Runner - Part 1

Developers have a wide range of business tools, development tools, and services to choose from to do their work. Using them successfully can increase work efficiency, enabling them to spend their time on more important tasks.

Codegraph has conducted research and evaluations in order to identify the best tools, from the point of view of user-friendliness and other factors, primarily interoperability, reliability, and workflow. We also restrict the number of tools that we use in order to maintain simplicity in our workflow. In addition, we try to avoid tools with extremely high learning costs, even if they are useful. It is important to consider tools based on how easy they are to use inside and outside of the team, and also based on their impact on learning costs.

Going forward, these articles shall provide information on tools and services that have actually been used by our team as part of our workflow.

npm-scripts as a Task Runner

In front-end development, in order to achieve work efficiency, it is essential to use task runners to automate complex tasks in as much as possible. There are a number of options available for implementing task runners, such as using a combination of Grunt, Gulp and Webpack, the use of GUI-based tools such as Codekit, and the like.

Codegraph has been evaluating these tools up until now. However, recently we have started using npm-scripts as task runners. npm-scripts are scripts that are supported in package.json, and which can be implemented as shell scripts or alias commands. They are defined in the scripts property in package.json.

scripts | npm Documentation

Benefits of Using npm-scripts

Broadly speaking, npm-scripts offer the following benefits:

  • They are not dependent on wrappers, allowing you to handle npm tools directly.
  • They are not tied to task runners' own configuration settings or rules.
  • They allow you to perform centralized package management and handling, just through package.json.

One great benefit is their ability to share development environments with minimum configuration.

Collaborators can install packages by running $ npm install, and then implement the environment simply by running all the tasks defined in the scripts property of package.json by using the $ npm run command. The only global dependency is Node.js, leaving the environment relatively clean.

Let's take a look at a sample task runner implemented using npm-scripts.

Sample Project Configuration

This time, let's assume that we have a simple, static site configuration, and that we have decided to use the following files:

  • HTML
  • CSS
  • JavaScript
  • Images

Codegraph uses PostCSS for CSS, and ES6 for JS, aiming to develop code using relatively new specifications.

PostCSS
PostCSS is a tool for transforming styles using JS plugins. These plugins can support CSS, variables and mixins, be used with future CSS syntax, display inline images, and more.
PostCSS - a tool for transforming CSS with JavaScript

Let's prepare the following file structure:

├─ dist
├─ src
│   ├─ css
│   │   └─ main.pcss
│   ├─ images
│   └─ js
│         └─ main.js
├─ index.html
└─ package.json

Based on this file structure, let's implement the following tasks:

  • Use PostCSS to transpile extended CSS.
  • Use Babel to transpile Compress images. code from ES6 to ES5.
  • Compress images.
  • Perform the above tasks automatically when a file is changed.
  • Use Browsersync to start up a local server, and make browsers reload when a file is changed.
  • Perform CSS and JavaScript syntax validation.

*The CSS files before they are transpiled to the standard syntax using PostCSS have the extension .pcss so they can be easily identified.

Let's implement this functionality step by step.

package.json

This is a sample of the package.json file to have ready at the beginning.

{
"name": "sample",
  "version": "1.0.0",
  "scripts": {
  },
  "devDependencies": {
  }
}

According to official documents, the name and version fields are required data items, so they must always be set. Set the project's domain name and version number.

Tasks are added to the scripts property, and the packages required for those tasks are added to the devDependencies object. Packages are not added directly, but through the command line.

CSS Build Task

Install postcss-cli so you can use PostCSS through the CLI.

$ npm i -D postcss-cli

Execute the following command.

./node_modules/.bin/postcss src/css/main.pcss -o dist/css/main.css

This uses PostCSS to process src/css/main.pcss and output dist/css/main.css. Apart from adding an inline source map by default, PostCSS itself does not touch any CSS code. This step should have output the original content, without making any changes.

Let's add this task to the scripts property in package.json.

"scripts": {
  "build:css": "postcss src/css/main.pcss -o dist/css/main.css --no-map"
},

This task is for building a public CSS, so the --no-map option at the end ensures that the source map is not generated in the output. This task can now be executed using the following command:

$ npm run build:css

Our team has implemented as standard the PostCSS plugins below for CSS development:

  • postcss-cssnext
  • postcss-import
  • cssnano
postcss-cssnext
A plugin that allows you to write next-generation CSS syntax. It provides functionality that is useful for CSS development, such as automatic addition of browser vendor prefixes, custom variables, selector nesting, and more.
postcss-cssnext
cssnext - Use tomorrow’s CSS syntax, today.
postcss-import
Imports other files specified by @import. Useful for managing CSS components.
postcss-import
cssnano
Compresses CSS.
cssnano
cssnano: A modular minifier based on the PostCSS ecosystem. - cssnano

Let's install them all together.

$ npm i -D postcss-cssnext postcss-import cssnano

When using these plugins, you can specify them using the -u option in the PostCSS CLI. However, it is a good idea to make it available in the PostCSS configuration file in order to make the script easier to read. Plugin options can also be specified in the file as necessary.

The PostCSS configuration file is called postcss.config.js, and it is located in the project's root directory. When setting options through the CLI, it is mandatory to reference ctx.options in postcss.config.js, so let's write the following code:

module.exports = (ctx) => ({
    map: ctx.options.map,
    plugins: [
        require('postcss-import')(),
        require('postcss-cssnext')({
            warnForDuplicates: false
        }),
        require('cssnano')()
    ]
})

Plugins are imported in using the require statement. However, taking the actual processing steps into account, you need to make sure to import postcss-import first for it to work correctly.

Also, the autoprefixer of cssnext and cssnano will end up being doubled up, so this would generate a warning. Although it would be fine for you to ignore this and leave it as it is, it would be fairly annoying for the warning to be displayed every time. To avoid this, set cssnext's warnForDuplicates option to false.

We have now successfully implemented a CSS transpiler using PostCSS.

As an aside, our team is currently designing CSS that incorporates OOCSS (Object-Oriented CSS). We manage components by importing multiple .pcss files comprised of multiple layers into main.pcss. Once imported, cssnext's extension syntax is converted into standard CSS code. It is then compressed using cssnano, and in the end it is output as dist/css/main.css.

At this point, package.json looks like this:

{
  "name": "sample",
  "version": "1.0.0",
  "scripts": {
    "build:css": "postcss src/css/main.pcss -o dist/css/main.css --no-map"
  },
  "devDependencies": {
    "cssnano": "^3.10.0",
    "postcss-cli": "^4.1.0",
    "postcss-cssnext": "^3.0.2",
    "postcss-import": "^10.0.0"
  }
}

After the CSS build task is executed, the file structure should look as follows:

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

*package-lock.json is an automatically generated file that is used for managing node_module's tree information.

This section is now a bit long, so let's move onto Part 2.