Opinionated npm Style Guide for teams by De Voorhoede.
This guide provides a set of rules to better manage, test and build your npm modules and project scripts. It should make them
- easier for a new developer to pick up
- reduce friction with different environment configurations
- have a predictable api
- easier to add new scripts
- Use nvm to manage node versions
- Configure your npm personal info
- Use
save exact
option - Specify engines on
package.json
- Avoid installing modules globally
- Use standard script names
- Write atomic scripts
- Use npm modules for system tasks
- Avoid shorthand command flags
- Group related scripts by prefix
- Document your script API
With nvm you can have multiple different versions available and switch to the one that suits better your project.
To install or update nvm, you can use the install script using cURL:
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash
For Windows check nvm for windows.
If everything goes well, you can now install a specific node version.
nvm install stable
nvm install vX.Y.Z
nvm alias default stable
It’s also easy when updating a newer version, copying your existing global modules.
nvm copy-packages <previous-version>
When creating a new package npm init
your defaults will be already included on the scaffolding.
npm config set init-author-name "{name}"
npm config set init-author-email "{email}"
Check npm config docs, for more info.
You can use cat ~/.npmrc
to check your current definitions.
By default, installing a package with the --save
or --save-dev
option, npm saves the package version with ^
prefix, meaning that will update minor versions if available. While this is a good idea as is, this makes it possible for different developers having different versions of the same package and making it harder to debug if there is inconsistency. Defining the save-exact
option prevents this. More info npm config docs.
npm config set save-exact
Specifying engine versions for your module, warns the user if he is not using a supported version. This is specially important for ensuring npm@3 flat tree dependency on Windows, or ES2015 features that your scripts require on node.
In package.json
:
"engines" : {
"node" : "5.10.0",
"npm" : "3.8.5"
}
Preventing the user from using your module is also possible with check-pkg-engines.
NPM first tries globally installed modules before looking for local ones. Globally installed modules are shared between projects and might not match the required version for the project.
- Locally installed modules are custom and specific for the project.
- Locally installed modules are directly accessible via npm scripts.
# recommended: install locally
npm install --save-dev grunt-cli grunt
and use in package.json
:
"scripts": {
"icons": "grunt grunticon"
}
# avoid: don't install modules globally
npm install -g grunt-cli grunt
When you use system specific commands like rm -rf
or &&
, you are locking your tasks to your current operating system. If you want to make your scripts work everywhere think about Windows developers also.
Use npm modules with node that mimic the same tasks but are system agnostic. Some examples:
- create directory (
mkdir
/mkdir -p
) ->mkdirp
- remove files and directories (
rm ...
) ->rimraf
- copy files (
cp ...
) ->ncp
- run multiple scripts in sequence (
... && ...
) or in parallel (... & ...
) ->npm-run-all
- set environment variable (
ENV_VAR = ...
) ->cross-env
npm and npm modules with a command-line interface support different options using fully written out and / or shorthand flags. For instance, instead of npm install --save-dev
you can use the shorter npm i -D
. For npm test
you can use simply npm t
. But npm start
is not the same as npm s
, as that's an alias for npm search
. So while you can use these shorthands in your daily routine, you should avoid them in scripts and documentation shared with other developers.
- Shorthand flags can only be understood by developers who know the modules and options well.
- Fully written out command options help in writing self documented scripts.
- Fully written out command options make scripts more accessible to other developers.
Always prefer fully written command flags over shorthand. Example using uglifyjs:
# recommended
uglify index.js --compress --mangle --reserved '$' --output index.min.js
# avoid
uglifyjs index.js -c -m -r '$' -o index.min.js
npm lets you define custom scripts. You can give these scripts any name you like, but you should stick to standard names when you can.
Using standard script names creates a predictable script API, which makes your project easier to use by other developers. When used consistently between projects a user doesn't even need to read the documentation or package.json
to know which scripts are available.
npm start
and npm test
are predefined aliases for custom scripts. In addition use of build
, deploy
and watch
are widely spread within the developer community. You should use these script names as follows:
npm run build
to create a distribution of your project (mostly for production).npm run deploy
to put your project on a host environment.npm start
(alias fornpm run start
) to start a web server (defaults tonode server.js
).npm test
(alias fornpm run test
) to run project's entire test suite.npm run watch
to run other scripts on files changes.
In package.json
:
/* recommended: standard script names */
{
"scripts": {
"build": "...",
"deploy": "...",
"start": "...",
"test": "...",
"watch": "..."
}
}
/* avoid: */
{
"scripts": {
"bundle": "...",
"upload": "...",
"serve": "...",
"check": "...",
"watcher": "..."
}
}
Each script should be only responsible for one action.
- Atomic scripts are easy to read and understand.
- Atomic scripts are easy to reuse.
Separate each step of the script to an individual script. For example a "generate icon" script can be split into atomic script like "clean directory", "optimize SVGs", "generate PNGs" and "generate data-uris for SVGs".
Bundle your scripts with a prefix so you can execute them all at once.
- Bundling helps keeping your scripts organized.
- Tasks grouped by prefix can be easily executed with one command.
- Your high-level script API remains unchanged when scripts are added, removed or renamed.
In package.json
:
/* recommended: group related scripts by prefix */
scripts: {
"test": "npm run test:eslint && npm run test:unit && npm run test:e2e",
"test:eslint": "eslint src/**/*.js",
"test:unit": "tape --require dist/index.js src/**/*.test.js",
"test:e2e": "karma start test/config.js"
}
/* avoid */
scripts: {
"eslint": "eslint src/**/*.js",
"tape": "tape --require dist/index.js src/**/*.test.js",
"karma": "karma start test/config.js",
}
Bundled scripts can be executed (in parallel or in sequence) using npm-run-all:
/* recommended: use `npm-run-all` to run all bundled scripts */
scripts: {
"test": "npm-run-all test:*",
"test:eslint": "eslint src/**/*.js",
"test:unit": "tape --require dist/index.js src/**/*.test.js",
"test:e2e": "karma start test/config.js"
}
- Documentation provides developers with a high level overview to the script, without the need to go through all its code. This makes a module more accessible and easier to use.
- Documentation formalises the API.
Document your script API in the project's README.md or CONTRIBUTING.md as those are the first places contributors will look. Describe what each task does using a simple table:
`npm run ...` | Description
---|---
task | What it does as a plain human readable description.
An example:
npm run ... |
Description |
---|---|
build |
Compile, bundle and minify all CSS and JS files.. |
build:css |
Compile, autoprefix and minify all CSS files to dist/index.css . |
build:js |
Compile, bundle and minify all JS files to dist/index.js . |
start |
Starts a server on http://localhost:3000 . |
test |
Run all unit and end-to-end tests. |
De Voorhoede waives all rights to this work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.