npm とフロントエンドのパッケージ管理の未来

JavaScript 系パッケージマネージャの重複問題

npm は言わずもがな Node.js のパッケージマネージャだが、フロントエンド開発においては Bower も利用するのが一般的になっている。この現状の問題点は、package.jonbower.json という似たような管理ファイルを二重で管理しなければならないということだ。

現状の使い分けをおさらいをしておくと、次のような感じになる。

npm

タスクランナー(Grunt/gulp)・モジュールシステム(browserify/webpack)・テストスイート(karma/testem)などの開発環境系の管理が npm の主なお仕事。インストールされたパッケージは node_modules 内に展開されて、CommonJS スタイルのモジュール管理から利用する。

本題につながる話としては、ブラウザで動くライブラリの一部は npm にも publish されている。

Bower

npm が主に Node.js の世界であるのに対して、Bowr は jQuery や AngularJS、BackboneJS のようなブラウザの世界で動くライブラリを管理するのがお仕事。インストールされたパッケージは bower_components 内に展開されるが、モジュール管理の仕組みはないので他の手段でビルドしたり、bower_components 自体を公開ディレクトリ下に配置したりして扱う。

本題

先週の11/4に npm と フロントエンドのパッケージングについてのブログが公開された。その中では npm とフロントエンドのツール群やパッケージの現状と未来の展望についてが書かれていた。

フロントエンドにとってなくてはならない npm

npm のリポジトリには Node.js で動作するあらゆるパッケージが公開されている。前述したが、フロントエンド開発で使われるタスクランナー・モジュールシステム・テストスイートもこの中に含まれている。

実際に npm でダウンロードされているパッケージの上位50位(全体の50%)までのうち 32% が Grunt, Bower, Gulp といったフロントエンドでよく使われるツール類が占めているそうだ。このことから、フロントエンドと npm の現状における密接な関係がうかがい知れる。

パッケージマネージャの断片化

今日まで npm は主に Node.js のエコシステムとして扱われ、ブラウザで動作するパッケージ(ライブラリ)のほとんどは Bower で公開されてきた。

このダブルスタンダードが package.jsonbower.json という設定ファイルの二重管理という問題をもたらしている。

設定ファイルの二重管理だけではない。再利用できるはずの JavaScript が複数のリポジトリに分散してしまっている状態もある。npm のパッケージの多くは Node.js の実行環境に依存していない限り Browserify を使えばブラウザで実行できる。

npm の提案

「おれおれレジストリはやめて、npm 使いなよ!何とかするから!」

おっ?

「package.json を使えばきみも npm の一員!さもなくば死!」

おう・・・

「npm がフロント用途に向いてないのは努力でカバーだ! Browserify いいよ!」

えっ!

一応、丁寧に言い直すとフロントエンド開発者に向けて次のような提案をしている。

  • npm の安定したレジストリを使う どこの世界線の話だ?
  • メタデータは package.json に格納する
  • keywords フィールドに ecosystem:hapi のようなタグをつけて検索可能にする
  • lifecycle scripts (postinstall など) と Browserify を使って何とかする

フロントエンドの開発を管理する難しさ

レジストリの分散とパッケージの断片化を解決するために npm を統一されたリポジトリの立場に押し上げるというのは自然な発想だが、ジャイアン感があるのに加え、フロントエンドのパッケージを管理すること自体が難しいという問題が残る。

これまで乱立してきたフロントエンドのパッケージマネージャ (参考: wilmoore/frontend-packagers) たちも様々なアプローチをとってきたが、Bower 以外は浸透しなかった。

今の npm ではフロントエンドの依存解決を完璧に行えない

Node.js は packageApackageB@1.0.0 への依存と、packageCpackageB@0.9.0 への依存を両立できる。各パッケージのnode_modules にそれぞれのバージョンが展開されるから基本的には競合しないし、npm dedupe で多少整理する手段も用意される。

フロントエンドでは、異なるバージョンの jQuery を読み込むことは noConflict で回避できなくはないが、リソースを2重に呼び出すこと自体も看過できないロスになる可能性がある。CSSに至っては、Bootstrap の異なるバージョンを同時に読み込めばスタイルは容易に破壊される。こういうときに限って Shadow DOM の必要性が待ったなしになる。

このあたりの事情も、既存の npm のコマンドラインツールの振る舞いのままではフロントエンドの開発を支えきれないという現状を作っている。だからこそ Bower のような専用ツールがある。

