The package.json file is the heart of any Node.js project, but it often goes entirely undocumented. In this post, I’ll review a few areas that are worth documenting, and how I like to do it.
## What to Document
### Packages and versions
The package.json file’s largest responsibility is to specify all the packages on which the project depends. Most of them won’t be very interesting, but it’s occasionally worth explaining why a specific package or version is in use. Here are a few real-world examples from my current project:
- `react` - we're on the 16.9 alpha in order to use
[async act()](https://github.com/facebook/react/pull/14853).
We should be able to go back to stable when 16.9 releases.
- `@emotion/core` - this is only installed to workaround a
[storybook bug](https://github.com/storybooks/storybook/issues/5817);
we should be able to remove it after updating to storybook 5.1.0.
- `knex` - knex 0.16.3 is [unable](https://github.com/tgriesser/knex/issues/3003)
to automatically discover `knexfile.ts` the way previous versions
could. For now we're specifying `--knexfile knexfile.ts` all over
the place; hopefully that can go away in some future version.
You may have noticed a pattern: I try to leave instructions for my future self, along with actions to take and the conditions that trigger them.
A few other hypothetical situations I’ve documented in the past:
- We’re holding _foo_ back to version _1.2.3_ because we haven’t gotten around to migrating to v2, which comes with breaking changes x, y, and z.
- Upgrading _bar_ beyond _3.4.5_ causes test failures. I haven’t investigated them yet.
- We’re using our own [fork][open_source_basics_post] of the _baz_ package to add feature _X_. Here’s our fork (link), and here’s the PR where we’re upstreaming the changes (link).
You should [thoughtfully consider][case_against_dependencies] adding third-party dependencies to your system. Put those thoughts somewhere that future developers can find them!
### Scripts
For better or worse, package.json’s [scripts] section is typically home to a bunch of project-specific developer tooling. These are often self-explanatory shell one-liners or invocations of third-party tools, and you can usually get by with just a name. (Want to guess what my project’s `db:migrate` does?)
What’s harder to represent is the story behind decisions that are encoded in these scripts, e.g.:
`test:unit:ci` sets `–maxWorkers 3` to avoid out-of-memory in CI.
Or how to use them:
- After running `yarn test:unit:debug`, connect VS Code with `⌘⇧P`, “attach to process.”
- Build a [bundle size][webpack-analyzer] visualization with `ANALYZE=true yarn build:production`.
- `DEBUG=”knex:tx” yarn test:unit` to log database transactions during a unit test.
### The rest
The unrestricted nature of package.json leads to arbitrary additions by various tools in the ecosystem. A few examples that may merit explanation:
- **Tool configs** [Several][babel] [prominent][eslint] [ecosystem][prettier] [tools][jest] support placing their whole configuration in special keys of package.json. Configuration like this may represent a significant effort investment, like team consensus around lint rules or carefully-constructed compiler settings. Leave a paper trail!
- **Package resolutions** If you find yourself using [these][resolutions], it’s probably after pounding your head against conflicting transitive dependencies. Record what you learned!
- **Browserslist** is a [convention][browserslist] for specifying intended browser support, supported by multiple tools to e.g. generate code supporting old browsers. Document how you arrived at your browser support policy and your plans for the future.
## But How?
JSON famously does not support any kind of comment syntax. There are ideas [floating][json5] [around][hjson] to improve on this, but nothing has gained much traction yet.
One approach I’ve seen is to add [extraneous entries][gross_comments] or duplicate keys:
{
"//": "nobody is using '//' so we'll put comments in it",
"foo": "this will be ignored in favor of the next line",
"foo": "^5.0.2",
}
But then your comments are limited to valid strings, and file-rewriting tools may rearrange or discard them.
I’ve settled on maintaining a second file–`package.md`–on disk right next to `package.json`. I’ve been doing this for a few years, and it’s worked pretty well. Maybe one day it will become a common convention like `readme.md`, `changelog.md`, or `contributing.md`?
## Explain the Why
Comments should [tell the why][comments_tell_why]. The package.json file densely bottles up a lot of decisions, and JSON’s unsuitability as a human-edited config format shouldn’t stop you from documenting them. Your future self will be grateful!
What non-code areas of your software projects have you found useful to document? How do you do it?
—–
[comments_tell_why]: https://blog.codinghorror.com/code-tells-you-how-comments-tell-you-why/
[Node.js]: https://nodejs.org/
[scripts]: https://yarnpkg.com/en/docs/package-json#toc-scripts
[gross_comments]: http://villageblacksmith.consulting/how-to-add-comments-to-package-dot-json/
[webpack-analyzer]: https://github.com/webpack-contrib/webpack-bundle-analyzer
[json]: https://www.ecma-international.org/publications/standards/Ecma-404.htm
[browserslist]: https://github.com/browserslist/browserslist
[resolutions]: https://yarnpkg.com/en/docs/selective-version-resolutions/
[json5]: https://json5.org/
[hjson]: http://hjson.org/
[node_modules]: https://i.redd.it/tfugj4n3l6ez.png
[open_source_basics_post]: https://spin.atomicobject.com/2016/09/22/open-source-basics-npm-edition/
[jake]: https://jakejs.com/
[case_against_dependencies]: https://spin.atomicobject.com/2018/11/06/case-against-dependencies/
[jest]: https://jestjs.io/docs/en/configuration
[eslint]: https://eslint.org/docs/user-guide/configuring
[prettier]: https://prettier.io/docs/en/configuration.html
[babel]: https://babeljs.io/docs/en/configuration#packagejson
This is the direction I’m leaning. It’s that or having a different format entirely which then generates the package.json.
Having comments/docs for dependencies is critical.