Document Your Project’s package.json File

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(). We should be able to go back to stable when 16.9 releases.
  • @emotion/core – this is only installed to workaround a storybook bug; we should be able to remove it after updating to storybook 5.1.0.
  • knex – knex 0.16.3 is unable 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 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 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 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 configsSeveral prominent ecosystem tools 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, it’s probably after pounding your head against conflicting transitive dependencies. Record what you learned!
  • Browserslist is a convention 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 around to improve on this, but nothing has gained much traction yet.

One approach I’ve seen is to add extraneous entries 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. 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?

Conversation
  • Craig says:

    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.

  • Comments are closed.