NPM Build Scripts for Static Sites

Express & NPM Build Scripts

This post will show you how to use npm packages for running tasks and bundling your static site. This is not an exhaustive guide but enough to get you started on customizing your own configurations. I use this setup for deploying to heroku or netlify all the time for development.

You could definitely use webpack, parcel, grunt or gulp. Webpack is the go to industry standard for more complex projects, see this course from the maintainer for what is capable. Currently the sites I'm working on do not require such meticulous build tools, so NPM scripts are perfect. Plus, you get to build something that best fits your needs and workflow style. I don't have time to read through the docs when I want to get started on a new project quick and fast. I tried using parcel for an express node app and I spent over an hour trying to get it setup. It didn't do what I needed and I could have spent more time learning about how to get it just right. But NPM has many great tools that can easily be setup for my needs.

Runners and Bundlers

Task runners automate the tedious process of watching for files changes and reloading the browser when a change occurs. Bundlers prepare your files for deployment doing things like compressing and minifying your code.

They save you clicking and switching, streamline the devlopment process, and restart processes automatically. which reduces your time spent . I strongly urge you to look into your tooling, I'll show you the basics for a static site first. Then how to use them for a express app that uses express-handlebars for templating along with a /server and /public directory for front and back end code.

Static Site

First, we will build our static site file structure with a bash script. cd into a fresh new directory and then run the script below.

npm init -y will create a default package.json file for the project and npm i -D will install our developer dependencies.

        mkdir -p {scss,css,views,js,img,fonts,design}
        cd scss && mkdir {base,config,objects,globals,components,utilities,pages}
        touch styles.scss
        cd base && touch _defaults.scss _normalize.scss _typography.scss && cd ..
        cd components && touch _component.scss && cd ..
        cd config && touch _mixins.scss _functions.scss _variables.scss && cd ..
        cd globals && touch _global.scss && cd ..
        cd objects && touch _object.scss && cd ..
        cd pages && touch _page.scss && cd ..
        cd utilities && touch _utility.scss && cd ..
        cd ..

        cd js && touch scripts.js && cd ..
        cd views && touch index.html && cd ..

        npm init -y
        npm i -D node-sass autoprefixer postcss-cli onchange eslint uglify-js imagemin-cli browser-sync npm-run-all

With node-sass we can compile SASS files (.scss) files into CSS files. onchange will watch files and directories for a file change initiated by saving a file. Together, we can compile our SASS files into one main styles.css file and re-compile it every time we make a change to any .scss file.

          "name": "new-site",
          "version": "1.0.0",
          "description": "",
          "main": "index.js",
          "scripts": {
            "dev:scss": "node-sass --output-style nested -o css scss/styles.scss",
            "dev:watch": "onchange 'scss' -- npm run dev:scss",
            "dev:serve": "browser-sync start --server --ss 'views' --files 'scss/**/*.scss, views/*.html, js/*.js'",
            "server": "run-p dev:watch dev:serve"
          "keywords": [],
          "author": "",
          "license": "ISC",
          "devDependencies": {
            "autoprefixer": "^9.8.4",
            "browser-sync": "^2.26.7",
            "eslint": "^7.4.0",
            "imagemin-cli": "^6.0.0",
            "node-sass": "^4.14.1",
            "npm-run-all": "^4.1.5",
            "onchange": "^7.0.2",
            "postcss-cli": "^7.1.1",
            "uglify-js": "^3.10.0"


The dev:scss command will watch our scss/styles.scss file which will contain all our imports of the SASS partials. SASS partials have an underscore in the first position like _mixins.scss. This will output our scss/styles.scss file into the css directory as styles.css.

The next line, dev:watch runs the onchange package to watch the /scss directory and run npm run dev:scss any time we make a change in a file within the /scss directory. So, anytime we make a change to a SASS file, we recompile the scss/styles.scss and create a fresh new css/styles.css which is linked to the views/index.html file.

Live Reload

browser-sync will reload our browser anytime we make file change. Here are the docs for all the settings and the command line usage which we will be using. It takes several options, let's take a closer look at the dev:serve line.

--server will start local server for our project, --ss means 'serve static' and tells our server where to server our static files, in this case it is the /views directory. The --files flag will watch files and directories that we provide it to reload when changed. We can use the glob command (*) to watch all our different file types.

Finally, we put it all together with the server command by using the run-p which runs our two commands at the same time in parallel. This is preferable to using & between the two commands because run-p ensures that it will work on all platforms and properly stops all processes, see this post for more details.

Then run it on the command line:

        npm run server


Next, we'll bundle everything up and save it to a dist folder for deployment.

          "scripts": {
            "dev:scss": "node-sass --output-style nested -o css scss/styles.scss",
            "dev:watch": "onchange 'scss' -- npm run dev:scss",
            "dev:serve": "browser-sync start --server --ss 'views' --files 'scss/**/*.scss, views/*.html, js/*.js'",
            "server": "run-p dev:watch dev:serve",
            "build:dist": "mkdir -p dist/{css,js,img,fonts}",
            "build:html": "html-minifier --input-dir ./views --output-dir ./dist --collapse-whitespace",
            "build:css": "postcss -u autoprefixer -o dist/css/styles.css css/*.css",
            "build:js-lint": "eslint js",
            "build:js-uglify": "mkdir -p dist/js && uglifyjs js/*.js -m -o dist/js/scripts.js && uglifyjs js/*.js -m -c -o dist/js/scripts.min.js",
            "build:img": "imagemin src/images dist/images -p",
            "build:fonts": "cp fonts/* dist/fonts",
            "build": "run-s build:*"


build:dist will forcefully remove /dist if it exists and create a new one using the curly braces to create children folders within it. The -p flag will not throw an error if the directory already exists.

build:html will minify our html, can't forget about our plain old HTML! html-minifier will do the job.

build:css uses postcss-cli to run our css file through autoprefixer. Autoprefixer will add vendor prefixes such as -moz and -webkit to our css rules ensure they work properly for most browsers. Very helpful tool for front end developers!PostCSS is tool that uses javascript to do a whole bunch of useful cool things with CSS. Read through the github repo to see the many things it is capable of.


build:js:lint will check our code any syntax errors and alert us to make a fix. There are many options to incorporate and you will need to run eslint --init to generate a config file for setting up linting rules. Read more here.

build:js:uglify will minify and combine our javascript files using the uglify-js package.

It will mangle which means it will reduce variable names to a 1 or 2 letter replacement. Minification will reduce the file size by removing white-space, comments and other non-essential characters and is still valid code, just harder to read. Compressing will further reduce the file size with more optimizations.


build:img will compress the images with imagemin. Read up on image compression, it really makes a difference.


build:fonts will copy our optimized .woff and .woff2 font files. Check out web font guru Zach Leatherman on font optimizations and loading or my posts on font tools and web fonts both heavily using Zach's advice.

Build It

build will sequentially run each of the npm scripts that contain the build suffix. We are done! Hopefully no errors.

This is my setup and you can easily make changes that fit your developer workflow. I've based this on several great blog posts fromCSS Tricks by Damon Bauer and this one. CSS In Real Life is super great and here is another repo for some more ideas.

If the above code suits your needs, get the full repo here to get started.