こんにちは@watildeです。趣味でnpmへのコミットをたまにしているのですが、最近はNode.js側に比重を置いているのでv7のキャッチアップに少し遅れてしまいました。キャッチアップついでに自分なりに情報をまとめたので、v7における代表的な新機能と破壊的変更について、GitHubの記事よりも少し詳しめに紹介と解説を行ってみます。なるべく参考リンクを付けているので、詳細が気になった際はリンクから一次情報に飛んで読んでみてください。
また、情報に誤りがあった場合はTwitterにて教えていただけると助かります。
背景
npmチームより、2020/10/13にnpmの新しいメジャーアップデートであるところのv7の公式リリースが発表されました。8月からbeta版のリリースを繰り返していましたが、2ヶ月かけて内部的なリファクタリングとスモークテストを繰り返して今回の発表に至ったのかと思います。また、正式なGAは来週の火曜日に予定されており、それまではクオリティ調整に使うとのことです。
2020/10/20に予定されているNode.jsの次のメジャーアップデートのv15に同梱される予定ですが、手元でも npm i -g npm@7
を実行することでインストールができます。ただ、メジャーアップデートということで破壊的変更がいくつか含まれているので、内容を精査してから試すことをおすすめします。
詳細については、下記のnpmの作者による記事にまとまっています。

