npm
npm-scripts
prepublish

npm prepublish の現状と今後どう変わっていくか

発端

今現在、私は Node.js v6 を使っているので npm 3 を使っているのですが、いい加減 Node.js v8 を使いたいと考え、そうなると npm 5 を使うことになりそうです。
npm 5 へバージョンを上げるに際し、「prepublish がまともな挙動をするように、なだらかに変更かけていくよ、だから気をつけてね」って言われてるのもわかっていたのだけれど、その内容とか現状をちゃんと把握できていなかったので、調べました。

作業して結果をまとめたリポジトリがあるので、それの日本語訳+アルファ を書きます。

"ステップ4" と "ステップ 5" とはなにか

まとめを読むのに必要な用語(?)なので、先に説明しておきます。

https://github.com/npm/npm/issues/10074 で言ってるやつです。

ステップ 4 は、

In a year or so, make a semver-major bump to npm and make prepublish's behavior match prepublishOnly.
1年後くらいに、メジャーバージョンを上げて(=破壊的変更だと明示して)、 prepublishprepublishOnly と同じ挙動になるに変更した状態にします。

ステップ 5 は、

Either then or sometime after that, deprecate prepublishOnly and have just prepare and prepublish.
適当に時間が経った後、 prepublishOnly を非推奨にして(≒削除)、 prepareprepublish だけがある状態にします。

今現在(2018-02-07)は ステップ 3 まで実施済みの状態です。

TL;DR: 今は prepublish は避け、 prepareprepublishOnly にしよう

現状は、まだ「移行作業を進めるための段階」です。 prepare はまだ"本来の挙動" ではないので、使用は控えるべきです。もともと prepare で行っていたタスクは、きちんと prepublisOnlyprepare のどちらか適切な方に振り分け、「 prepare ではなにもしていない状態」にして、次の step 4 に備えましょう。

step 4 が来て prepublish が本来の姿になったら 、速やかに prepublishOnly のものを prepublish へ戻して「 prepublishOnly を使用していない状態」にし、 step 5 へ備えましょう。

調査内容

作業したリポジトリ(どこで、なにを、どうやってやったか)

https://github.com/ndxbn/npm_prepbulish_migration_test/

prepublish と prepare の(現状のパット見よくわからない状態になった)発端的なものは、 https://docs.npmjs.com/misc/scripts#prepublish-and-prepare とか https://github.com/npm/npm/issues/10074 を見てください。

.travis.yml を見ればわかりますが、複数のバージョンで npm installnpm install ./local_module/sub_projectnpm publishnpm pack を実行してます。
どの npm-script がどのような順番で実行されたか?を眺めて、なんとなく表にしてみました。

実行結果は、 https://travis-ci.org/ndxbn/npm_prepbulish_migration_test で見れます。
エラーになってるやつは、 npm publishprivate: true で抑止してるからなので、意図通りです。

使用する package.json の概要

必要なところは、以下の通りです。

{
  "name": "npm_prepbulish_migration_test",
  "private": true,
  "dependencies": {
    "npm_prepbulish_migration_test_sub": "file:local_module/sub_project"
  }
}

この後の表に出てくる、 "main" というのは npm_prepbulish_migration_test (以下、メインモジュール側)、"sub" というのは local_module/sub_project のこと(以下、サブモジュール側)です。

やってみた結果

表の数字は、実行された順番です。

npm install したときに実行される npm-scirpt の移り変わり

npm script stage \ version 2.14.3 2.15.11 3.8.6 3.10.10 4.2.0 5.6.0 step 4 step 5
prepublish main 8 8 8 8 9 7 No No
preinstall main 1 1 5 2 3 1 1 1
install main 6 6 6 6 7 5 5 5
postinstall main 7 7 7 7 8 6 6 6
prepare main No No No No 10 8 7 7
=== == == == == == == == ==
prepublish sub 2 2 1 1 1 No No No
preinstall sub 3 3 2 3 4 2 2 2
install sub 4 4 3 4 5 3 3 3
postinstall sub 5 5 4 5 6 4 4 4
prepare sub No No No No 2 No No No

見どころは、

  • 4.2.0 で prepare が追加され、実行されるようになった
  • 5.6.0 で サブモジュール側で prepublish されなくなった
  • 5.6.0 で サブモジュール側で prepare されなくなった
    • たぶん、「そもそもサブモジュール側でインストール時に prepare するのおかしくね?」ということに気づいた
  • step 4 で メインモジュール側でも prepublish が走らなくなる

あたりです。

npm install した時に サブモジュール側が prepare されないのは、 npm publish するときに prepare された結果のものだけを npm registry にアップロードするため、インストール時にインストールしたものを prepare する必要がないからだと思います。

node_modules にインストールし終わったあとに、 main のほうで prepare が実行されるのは、 prepublish が実行されていたのと同様に「便利だから」だと思います。イマドキの node module はなにかしらの ”ビルドプロセス" があるでしょう。

