Scripts need love too!
Moritz Jacobs
March 4, 2020
One of the most unique and appealing aspects of modern JavaScript development is the tooling that revolves around it. Remember the times when your teammates had strong opinions on indentation and scrutinized each other for checking in “bad” or “ugly” code? Nowadays, ESLint and Prettier take care of that problem by cementing those opinions in configuration and automatically scrutinizing you before you can even compile that code. Testing is another example: we use Jest for unit tests, Cypress for e2e tests and all kinds of pretests, checks and other linters. And we run all of that locally as well as in the CI environment. All that tooling is hard to set up and has a steep learning curve, but once it works, it does a great job of maintaining code quality and maintainability, even in bigger teams.
The typical entry point to these tools is package.json ’s "scripts" section, also known as “npm scripts” (yet they can be executed with yarn and other npm alternatives as well). They provide easy CLI access to all the tooling in your project. But when it comes to maintanability of those scripts, they’re somewhat neglected. If you ever worked in a big and mature JS codebase, you’ve seen it: dozens and dozens of build, lint and test scripts, sometimes hooking/calling each other using pre*or post* prefixes, running stuff sequentially using && and almost always in a seemingly random order that makes it hard to find what you’re looking for. What a nightmare.
Even worse, every scripts section uses different — if any — naming and calling conventions. Maybe npm run start triggers prestart, which runs rm -rf ./dist before Babel transpiles it all and poststart starts a server? Maybenpm run start starts the local dev server plus some kind of watcher? Maybe you have to start that via start:development (or was it start-develop?) — without looking at package.json directly — who knows? You can’t add comments in package.json and there’s hardly ever any README section for the scripts… oh and what if you onboard a new team member who insists on using Windows? rm -rf ./dist will fail on Windows.
test:lint or test-lint, which was it again?
Can we PLEASE find some way to organize this in a consistent way!? Can’t we agree on some best practices!? I hear you scream. Well, your friends at Peerigon have just the right thing to ease your pain.
State of package.json 2020
This is somewhat of a blog post topic in itself, but we took an in-depth look at how people use package scripts. We crawled the package.jsons of the 1000 most depended upon npm modules (there are better metrics out there, if you have the data – this was just easily crawlable) and looked at their "scripts". A few common patterns emerged:
1. People namespace their scripts
You see a lot of build:* or test:* names, which provide two advantages: it groups scripts together both visually and alphabetically (some people maintain alphabetic order using their editors sort-line functionality).
2. : is a very common namespace seperator
It’s unclear who started it, but it seems to be the favorite. Another popular option is - .
3. People divide and conquer
To make processes easier to maintain, it is very common to divide a complex script up into smaller parts and then run them sequentially. Mostly this is done by using && — which works on *nix and Windows systems. Another best practice you see is using npm-run-all. Using custom hooks (pre*/post*) is less common but also a valid (yet arguably unintuitive) approach.
4. Some people just don’t care
There are all kinds of best practices reflected in these findings, but also a lot of projects could use some help with their scripts.
Don’t call it a “standard”
As tech people we tend to reach for a “standard” every time we aren’t sure how to do something “right”. There’s this famous xkcd comic about standards that is a testament to software developers’ tendency to invent new standards if the old ones don’t fit their needs.
In this case there was no standard. Naming and designing npm scripts is uncharted territory and as a newcomer, you’re left to your own devices. Senior JS developers have a way of doing things “right” by experience, but no way of explaining why or how.
Still, we wanted to abstain from writing a complicated and abstract technical standard paper that no one is able to read and understand. We want to make it as easy as possible to improve your project’s scripts.
That’s why we developed…
scriptlint
… as a CLI tool — think of a linter for your package scripts — with configurable rules and easily consumable documentation.
Here’s an example in a webpack based frontend project using scriptlint on the shell. Here’s the package file:
If you run scriptlint in this project, you will see a few warnings:
These are the warnings from the non-strict/minimum rules of scriptlint: your package.json must have scripts called dev, start and test and the latter one can’t be the default test script from npm init. So, here’s how we fix those issues:
- Since “starting” the project would probably mean starting the dev server, we rename start-dev to start and alias dev to that. The other way around would also be fine.
- Also, since we do not use any unit testing yet, we define test as a combination of “the code builds without errors” and “ESLint doesn’t complain” as the next best thing. We can add Jest and/or Cypress later, if we want but building, type checking and linting should be part of any test script chain.
Here’s how that looks now:
Run scriptlint again to check:
So now that we have the non-strict rules out of the way, let’s further improve this by running scriptlint --strict. Three new warnings come up:
Let’s start with the first 2 issues, because those are the easiest to fix — by running scriptlint --strict --fix that is! Some of the rules can be autofixed, in this case scriptlint will …
- prepend the script named eslint with other:, the fallback namespace
- sort the scripts alphabetically
Since other:eslint is not a great name, we will manually rename it to test:lint.
The last remaining issue (“Use of unix double ampersand (&&) in script ‘test’ is not allowed […]”) can be fixed by installing npm-run-all as a devDependency and rewriting test like this: run-s build test:lint. A quick check running scriptlint --strict --fix and there you go:
A perfectly “scriptlint standard” compliant scripts section!
In this example we saw about half of the available rules in action. If you want to know what else scriptlint can do for you, check out our Github page! Of course scriptlint is open source (MIT license), available on npm, comes with JS module support, typescript typings, is configurable and extensible (custom rules!). It can be installed locally to your projects as well as globally to your shell. Here’s our installation guide.
We hope it helps your team with maintaining script quality and would love to hear your feedback. Maintaining the “standard”/scriptlint rules should be a community effort!
From Peerigon with ❤️
*Thanks to Tanner Hoisington and Johannes Ewald. *
npm scripts
Tooling
Linting
Code Quality Tools
Read also
Klara, 12/05/2024
Accessibility – An Essential Ingredient in the Batter or the "Icing" on the Cake?
Web Accessibility
Post Mortem
Konsens
Digital Inclusion
Web Development
Digitale Barrierefreiheit
Francesca, Ricarda, 11/21/2024
Top 10 Mistakes to Avoid When Building a Digital Product
MVP development
UX/UI design
product vision
agile process
user engagement
product development
Leonhard, 10/22/2024
Strategies to Quickly Explore a New Codebase
Web App Development
Consulting
Audit