npm の prepublish と prepare の変遷

# 2025-05-12 追記：いつ prepublishOnly なくなんの？→未定（たぶんやらない）

[Are changes to prepublishOnly and prepublish still planned? #8191](https://github.com/npm/cli/issues/8191) にある通りなんですけど、"ステップ4" と "ステップ5” はおそらく今後も実施されません。

https://github.com/npm/statusboard/issues/267 あたりで[ドキュメントに prepublishOnly も含めたライフサイクルが明記された](https://docs.npmjs.com/cli/v11/using-npm/scripts#life-cycle-operation-order)上で、「Deprecation Note: prepublish」というセクションができているため、わざわざ削除するための作業をする必要性もなさそうに思います。

# 発端

npm@5 になるときに、 [`prepublish` の挙動が大きく変更](https://github.com/npm/npm/issues/10074)されました。
Node.js の 8.x が LTS になった当時、Node.js@6 は npm@3 でしたが、Node.js@8 では npm@5 になるため、
その内容をちゃんと把握できていなかったので、調べました。

[作業して結果をまとめたリポジトリがある](https://github.com/ndxbn/npm_prepbulish_migration_test/)ので、それの日本語訳+アルファ を書きます。

# "ステップ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年後くらいに、メジャーバージョンを上げて（=破壊的変更だと明示して）、 `prepublish` を `prepublishOnly` と同じ挙動になるに変更した状態にします。

ステップ 5 は、

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

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

# TL;DR: 今は `prepublish` は避け、 `prepare` と `prepublishOnly` にしよう
現状は、まだ「移行作業を進めるための段階」です。 `prepare` はまだ"本来の挙動" ではないので、使用は控えるべきです。もともと `prepare` で行っていたタスクは、きちんと `prepublisOnly` と `prepare` のどちらか適切な方に振り分け、「 `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](https://github.com/ndxbn/npm_prepbulish_migration_test/blob/master/.travis.yml) を見ればわかりますが、複数のバージョンで `npm install` と `npm install ./local_module/sub_project` と `npm publish` と `npm pack` を実行してます。
どの npm-script がどのような順番で実行されたか？を眺めて、なんとなく表にしてみました。

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


## 使用する package.json の概要
必要なところは、以下の通りです。

```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-script の移り変わり

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-script の移り変わり

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-script の移り変わり』に書いたのと同じ理由だと思います。


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

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  
===                 | ==  | ==  | ==  | ==  | ==  | ==  | ==  | ==  
サブモジュールのものは、なにも実行されない | --  | --  | --  | --  | --  | --  | --  | --  

[`prepack` と `postpack` は実装されたのが v5.0.0 です。](http://blog.npmjs.org/post/161081169345/v500)

見どころは

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

あたりです。

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

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

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

* `prepublish` は `prepare` の後に実行されるように変更される
* `prepublish` と `prepublishOnly` はお互いの実行に依存していないはずだし、入れ替えても問題ないはず

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

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

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

そんなコマンドでも、 `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 氏の以下のコメント](https://github.com/npm/npm/issues/15454#issuecomment-288606051)に説明されている意図通りになった、という感じです。

> "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` は実行されるべきではありません**。

# まとめ
[この記事の最初の方に書いた通り](https://qiita.com/ndxbn/items/f0cd2b13a3268254f2aa#tldr-%E4%BB%8A%E3%81%AF-prepublish-%E3%81%AF%E9%81%BF%E3%81%91-prepare-%E3%81%A8-prepublishonly-%E3%81%AB%E3%81%97%E3%82%88%E3%81%86)です。

# おまけ

[prepublishOnly should run on `npm pack` という ISSUE](https://github.com/npm/npm/issues/15363) がありました。私も、最初はこの疑問を持ちましたが、やはり同じように疑問に思う人はいる（？）みたいですね。

`npm publish` は、[実際に publish して確認しました](https://www.npmjs.com/package/@ndxbn/npm_prepbulish_migration_test)。ログは、[GitHub のリポジトリに含めています](https://github.com/ndxbn/npm_prepbulish_migration_test/blob/master/misc/npm-publish.md)。