How to Tame messy NPM scripts in 5 minutes

Adam Crockett 🌀 - Sep 28 '20 - - Dev Community

Problem? Sure, let's describe it, npm scripts are powerful, but in the case where you don't have a task runner and just want to keep it simple, you might opt to use npm scripts to achieve most everything with cli tools, bash and node scripts.

But sooner or later you realize that your scripts have become anything but simple. I'm here to tell you about a 🔥 hot new thing that isn't really new and it's been hidden in npm docs all along, it will help you move the complexity out of your npm scripts and into something you can reason about and maintain.

I have created a pattern (no downloads required) which I coin the ops pattern, so named because A, I couldn't think of a name and B, if you ask yourself what does it do, operations is probably a good description.

The code

package.json (simplified)

{
    ...
    "config" : { "heads" : 7 },
    "scripts": {
        "compile_the_jabberwocky": "./ops.js",
        "minify_the_hydra": "./ops.js"
    }
}
Enter fullscreen mode Exit fullscreen mode

"Adam, you fool, this is going to run the same script!"

Mmm yes but hear me out, you still want all your scripts in the same place, I promise I will show you the JavaScript in a moment, all will become clear, but let's cover the rules of the pattern first.

  • one script
  • use train case for script names because npm uses this for it's variable interpolation, it's the closest thing we have to an official standard... You know what they say, when in rome, don't invent a competing standard.
  • no arguments, prefer "config" and process.env

ops.js

const lifecycle = process.env.npm_lifecycle_event;
const scripts = {
    /*
    * @description such docs!
    * @note normally you can't comment
    */
    "compile_the_jabberwocky"() {
        //... Maybe exec bash or write some js or something else.
        // How about concurrently spawn or handle sync tasks
     },
     /*
     * @description proves you only need the key to match
     */
     "minify_the_hydra"() {
         // Get some params
         const silly_var = process.env.npm_package_heads
        exec('npx run_hydra');
     }
}

if (lifecycle in scripts) {
    scripts[lifecycle]();
}
Enter fullscreen mode Exit fullscreen mode

Closing thoughts

So long as the script name key in the package.json matches the function name, this will work, since all you want to do is to get npm to run some scripts, why not skip that and get JavaScript to run some scripts.

💡 Where did I get the idea from?

"Lastly, the npm_lifecycle_event environment variable is set to whichever stage of the cycle is being executed. So, you could have a single script used for different parts of the process which switches based on what’s currently happening."

I stole it from the docs 😳, so next time you think, I need a task runner, do you really though?

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .