惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
N
News and Events Feed by Topic
L
LINUX DO - 热门话题
PCI Perspectives
PCI Perspectives
www.infosecurity-magazine.com
www.infosecurity-magazine.com
爱范儿
爱范儿
D
DataBreaches.Net
Simon Willison's Weblog
Simon Willison's Weblog
S
Secure Thoughts
S
SegmentFault 最新的问题
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 叶小钗
P
Proofpoint News Feed
The Hacker News
The Hacker News
T
ThreatConnect
N
News and Events Feed by Topic
T
Threatpost
The Register - Security
The Register - Security
WordPress大学
WordPress大学
博客园 - Franky
Recorded Future
Recorded Future
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Project Zero
Project Zero
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
腾讯CDC
F
Future of Privacy Forum
F
Full Disclosure
Cyberwarzone
Cyberwarzone
J
Java Code Geeks
李成银的技术随笔
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
H
Hacker News: Front Page
人人都是产品经理
人人都是产品经理
博客园_首页
Scott Helme
Scott Helme
Google DeepMind News
Google DeepMind News
美团技术团队
Malwarebytes
Malwarebytes
Last Week in AI
Last Week in AI
T
Tailwind CSS Blog
T
The Exploit Database - CXSecurity.com
G
GRAHAM CLULEY
Recent Announcements
Recent Announcements
C
CXSECURITY Database RSS Feed - CXSecurity.com

CSS-Tricks

Revealing Text With CSS letter-spacing | CSS-Tricks Technical Writing in the AI Age | CSS-Tricks Cross-Document View Transitions: Scaling Across Hundreds of Elements | CSS-Tricks Cross-Document View Transitions: Scaling Across Hundreds of Elements | CSS-Tricks The State of CSS Centering in 2026 | CSS-Tricks Stack Overflow: When We Stop Asking | CSS-Tricks Cross-Document View Transitions: The Gotchas Nobody Mentions | CSS-Tricks What’s !important #11: 3D Voxel Scenes, Flying Focus, CSS Syntaxes, and More | CSS-Tricks Computing and Displaying Discounted Prices in CSS | CSS-Tricks rotateX() | CSS-Tricks rotateY() | CSS-Tricks rotateZ() | CSS-Tricks rotate() | CSS-Tricks Soon We Can Finally Banish JavaScript to the ShadowRealm | CSS-Tricks Using CSS corner-shape For Folded Corners | CSS-Tricks A Scrollytelling Gift for Mum on Mother’s Day 2026 | CSS-Tricks Google’s Prompt API | CSS-Tricks Making Zigzag CSS Layouts With a Grid + Transform Trick | CSS-Tricks Fixed-Height Cards: More Fragile Than They Look | CSS-Tricks What’s !important #10: HTML-in-Canvas, Hex Maps, E-ink Optimization, and More | CSS-Tricks The Importance of Native Randomness in CSS | CSS-Tricks contrast() | CSS-Tricks contrast-color() | CSS-Tricks Let’s Use the Nonexistent ::nth-letter Selector Now | CSS-Tricks Quick Hit #126 Recreating Apple’s Vision Pro Animation in CSS | CSS-Tricks Quick Hit #125 Enhancing Astro With a Markdown Component | CSS-Tricks Quick Hit #124 Markdown + Astro = ❤️ | CSS-Tricks Quick Hit #123 What’s !important #9: clip-path Jigsaws, View Transitions Toolkit, Name-only Containers, and More | CSS-Tricks A Well-Designed JavaScript Module System is Your First Architecture Decision | CSS-Tricks hypot() | CSS-Tricks The Radio State Machine | CSS-Tricks 7 View Transitions Recipes to Try | CSS-Tricks Quick Hit #122 Quick Hit #121 Selecting a Date Range in CSS | CSS-Tricks saturate() | CSS-Tricks justify-self | CSS-Tricks Quick Hit #120 Alternatives to the !important Keyword | CSS-Tricks Quick Hit #119 New CSS Multi-Column Layout Features in Chrome | CSS-Tricks Quick Hit #118 Making Complex CSS Shapes Using shape() | CSS-Tricks Quick Hit #117 Front-End Fools: Top 10 April Fools’ UI Pranks of All Time | CSS-Tricks Sniffing Out the CSS Olfactive API | CSS-Tricks What’s !important #8: Light/Dark Favicons, @mixin, object-view-box, and More | CSS-Tricks Quick Hit #116 Form Automation Tips for Happier User and Clients | CSS-Tricks Quick Hit #115 Generative UI Notes | CSS-Tricks Quick Hit #114 Quick Hit #113 Experimenting With Scroll-Driven corner-shape Animations | CSS-Tricks Quick Hit #112 JavaScript for Everyone: Destructuring | CSS-Tricks Quick Hit #111 Quick Hit #110 What’s !important #7: random(), Folded Corners, Anchored Container Queries, and More | CSS-Tricks 4 Reasons That Make Tailwind Great for Building Layouts | CSS-Tricks Quick Hit #109 Quick Hit #108 Abusing Customizable Selects | CSS-Tricks Quick Hit #107 The Value of z-index | CSS-Tricks Quick Hit #106 The Different Ways to Select <html> in CSS Quick Hit #105 Popover API or Dialog API: Which to Choose? Quick Hit #104 What’s !important #6: :heading, border-shape, Truncating Text From the Middle, and More Yet Another Way to Center an (Absolute) Element An Exploit ... in CSS?! Quick Hit #103 A Complete Guide to Bookmarklets Quick Hit #102 Loading Smarter: SVG vs. Raster Loaders in Modern Web Design Potentially Coming to a Browser :near() You Quick Hit #101 Distinguishing "Components" and "Utilities" in Tailwind Quick Hit #100 Spiral Scrollytelling in CSS With sibling-index() Interop 2026 Quick Hit #99 What’s !important #5: Lazy-loading iframes, Repeating corner-shape Backgrounds, and More Quick Hit #98 Making a Responsive Pyramidal Grid With Modern CSS Approximating contrast-color() With Other CSS Features Quick Hit #97 Trying to Make the Perfect Pie Chart in CSS Quick Hit #96 Quick Hit #95 CSS Bar Charts Using Modern Functions Quick Hit #94 No Hassle Visual Code Theming: Publishing an Extension Quick Hit #93
Why npm Scripts?
CSS-Tricks · 2016-02-13 · via CSS-Tricks

