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

More than 1 year has passed since last update.


はじめに

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

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


..psst! While Bower is maintained, we recommend yarn and webpack for new front-end projects!


という不穏な警告が出るようになりました。

うーん、以前から「bowerはオワコン」みたいに言われてましたが、これは本格的に廃止すべきですねぇ。

bowerが絡むとwebpack2.0系がうまく動かなかったりしたし。

というわけで、bowerをやめてnpmに統合した流れと注意事項を記しておきます。


構成

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


  1. bowerでパッケージ管理

  2. TypeScriptで実装

  3. webpackでバンドル



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


npmへの統合までの道筋


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

まずはbower.jsonに記載されているパッケージをnpm installしていきます。

簡単なお仕事ですね。

...と思っていた時期が私にもありました。

この時点でいくつかトラップがあります。


第一の罠 パッケージ名

同一のパッケージなのにbowerとnpmで名前が違うことがあります。

一例を挙げるとこんな感じ。

githubのリポジトリ
bowerでのパッケージ名
npmでのパッケージ名

https://github.com/malsup/blockui
blockUI
block-ui

https://github.com/julianshapiro/velocity
velocity
velocity-animate

https://github.com/nolimits4web/Swiper
Swiper
swiper

とりあえずbower.jsonに記載されたパッケージ名でnpm installを試みて、npmに「無ぇよ」と怒られたらnpmでのパッケージ名を調べましょう。

githubとか見に行けばわかるはずです。


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

npmのリポジトリはメンテナンスされてるけど、bowerの方はメンテナンスされてなくて古くなってることがあります。

例を挙げるとD3.jsはnpmだとv4系が最新となってますが、bowerでは未だv3系が最新となってます。

しかもv3系からv4系になったときに、割と破壊的な変更が加えられました。

よって、「よーし、パパD3をbowerからnpmに移行しちゃうぞ!」と張り切ってnpm install d3 --saveとかやろうものなら、いきなりv4系になってしまって色々と死にます。

大人しくバージョン情報も含めてbower.jsonからpackage.jsonにコピペしてインストールしていきましょう。


2. TypeScriptのimport修正

さて、一通りnpm installが済んだら前述のとおりパッケージ名が変わってる箇所があるので修正していきます。

tsファイルでグレップかけて、


before.ts

require("velocity");


みたいなの見つけたら


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系準拠です。


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を駆逐する

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


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のようにパッケージ内で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もインポートします。

時には力業も必要です。


leaflet-before.ts

import L = require("leaflet");


これを


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周辺をなんとかするを参考に


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系にアップグレードしようか