4.2.0 以前では、サブモジュール側でも prepublish されていたようですが、(それに引きづられて prepare も実行されるようになっていましたが、)こうなった経緯と「これそもそもおかしくね?」という議論までは確認できていません。ごめんなさい。

npm install foo したときに実行される npm-scirpt の移り変わり

npm script stage \ version 2.14.3 2.15.11 3.8.6 3.10.10 4.2.0 5.6.0 step 4 step 5
main module のものは、なにも実行されない -- -- -- -- -- -- -- --
=== == == == == == == == ==
prepublish sub 1 1 1 1 1 No No No
preinstall sub 2 2 2 2 3 1 1 1
install sub 3 3 3 3 4 2 2 2
postinstall sub 4 4 4 4 5 3 3 3
prepare sub No No No No 2 No No No

見どころは、

  • 4.2.0 で prepare が追加され、実行されるようになった
  • 5.6.0 で サブモジュール側で prepublish されなくなった
  • 5.6.0 で サブモジュール側で prepare されなくなった

あたりです。

prepare されなくなった理由は、『 npm install したときに実行される npm-scirpt の移り変わり』に書いたのと同じ理由だと思います。

npm publish したときに実行される npm-scirpt の移り変わり

npm script stage \ version 2.14.3 2.15.11 3.8.6 3.10.10 4.2.0 5.6.0 step 4 step 5
prepublish main 1 1 1 1 1 1 3 2
prepublishOnly main No No No No 3 3 2 No (deleted)
publish main 3 3 3 3 5 7 7 6
postpublish main 4 4 4 4 6 8 8 7
prepack main No No No No No 4 4 3
pack main No No No No No No! No? No?
postpack main No No No No No 5 5 4
prepare main No No No No 2 2 1 1
(is_private) main 2 2 2 2 4 6 6 5
=== == == == == == == == ==
サブモジュールのものは、なにも実行されない -- -- -- -- -- -- -- --

prepackpostpack は実装されたのが v5.0.0 です。

見どころは

  • 5.0.0 から prepackpostpack が導入された(今回のはあまり関係ない)
  • 5.6.0 から step 4 になるときに、 prepublish の実行タイミングが変更される
  • step 5 で、 prepublishOnly が非推奨になる

あたりです。

「5.6.0 から step 4 になるときに、 prepublish の実行タイミングが変更される」のは、

  1. In a year or so, make a semver-major bump to npm and make prepublish's behavior match prepublishOnly. 1年後くらいに、メジャーバージョンを上げて(=破壊的変更だと明示して)、 prepublishprepublishOnly と同じ挙動になるに変更した状態にします。 (再掲)

を根拠に言っています。
これを忠実にやるのであれば、

  • prepublishprepare の後に実行されるように変更される
  • prepublishprepublishOnly はお互いの実行に依存していないはずだし、入れ替えても問題ないはず

の2点から、「step 4 では、 prepublishprepublishOnly の後に実行される」ように変更されるのではないかと予想しています。

npm pack したときに実行される npm-scirpt の移り変わり

npm pack というコマンドは、もしかしたら馴染みのない人が多いかもしれません。
ドキュメントはちゃんとありますし、 prepublish のほうにも「(registry に アップロードしないという意味の)"dry run" がしたかったら、 npm pack を使えばいいんじゃないかな」と下の方に書いてあります。

そんなコマンドでも、 prepublish は実行されていました。

npm script stage \ version 2.14.3 2.15.11 3.8.6 3.10.10 4.2.0 5.6.0 step 4 step 5
prepublish main 1 1 1 1 1 1 No No
prepack main No No No No No 3 2 2
pack main No No No No No No! No? No?
postpack main No No No No No 4 3 3
prepare main No No No No 2 2 1 1
=== == == == == == == == ==
サブモジュールのものは、なにも実行されない -- -- -- -- -- -- -- --

見どころとしては、

  • 4.2.0 で、pack する前に prepare されるようになった
  • step 4 で、 prepublish されなくなる

あたりでしょうか。

iarna 氏の以下のコメントに説明されている意図通りになった、という感じです。

"I want to do build steps that are neccessary to use my module." — use prepare
prepare は、「モジュールを実行できるようにするために、ビルドする」ステップであってほしい。
"I want to do validation steps that stop me from publishing bad code." — use prepublishOnly
("本来の" prepublish という意味で)prepublishOnly は、「おかしなコードを publish しないように、バリデーションするステップ」であってほしい。

npm pack は、 「 npm publish から ファイルのアップロードをする部分を抜いただけのもの」ではありません。単に「 npm publish する時と同じ tar ball を作る」というだけなので、npm pack では prepublish は実行されるべきではありません

まとめ

この記事の最初の方に書いた通りです。

おまけ

prepublishOnly should run on npm pack という ISSUE がありました。私も、最初はこの疑問を持ちましたが、やはり同じように疑問に思う人はいる(?)みたいですね。

npm publish は、実際に publis して確認しました。ログは、GitHub のリポジトリに含めています