There has been a growing sentiment (for instance) that using node packages directly, with the command line interfaces they provide, is a good route to take. As opposed to abstracting the functionality away behind a task runner. Partly: you use npm anyway, npm provides scripting functionality, why not just use that? But there is more to it than that. Let’s walk through the thinking, but also exactly how to accomplish many of the most important tasks in a front end development build process.

I’ve started using npm scripts in my projects for about the last six months. Before that, I used Gulp and before that, Grunt. They’ve served me well and helped me perform my work faster and more efficiently by automating many of the things I used to do by hand. However, I started to feel that I was fighting the tools rather than focusing on my own code.

Grunt, Gulp, Broccoli, Brunch and the like all require you to fit your tasks into their paradigms and configurations. Each has it’s own syntaxes, quirks and gotchas that you need to learn. This adds code complexity, build complexity and makes you focus on fixing tooling rather than writing code.

These build tools rely on plugins that wrap a core command line tool. This creates another layer of abstraction away from the core tool, which means more potential for bad things to happen.

Here are three problems I’ve seen multiple times:

  • If a plugin doesn’t exist for the command line tool you want to use, you’re out of luck (unless you write it).
  • A plugin you’re trying to use wraps an older version of the tool you want to use. Features and documentation don’t always match between the plugin you’re using and the current version of the core tool.
  • Errors aren’t always handled well. If a plugin fails, it might not pass along the error from the core tool, resulting in frustration and not really knowing how to debug the problem.

But, bear in mind…