このあたりのことは npm Blog でも同様に述べられている

npm は HTML/CSS のパッケージを管理できるのか

フロントエンドであるならば HTML/CSS も管理の対象でなければならない。他のフロントエンドのパッケージマネージャのいくつかは、HTML/CSS の依存管理にも熱心だ。以前に紹介した Component はその代表例だろう。今となっては Duo になっているが引き続き CSS もパッケージインストール&ビルドの対象として扱っている。

この点については、npm が CommonJS のモジュールを支えているのは確かだが、npm にとって CommonJS が必須なわけではない。npm をただのパッケージレジストリとして見なせば、パッケージの中身が JavaScript だけである必要はないので HTML/CSS も配置できる。

しかし、配置できるだけであってそれを適切に運用する手段が npm 自体には用意されていないのが実状だ。

エコシステムの棲み分け問題

もうひとつのポイントは、フロントエンドの依存管理は npm と相性の良い Browserify のエコシステム以外にも、RequireJS の AMD や WebComponents、ES6 modules などさまざまであることだ。

引き当てたパッケージがどのエコシステムで動作するのか、特定のエコシステムで使えるパッケージをどのように探し当てるのか。このエコシステムの棲み分け問題に npm は次のような Ecosystems の仕組みが予定されている。

Introducing ecosystems: subsets of npm packages that all have a common foundation. We expect ecosystems to be rather large and constantly changing; as such they will be maintained programmatically via an API, instead of being hand-curated like collections. The npm Blog — The Future of the npm Website: Let's Map this Road!

いまだと package.json の keywords フィールドに grunt だとか browser のような単語を仕込んで管理するしかない。明示的なサブセットになるコレクションを npm の中に作って管理することが想定されている。publish するときに --subset でも付けるのか、結局 package.json のなかのフィールドのひとつになるのかはよく分からないが、API で機械的にやるそうだ。

npm を活かしたフロントエンドパッケージマネージャ

エコシステムを活かせるレジストリを構築する計画を示した上で、現在の npm という Node.js のためのコマンドラインツールがフロントエンドにマッチしていない点は、個別の API に分解することでフロントエンド用のコマンドラインツールを再実装できる可能性を示唆する。

  1. a API for downloading packages from the registry
  2. a “cache” API that can store, read and unpack packages locally
  3. an installer API that places packages into your project in the right location

The npm Blog — npm and front-end packaging

確かにこれならエコシステムで検索できることさえ担保できれば、npm レジストリで無数のパッケージを共有しつつ、エコシステム毎のパッケージマネージャを作ることすら可能そうだ。

例えばパッケージを ecosystem:webcomponents で npm レジストリから検索・インストールしたのち、専用のパッケージマネージャが都合の良い場所に展開したりビルドシステムと連携させたりすればいい。

このアプローチを見る限り、npm としてはレジストリの分散とパッケージの断片化を避けたいのであって、レジストリにアクセスするツールは都合にあわせて様々あれば良い、というスタンスのようだ。

Bower の反応

こうなってくるとフロントエンドとして気になるのは Bower コミュニティの反応だが、自分の観測範囲では下記の issue と ワザノバで紹介されていた議論の一端ぐらいしか見つかっていない。

まだ空中戦をしている段階のように見受けられるので、このあたりの動静はウォッチャー諸氏のタレコミを待ちたい。

とはいえ前述した npm の機能を API に分解することが行われれば、それらの API を利用してバックエンドに npm リポジトリを備えた新しい Bower が生まれる可能性はあるかもしれない。

2012年ごろにあった過去の経緯的には次の issue も参考になるのかもしれないが目を通してない。:(

後記

package.jsonbower.json の二重管理は、できれば避けたいが死ぬほど困ってるわけではない。npm が Browserify と仲良しなのはあたりまえだが、他のビルドシステム/エコシステムまで npm と無理して仲良くなる必要性も感じていない。

まだまだ npm 側の壮大な夢だなというのが正直な感想。

npm におけるパッケージ名の椅子取りゲームにフロントエンドが本格参戦すると、ますますカオスになりそうだなと不安に思う。あと package.json にメタデータ仕込むのは良いけど、名前の衝突は紳士協定 or プレフィックス回避って感じになりそうで草。

元記事からの読み取りに誤りあったら、ぜひ twitter で mention ください (;´□`人