代表的な新機能の紹介
Workspaceによるmonorepoのサポート
Yarnと同様に、package.json
内でworkspaceの場所を指すファイルをworkspaces
プロパティに指定することで、npm install
を実行した際にchildパッケージのdepdendyが同時にインストールされます。
// package.jsonのworkspacesとファイル構成
├── package.json { "workspaces": ["packages/*"] }
└──packages
├── dep-a
│ └── package.json { "dependencies": { "dep-b": "^1.0.0" } }
└── dep-b
└── package.json { "version": "1.3.1" }
// インストールをした後のシンボリックリンクによるファイル構成
$ npm install
├── node_modules
│ ├── dep-a -> ./dep-a
│ └── dep-b -> ./dep-b
└──packages
├── dep-a
│ └── package.json { "dependencies": { "dep-b": "^1.0.0" } }
└── dep-b
└── package.json { "version": "1.3.1" }
実装例: https://github.com/watilde/npm-workspace-example
workspacesプロパティの形式は簡略版も含めて下記の2種類が、パスはglob形式がサポートされてます。
通常版
{ "name": "workspace-example", "version": "1.0.0", "workspaces": { "packages": [ "packages/*" ] } }
簡略版
{ "name": "workspace-example", "version": "1.0.0", "workspaces": [ "packages/*" ] }
npm runのmonorepoサポート
参考: https://github.com/npm/rfcs/blob/latest/implemented/0024-npm-run-traverse-directory-tree.md
yarn.lockのサポート
npm install
を実行した際に、yarn.lockファイルが利用可能であればmetaファイルとして活用されます。cacheファイルは下記の順番で優先されて利用されます。
node_modules/.package-lock.json
npm-shrinkwrap.json
package-lock.json
yarn.lock
参考: https://github.com/npm/cli/blob/latest/docs/content/cli-commands/npm-install.md#description
package-lock.json v2
yarn.lock
がサポートされたにも関わらず、なぜpackage-lock.json
が存在するのか。それは、npm視点でいくつかの要件を満たしていないからです。今回はそのいくつかの要件のうち、再現性の担保について解説します。
yarn.lockにはversionが無く、Yarnコマンドのバージョンが異なるとnode_modules以下のdependency treeに差異が生まれることがある。例えば、以下のような依存関係がある場合。
root -> (foo@1, bar@1)
foo -> (baz@1)
bar -> (baz@2)
dependency treeは下記の2つになる可能性があります。
root
+-- foo
+-- bar
| +-- baz@2
+-- baz@1
~~ OR ~~
+-- foo
| +-- baz@1
+-- bar
+-- baz@2
この差異により、コード上でrequire("baz")
をした際に、v1とv2のどちらが採用されるか分かりません。package-lock.json v2では、dependency treeの再現性を担保した情報を保存します。
参考: https://blog.npmjs.org/post/621733939456933888/npm-v7-series-why-keep-package-lockjson
peerDependencyの自動インストール
package.json内のpeerDependenciesプロパティで管理されているパッケージがnpm install実行時に自動でインストールされるようになります。
参考: https://github.com/npm/rfcs/blob/latest/implemented/0025-install-peer-deps.md
acceptDependenciesの追加
パッケージ開発では通常、ロジックが一切変わっていなくても、サポートするNode.jsのバージョンを上げるためだけの変更でパッケージのバージョンを更新します。acceptDependenciesは、既に他のdependencyで既にインストールされている場合のみ適用されるパッケージを指定することができます。これにより、より効率的なパッケージの再利用が可能となります。
例として、make-dirというパッケージを使って解説します。make-dirは、v1.xではNode.js v4をサポートし、make-dir@2.x
やmake-dir@3.x
ではそれ以上のバージョンのNode.jsをサポートします。
例えば、Node.js v8を使ってアプリケーションを開発していて、package.jsonには下記のような情報が入っているとします。
{ "name": "my-node4-package", "engines": { "node": ">=4" }, "dependencies": { "make-dir": "^1.3.0" }, "acceptDependencies": { "make-dir": "2.x - 3.x" } }
この状態で、npm install
を実行すると下記のように解決されます。
example-app@0.1.0 /usr/src/npm/example-app └─┬ my-node4-package@0.1.0 └─┬ make-dir@1.3.0 └── pify@3.0.0
その後、make-dir
をコードでも利用することになりnpm i make-dir
を実行するとします。v7より前のバージョンだと、下記のように解決されますが、
example-app@0.1.0 /usr/src/npm/example-app ├─┬ make-dir@3.0.0 │ └── semver@6.3.0 └─┬ my-node4-package@0.1.0 └─┬ make-dir@1.3.0 └── pify@3.0.0
v7のacceptDependencies
を活用すると以下のようになります。
example-app@0.1.0 /usr/src/npm/example-app ├─┬ make-dir@3.0.0 │ └── semver@6.3.0 └─┬ my-node4-package@0.1.0 └── make-dir@3.0.0 deduped
この例から、acceptDependenciesのユースケースは限定的であると思っています。
2020/10/15 追記: 背景とユースケースについてリプライ頂いたので紹介
参考: https://github.com/npm/rfcs/blob/latest/implemented/0023-acceptDependencies.md
代表的な破壊的変更の紹介
peerDependencyの挙動変更
新機能でも紹介しましたが、実質これはpeerDependency
のインストールの挙動を破壊的に変更しています。影響範囲は小さいと考えられていますが、ここでは破壊的変更として挙げています。
以前の挙動に戻したい場合は--legacy-peer-deps
オプションを付けてnpm install
を実行することで可能です。karma使ってる人とかpluginのpeerDependencyがsyntax errorになるはずなので利用することになるかと思います。
参考: https://github.com/npm/rfcs/blob/latest/implemented/0025-install-peer-deps.md
npxが実行前に確認
v5.2.0でリリースされたインストールと実行を同時に行うnpx
コマンドですが、独立したpackageとして作られていたものがnpm exec
をコールする実装に内部的には書き換えられました。インストールされていないpackageを実行する際に、下記のように確認を聞かれる挙動が追加されたのが破壊的変更となります。
$ npx create-react-app
Need to install the following packages:
create-react-app
Ok to proceed? (y)
この確認を飛ばしたい場合は、--yes
か-y
オプション指定することで可能です。
その他、npxでの破壊的変更は以下の通り。
npm
configで宣言されている値がnpxに渡される--no-install
オプションは廃止され、--no
オプションとして名前が変更- パッケージが存在しない際のShellへのフォールバックが廃止
-p
オプションは、npxでは--package
の省略版でしたが、npm execでは--parseable
の省略版として扱う(npm exec -p foo
とnpx -p foo
では違う意味合いになる)--ignore-existing
オプションは廃止され、ローカルにインストールされているbinは常にPATHからアクセス可--npm
オプションは廃止され、npxは常に同梱元のnpmを利用--node-arg
オプションと-n
オプションが廃止--always-spawn
オプションが廃止--shell
オプションは--script-shell
オプションに名前が変更(後方互換性を目的に残したので、非推奨とのこと)
参考: https://blog.npmjs.org/post/626173315965468672/npm-v7-series-beta-release-and-semver-major
npm auditの出力形式変更
リファクタリングされ、注意喚起をより効果的に行うように出力内容が変更されました。実際に実行してみると、以下のようになります。
$ npm audit
npm audit report
minimist <0.2.1 || >=1.0.0 <1.2.3
Prototype Pollution - https://npmjs.com/advisories/1179
fix available via npm audit fix --force
Will install minimist@1.2.5, which is outside the stated dependency range
node_modules/minimist
1 low severity vulnerability
To address all issues, run:
npm audit fix --force
また、–jsonオプションを付けた場合の出力内容も変更されたので、npm auditを使ったツール開発をしている場合は実装の変更が必要になります。実際に実行してみると、以下のようになります。
{ "auditReportVersion": 2, "vulnerabilities": { "minimist": { "name": "minimist", "severity": "low", "via": [ { "source": 1179, "name": "minimist", "dependency": "minimist", "title": "Prototype Pollution", "url": "https://npmjs.com/advisories/1179", "severity": "low", "range": "<0.2.1 || >=1.0.0 <1.2.3" } ], "effects": [], "range": "<0.2.1 || >=1.0.0 <1.2.3", "nodes": [ "node_modules/minimist" ], "fixAvailable": { "name": "minimist", "version": "1.2.5", "isSemVerMajor": false } } }, "metadata": { "vulnerabilities": { "info": 0, "low": 1, "moderate": 0, "high": 0, "critical": 0, "total": 1 }, "dependencies": { "prod": 1, "dev": 0, "optional": 0, "peer": 0, "peerOptional": 0, "total": 1 } } }
package.exportsがnpmの内部モジュールを参照できないように
技術的にはpackage.json
のexports
プロパティでnpmの内部モジュールを指定してrequire
が可能だったが廃止。機能として提供すると内部モジュールの実装の変化をするたびに破壊的変更になってしまう、という理由なのかなと予想しています。
--ignore-scripts
の挙動変更
npm test
, npm start
, npm stop
, and npm restart
などのnpm-scriptsが実行された場合、–ignore-scriptsを指定していても実行されるようになります。ただ、pre, postのライフサイクルスクリプトは実行されません。
参考: https://github.com/npm/rfcs/blob/latest/implemented/0029-add-ability-to-skip-hooks.md
npm testの表示内容の変更
npm-scripts内でtestコマンドが指定されていない場合、今まではecho 'Error: no test specified'
がpackage.json
に自動挿入されていましたが、これからはmissing script: test
と表示されます。
npm build
とnpm unbuild
の廃止
元よりinternalなコマンドで、不要となったので廃止されました。