Let me say this: if you are happy with your current build system and it accomplishes all that you need it to do, you can keep using it! Just because npm scripts are becoming more popular doesn’t mean you should jump ship. Keep focusing on writing your code instead of learning more tooling. If you start to get the feeling that you’re fighting with your tools, that’s when I’d suggest considering npm scripts.

If you’ve decided you want to investigate or start using npm scripts, keep reading! You’ll find plenty of example tasks in the rest of this post. Also, I’ve created npm-build-boilerplate with all of these tasks that you can use as a starting point. Let’s get to it!

Writing npm scripts

We’ll be spending a majority of our time in a `package.json` file. This is where all of our dependencies and scripts will live. Here’s a stripped down version from my boilerplate project:

{
  "name": "npm-build-boilerplate",
  "version": "1.0.0",
  "scripts": {
    ...
  },
  "devDependencies": {
    ...
  }
}

We’ll build up our `package.json` file as we go along. Our scripts will go into the scripts object, and any tools we want to use will be installed and put into the devDependencies object.

Before we begin, here’s a sample structure of the project I’ll be referring to throughout this post:

Compile SCSS to CSS

I’m a heavy user of SCSS, so that’s what I’ll be working with. To compile SCSS to CSS, I turn to node-sass. First, we need to install node-sass; do this by running the following in your command line:

npm install --save-dev node-sass

This will install node-sass in your current directory and add it to the devDependencies object in your `package.json`. This is especially useful when someone else runs your project because they will have everything they need to get the project running. Once installed, we can use it on the command line:

node-sass --output-style compressed -o dist/css src/scss

Let’s break down what this command does. Starting at the end, it says: look in the `src/scss` folder for any SCSS files; output (-o flag) the compiled CSS to `dist/css`; compress the output (using the --output-style flag with “compressed” as the option).

Now that we’ve got that working the the command line, let’s move it to an npm script. In your `package.json` scripts object, add it like so:

"scripts": {
  "scss": "node-sass --output-style compressed -o dist/css src/scss"
}

Now, head back to the command line and run:

npm run scss

You will see the same output as running the node-sass command directly in the command line.

Any time we create an npm script in the remainder of this post, you can run it by using a command like the above.

Just replace scss with the name of the task you want to run.

As you will see, many of the command line tools we’ll use have numerous options you can use to configure it exactly you see fit. For instance, here’s the list of (node-sass options). Here’s a different setup show how to pass multiple options:

"scripts": {
  "scss": "node-sass --output-style nested --indent-type tab --indent-width 4 -o dist/css src/scss"
}

Autoprefix CSS with PostCSS

Now that we’re compiling Scss to CSS, we can automatically add vendor prefixes using Autoprefixer & PostCSS. We can install multiple modules at the same time, separating them with spaces:

npm install --save-dev postcss-cli autoprefixer

We’re installing two modules because PostCSS doesn’t do anything by default. It relies on other plugins like Autoprefixer to manipulate the CSS you give it.

With the necessary tools installed and saved to devDependencies, add a new task in your scripts object:

"scripts": {
  ...
  "autoprefixer": "postcss -u autoprefixer -r dist/css/*"
}

This task says: Hey postcss, use (-u flag) autoprefixer to replace (-r flag) any `.css` files in `dist/css` with vendor prefixed code. That’s it! Need to change the default browser support for autoprefixer? It’s easy to add to the script:

"autoprefixer": "postcss -u autoprefixer --autoprefixer.browsers '> 5%, ie 9' -r dist/css/*"

Again, there’s lot of options you can use to configure your own build: postcss-cli and autoprefixer.

Linting JavaScript

Keeping a standard format and style when authoring code is important to keep errors to a minimum and increase developer efficiency. “Linting” helps us do that automatically, so let’s add JavaScript linting by using eslint.

Once again, install the package; this time, let’s use a shortcut:

npm i -D eslint

This is the same as:

npm install --save-dev eslint

Once installed, we’ll set up some basic rules to run our code against using eslint. Run the following to start a wizard:

eslint --init

I’d suggest choosing “Answer questions about your style” and answering the questions it asks. This will generate a new file in the root of your project that eslint will check your code against.

Now, let’s add a lint task to our `package.json` scripts object:

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

Our lint task is 13 characters long! It looks for any JavaScript files in the src/js folder and runs them against the configuration it generated earlier. Of course, you can get crazy with the options.

Uglifying JavaScript files

Let’s work on combining and minifying our JavaScript files, which we can use uglify-js to do. We’ll need to install uglify-js first:

npm i -D uglify-js

Then, we can set up our uglify task in `package.json`:

"scripts": {
  ...
  "uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js"
}

One of the great things about npm scripts is that they are essentially an alias for a command line task that you want to run over and over. This means that you can use standard command line code right in your script! This task uses two standard command line features, mkdir and &&.

The first half of this task, mkdir -p dist/js says: Create a folder structure (mkdir), but only if it doesn’t exist already (-p flag). Once that completes successfully, run the uglifyjs command. The && lets you chain multiple commands together, running each one sequentially if the previous command completes successfully.

The second half of this task tells uglifyjs to start with all of the JS files (`*.js`) in `src/js/`, apply the “mangle” command (-m flag), and output the result to `dist/js/app.js`. Once again, check the documentation for the tool in question for a full list of options.

Let’s update our uglify task to create a compressed version of `dist/js/app.js`. Chain another uglifyjs command and passing the “compress” (-c) flag:

"scripts": {
  ...
  "uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js && uglifyjs src/js/*.js -m -c -o dist/js/app.min.js"
}

Compressing Images

Let’s now turn our attention to compressing images. According to httparchive.org, the average page weight of the top 1000 URLs on the internet is 1.9mb, with images accounting for 1.1mb of that total. One of the best things you can do to increase page speed is reduce the size of your images.

Install imagemin-cli:

npm i -D imagemin-cli

Imagemin is great because it will compress most types of images, including GIF, JPG, PNG and SVG. You can pass it a folder of images and it will crunch all of them, like so:

"scripts": {
  ...
  "imagemin": "imagemin src/images dist/images -p",
}

This task tells imagemin to find and compress all images in `src/images` and put them in `dist/images`. The -p flag is passed to create “progressive” images when possible. Check the documentation for all available options.

SVG Sprites

The buzz surrounding SVG has increased in the last few years, and for good reason. They are crisp on all devices, editable with CSS, and screen reader friendly. However, SVG editing software usually leaves extraneous and unnecessary code. Luckily, svgo can help by removing all of that (we’ll install it below).

You can also automate the process of combining and spriting your SVGs to make a single SVG file (more on that technique here). To automate this process, we can install svg-sprite-generator.

npm i -D svgo svg-sprite-generator

The pattern is probably familiar to you now: once installed, add a task in your `package.json` scripts object:

"scripts": {
  ...
  "icons": "svgo -f src/images/icons && mkdir -p dist/images && svg-sprite-generate -d src/images/icons -o dist/images/icons.svg"
}

Notice the icons task does three things, based on the presence of two && directives. First, we use svgo, passing a folder (-f flag) of SVGs; this will compress all SVGs inside the folder. Second, we’ll make the dist/images folder if it doesn’t already exist (using the mkdir -p command). Finally, we use svg-sprite-generator, passing it a folder of SVGs (-d flag) and a path where we want the SVG sprite to output (-o flag).

Serve and Automatically Inject Changes with BrowserSync

One of the last pieces to the puzzle is BrowserSync. A few of the things it can do: start a local server, automatically inject updated files into any connected browser, and sync clicks & scrolls between browsers. Install it and add a task:

npm i -D browser-sync
"scripts": {
  ...
  "serve": "browser-sync start --server --files 'dist/css/*.css, dist/js/*.js'"
}

Our BrowserSync task starts a server (--server flag) using the current path as the root by default. The --files flag tells BrowserSync to watch any CSS or JS file in the `dist` folder; whenever something in there changes, automatically inject the changed file(s) into the page.

