What is npm’s prepublish, and why is it so confusing?

In this article I want to explain why this one particular lifecycle script causes so much confusion and debate.

Stephan Bönnemann-Walenta
Greenkeeper Blog

--

Edit: To mitigate the confusing nature of prepublish two new scripts where added to npm as of version 4.0.0. prepublishOnly and prepare.

The process of publishing a package is fragile, because it consists of many tiny steps — each of them important, because the smallest mistake can break your users’ apps and modules. It is therefore important to automate as much of it as possible. npm’s solution for this are lifecycle event hooks, which allow you to execute arbitrary commands before or after something important happens, like the publishing of a package.

prepublish is one of these scripts and — as recommended by the official documentation—it can be used to transpile and minify your code and to fetch “remote resources that your package will use”.

The advantage of doing these things at prepublish time is that they can be done once, in a single place, thus reducing complexity and variability.
– npm documentation

What this implies — at the very least for transpilation—is that the prepublish hook is required to run before you can even consume your package. Given this assumption it only makes sense for npm to execute the prepublish hook during other events of the lifecycle, where a package is about to be consumed, namely npm pack, npm link and npm installand that’s where it’s starting to get confusing. Why would it make sense to run it during npm install when the whole point was to run it “once, in a single place”?

not ALL installs

There are three ways a package can be installed and only one of them is affected. Inside a module you’re developing named root-package you may run …

  1. npm install dependency-package and dependency-package gets installed.
  2. npm install and dependency-package gets installed, because it’s defined inside the the package.json.
  3. npm install and root-package itself gets installed.

Only during the third example the prepublish hook is executed, because you’re working on the package itself and nothing of itself is downloaded from the registry at this point. Hence running prepublish makes your package consumable to you as a developer. In the first two cases a tarball containing the already transpiled/prepared code is downloaded from the registry and prepublish is not executed.

Of course package maintainers want to automate as much as possible and therefore they started using the prepublish hook for more than just the use-cases outlined in the documentation. Given the name prepublish, wouldn’t it make sense to generate a changelog, or similar release meta data at that time?

While it’s certainly very reasonable to automate these things, this turned out to be problematic due to the implicit and — at first — unintuitive side-effects of prepublish. For semantic-release, a module of mine that aims to fully automate releases, I had to detect these cases by re-parsing the arguments passed to npm and explicitly doing nothing then. npm’s own Rebecca Turner wrote in-publish, a module dedicated to do just that.

A part of the semantic-release source code that handles the `prepublish` script not only running pre publish.

There are discussions popping up about how the behavior is all horribly broken and needs to be fixed, but after playing with, hacking and darning this behavior, I came to the conclusion that — despite the name — it’s actually pretty thought through and solid the way it is.

There is more than one way to prevent side effects and as of npm@2.11.0 there is an additional preversion lifecycle event to prepare your releases by the time you change the version number locally. Of course you can still roll your own script, with minimal effort, and wrap npm publish yourself.

Wrapping npm publish inside your own command gives you all the freedom you need.

The real problem I see is, because of prepublish’s implicit nature, the people who benefit from it might not be aware of and unable to advocate for it. When I remember how I first started out downloading other people’s packages as zip files, handling them manually and pasting in commands, I don’t doubt that a lot of beginners would benefit immensely if the packages they’re handling became consumable for them. They don’t need to know how babel, uglify or coffee-script work. Experienced module authors on the other hand might just need to go that one extra line of code in their already sophisticated scripts.

--

--