bowerが非推奨になったので別れを告げることを決意した

# はじめに

パッケージ管理に[bower](https://bower.io)を使っているプロジェクトがあったのですが、つい先日公式からこんな通達が...

- [Add yarn and webpack recommendation from bower.io to readme Fixes #2298](https://github.com/bower/bower/pull/2458)

これによって、bowerを使おうとすると

> ..psst! While Bower is maintained, we recommend [yarn](https://yarnpkg.com/) and [webpack](https://webpack.js.org/) for new front-end projects!

という不穏な警告が出るようになりました。
うーん、以前から「bowerはオワコン」みたいに言われてましたが、これは本格的に廃止すべきですねぇ。
bowerが絡むとwebpack2.0系がうまく動かなかったりしたし。
というわけで、bowerをやめてnpmに統合した流れと注意事項を記しておきます。

# 構成

件のプロジェクトのフロントエンド方はこんな構成でした。

1. bowerでパッケージ管理
2. TypeScriptで実装
3. webpackでバンドル
  - 悲しいことにv1系
  - [Bower Webpack Plugin](https://github.com/lpiepiora/bower-webpack-plugin)を使用。これが後々効いてきます。

ちなみに開発環境はwindows。これも伏線です。

# npmへの統合までの道筋

## 1. npmでインストールし直す

まずはbower.jsonに記載されているパッケージを`npm install`していきます。
簡単なお仕事ですね。

...と思っていた時期が私にもありました。
この時点でいくつかトラップがあります。

### 第一の罠 パッケージ名

同一のパッケージなのにbowerとnpmで名前が違うことがあります。
一例を挙げるとこんな感じ。

|githubのリポジトリ|bowerでのパッケージ名|npmでのパッケージ名|
|:--|:--|:--|
|[https://github.com/malsup/blockui](https://github.com/malsup/blockui)|blockUI|block-ui|
|[https://github.com/julianshapiro/velocity](https://github.com/julianshapiro/velocity)|velocity|velocity-animate|
|[https://github.com/nolimits4web/Swiper](https://github.com/nolimits4web/Swiper)|Swiper|swiper|

とりあえずbower.jsonに記載されたパッケージ名で`npm install`を試みて、npmに「無ぇよ」と怒られたらnpmでのパッケージ名を調べましょう。
githubとか見に行けばわかるはずです。

### 第二の罠 バージョンが違う

npmのリポジトリはメンテナンスされてるけど、bowerの方はメンテナンスされてなくて古くなってることがあります。
例を挙げると[D3.js](https://github.com/d3/d3)はnpmだとv4系が最新となってますが、bowerでは未だv3系が最新となってます。
しかもv3系からv4系になったときに、割と破壊的な変更が加えられました。
よって、「よーし、パパD3をbowerからnpmに移行しちゃうぞ!」と張り切って`npm install d3 --save`とかやろうものなら、いきなりv4系になってしまって色々と死にます。
大人しくバージョン情報も含めてbower.jsonからpackage.jsonにコピペしてインストールしていきましょう。

## 2. TypeScriptのimport修正

さて、一通り`npm install`が済んだら前述のとおりパッケージ名が変わってる箇所があるので修正していきます。
tsファイルでグレップかけて、

```ts:before.ts
require("velocity");
```

みたいなの見つけたら

```ts:before.ts
require("velocity-animate");
```

って感じで書き換えていきます。

### 第三の罠 大は小を兼ねない

ここで伏線回収。

velocityかvelocity-animateかみたいにパッケージ名が思いっきり違ってたらまだいいのですが、タチが悪いのがswiperとSwiperの違い。
windowsだとパスの大文字小文字を区別しないので`require("Swiper")`でswiperをインポートできますが、linuxだと大文字小文字は別文字扱いなので「Swiperなんてモジュール無ぇぞ」と憤死します。

## 3. webpackの設定変更

最後にwebpackの設定を変えていきます。
悲しいことにv1系(v2系にしようとしたらBower Webpack Pluginに阻まれました...)だったので、設定ファイルもv1系準拠です。

```js:webpack.config.before.js
const path = require("path");
const webpack = require("webpack");
const current = process.cwd();
const BowerWebpackPlugin = require("bower-webpack-plugin");

module.exports = {

  entry: {
     index: "./src/ts/index/main.ts"
  },
  dest: "./public/javascripts/",
  output: {
    filename: "[name].js"
  },
  resolve: {
    root:[path.join(current, "bower_components")],
    moduleDirectories: ["bower_components"],
    extensions:["", ".webpack.js", "web.js", ".js", ".ts", "css"]
  },
  plugins: [
    new BowerWebpackPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ],
  module: {
    loaders: [
      { test: /\.ts$/, loader: "ts-loader" },
      { test: /\.css$/, loader: "style!css" },
      { test: /\.(jpg|png)$/, loader: "file?name=[path][name].[ext]"}
    ]
  }
};
```

もともとこんな感じだったので

1. 依存関係のrootをnode_modulesにする
2. BowerWebpackPluginを駆逐する

という方向で修正していきます。

```js:webpack.config.js
const path = require("path");
const webpack = require("webpack");
const current = process.cwd();

module.exports = {

  entry: {
     index: "./src/ts/index/main.ts"
  },
  dest: "./public/javascripts/",
  output: {
    filename: "[name].js"
  },
  resolve: {
    root:[path.join(current, "node_modules")],
    moduleDirectories: ["node_modules"],
    extensions:["", ".webpack.js", "web.js", ".js", ".ts", "css"]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ],
  module: {
    loaders: [
      { test: /\.ts$/, loader: "ts-loader" },
      { test: /\.css$/, loader: "style!css" },
      { test: /\.(jpg|png)$/, loader: "file?name=[path][name].[ext]"}
    ]
  }
};

```

そしてビルド実行。エラーは出てない模様。
「よっしゃ、人類の英知の勝利や!」と思いつつ、出来上がったページをブラウザで見てみると...

**あるぇー！？cssが効かなくなってるぞー!?**

はい、例えば[leaflet](https://github.com/Leaflet/Leaflet)のようにパッケージ内でcssや画像が用意されているものがありますが、bowerからインポートしてたときは問題なくcssが適用されていたのに、npmがインポートしたとたんcssは適用されないし画像はNotFoundになって読み込めなくなるという事態が発生しました。

### 第四の罠 Bower Webpack Plugin、思いのほか有能

(githubとか見てもいまいちよくわからなかったので)Bower Webpack Pluginの動作をつぶさに観察すると、どうやらbower_components内でcssを見つけたら、その辺りもcss-loaderとかfile-loaderとか使って勝手にインポートして、パスを解決してたみたいです。
なので、こちらでは特に意識しなくてもパッケージ固有のcssや画像が読み込めてたわけですね。
修正前の設定でビルドしたら、出力先のpublic/javascripts/以下にbower_componentsっていうディレクトリができててcssとか画像とかが入ってたんですけど、全部Bower Webpack Pluginが忖度してくれてたわけですね。
恐ろしい子...!

これを解決するシンプルな手段として、tsファイル内でパッケージとともにcssもインポートします。
時には力業も必要です。


```ts:leaflet-before.ts
import L = require("leaflet");
```

これを

```ts:leaflet-after.ts
declare const require: Function;

import L = require("leaflet");
require("leaflet/dist/leaflet.css");
```

こんな感じにしますが、これだけだと「.css?そんなモジュール無ぇよ!」とTypeScriptに怒られます。
そこで、.cssをモジュールとして定義するというさらなる力業を重ねます。
[TypeScript2 + webpack2でのcss-loader, file-loader周辺をなんとかする](http://qiita.com/inuscript/items/56d2cc15f76df50dfee7)を参考に

```ts:resource.d.ts
declare module "*.css" {
  const classes: {[className: string]: string}; // css-moduleの結果をstring型のobjectに
  export = classes;
}
```

こんな剛腕型定義を作成します。
これで.cssもモジュールとして読み込めます。

## 4. 感謝の意を捧げつつbower_componentsとbower.jsonを削除する

サンキューbower、グッバイbower
君のことは忘れない

さて、webpackをv2系にアップグレードしようか