You can open multiple browsers (even on different devices) and they will all get updated file changes in real time!

Grouping tasks

With all of the tasks from above, we’re able to:

  • Compile SCSS to CSS and automatically add vendor prefixes
  • Lint and uglify JavaScript
  • Compress images
  • Convert a folder of SVGs to a single SVG sprite
  • Start a local server and automatically inject changes into any browser connected to the server

Let’s not stop there!

Combining CSS tasks

Let’s add a task that combines the two CSS related tasks (preprocessing Sass and running Autoprefixer), so we don’t have to run each one separately:

"scripts": {
  ...
  "build:css": "npm run scss && npm run autoprefixer"
}

When you run npm run build:css, it will tell the command line to run npm run scss; when it completes successfully, it will then (&&) run npm run autoprefixer.

Just like with our build:css task, we can chain our JavaScript tasks together to make it easier to run:

Combining JavaScript tasks

"scripts": {
  ...
  "build:js": "npm run lint && npm run uglify"
}

Now, we can call npm run build:js to lint, concatenate and uglify our JavaScript in one step!

Combine remaining tasks

We can do the same thing for our image tasks, as well as a task that combines all build tasks into one:

"scripts": {
  ...
  "build:images": "npm run imagemin && npm run icons",
  "build:all": "npm run build:css && npm run build:js && npm run build:images",
}

Watching for changes

Up until this point, our tasks require to make changes to a file, switch back to the command line and run the corresponding task(s). One of the most useful things we can do is add tasks that watch for changes that run tasks automatically when files change. To do this, I recommend using onchange. Install as usual:

npm i -D onchange

Let’s set up watch tasks for CSS and JavaScript:

"scripts": {
  ...
  "watch:css": "onchange 'src/scss/*.scss' -- npm run build:css",
  "watch:js": "onchange 'src/js/*.js' -- npm run build:js",
}

Here’s the breakdown on these tasks: onchange expects you to pass a path as a string to the files you want to watch. We’ll pass our source SCSS and JS files to watch. The command we want to run comes after the --, and it will run any time a file in the path given is added, changed or deleted.

Let’s add one more watch command to finish off our npm scripts build process.

Install one more package, parallelshell:

npm i -D parallelshell

Once again, add a new task to the scripts object:

"scripts": {
  ...
  "watch:all": "parallelshell 'npm run serve' 'npm run watch:css' 'npm run watch:js'"
}

parallelshell takes multiple strings, which we’ll pass multiple npm run tasks to run.

Why use parallelshell to combine multiple tasks instead of using && like in previous tasks? At first, I tried this. The problem is that && chains commands together and waits for each command to finish successfully before starting the next. However, since we are running watch commands, they never finish! We’d be stuck in an endless loop.

Therefore, using parallelshell enables us to run multiple watch commands simultaneously.

This task fires up a server with BrowserSync using the npm run serve task. Then, it starts our watch commands for both CSS and JavaScript files. Any time a CSS or JavaScript file changes, the watch task performs a respective build task; since BrowserSync is set up to watch for changes in the `dist` folder, it automatically injects new files into any browser connected to it’s URL. Sweet!

Other useful tasks

npm comes with lots of baked in tasks that you can hook into. Let’s write one more task leveraging one of these built scripts.

"scripts": {
  ...
  "postinstall": "npm run watch:all"
}

postinstall runs immediately after you run npm install in your command line. This is a nice-to-have especially when working on teams; when someone clones your project and runs npm install, our watch:all tasks starts immediately. They’ll automatically have a server started, a browser window opened and files being watched for changes.

Wrap Up

Whew! We made it! I hope you’ve been able to learn a few things about using npm scripts as a build process and the command line in general.

Just in case you missed it, I’ve created an npm-build-boilerplate project with all of these tasks that you can use as a starting point. If you have questions or comments, please tweet at me or leave a comment below. I’d be glad to help where I can!