はじめに
パッケージ管理にbowerを使っているプロジェクトがあったのですが、つい先日公式からこんな通達が...
これによって、bowerを使おうとすると
..psst! While Bower is maintained, we recommend yarn and webpack for new front-end projects!
という不穏な警告が出るようになりました。
うーん、以前から「bowerはオワコン」みたいに言われてましたが、これは本格的に廃止すべきですねぇ。
bowerが絡むとwebpack2.0系がうまく動かなかったりしたし。
というわけで、bowerをやめてnpmに統合した流れと注意事項を記しておきます。
構成
件のプロジェクトのフロントエンド方はこんな構成でした。
- bowerでパッケージ管理
- TypeScriptで実装
- webpackでバンドル
- 悲しいことにv1系
- Bower Webpack Pluginを使用。これが後々効いてきます。
ちなみに開発環境は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ファイルでグレップかけて、
require("velocity");
みたいなの見つけたら
require("velocity-animate");
って感じで書き換えていきます。
第三の罠 大は小を兼ねない
ここで伏線回収。
velocityかvelocity-animateかみたいにパッケージ名が思いっきり違ってたらまだいいのですが、タチが悪いのがswiperとSwiperの違い。
windowsだとパスの大文字小文字を区別しないのでrequire("Swiper")
でswiperをインポートできますが、linuxだと大文字小文字は別文字扱いなので「Swiperなんてモジュール無ぇぞ」と憤死します。
3. webpackの設定変更
最後にwebpackの設定を変えていきます。
悲しいことにv1系(v2系にしようとしたらBower Webpack Pluginに阻まれました...)だったので、設定ファイルもv1系準拠です。
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]"}
]
}
};
もともとこんな感じだったので
- 依存関係のrootをnode_modulesにする
- BowerWebpackPluginを駆逐する
という方向で修正していきます。
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もインポートします。
時には力業も必要です。
import L = require("leaflet");
これを
declare const require: Function;
import L = require("leaflet");
require("leaflet/dist/leaflet.css");
こんな感じにしますが、これだけだと「.css?そんなモジュール無ぇよ!」とTypeScriptに怒られます。
そこで、.cssをモジュールとして定義するというさらなる力業を重ねます。
TypeScript2 + webpack2でのcss-loader, file-loader周辺をなんとかするを参考に
declare module "*.css" {
const classes: {[className: string]: string}; // css-moduleの結果をstring型のobjectに
export = classes;
}
こんな剛腕型定義を作成します。
これで.cssもモジュールとして読み込めます。
4. 感謝の意を捧げつつbower_componentsとbower.jsonを削除する
サンキューbower、グッバイbower
君のことは忘れない
さて、webpackをv2系にアップグレードしようか