webpackを使い倒す
本エントリーJavaScript Advent Calendar 2014 7日目の記事になります。
webpackとは
概要については最近いろんな方が書いてるいるのでそちらを参考にしていただければつかめるかと。
ようはナウいフロントエンドの依存解決ツールですね。
っていうネタで書こうと思っていたら、昨日yutaponさんが既に書いていたりするので(gulp.jsを使ってフロントエンドのビルドをする【webpack, stylus】)、いいところはパクリ参考にしつつ、もうちょっとwebpackに突っ込んだ内容を書いてみようと思います。
Options
entryとoutput
簡単に試すときはentryは1ファイルだけ指定できればよいですが、実際にプロジェクトで使うとなると複数のファイルを指定しなければならない状況になるかと思います。
webpackのentryはオブジェクト形式に対応していて、outputでそのproperty名を使用して動的に名前をつけることでその問題が解決できます。
1 2 3 4 5 6 7 8 |
|
上記のような設定をすることで結果的に top.bundle.jsとlist.bundle.jsの2ファイルがdestに指定したディレクトリに置かれることになります。
loader
Webpackの大きな特徴としてjs以外のどんなファイルでもloaderさえ使えば読み込むことができる、というのがあります。
上記を見てわかるようにかなりの数のloaderがあるので、いくつかpuckupして紹介します。
html-loader
htmlファイルを読み込んで文字列としてjs内で使用することが可能。underscoreのtemplateなど、フロントでrenderingをするときに非常に便利。
css-loader(sass-loader, stylus-loader)
cssファイルをテキストとして読み込んで、headに埋め込むことが可能。sass-loaderやstylus-loaderの場合compileも同時に行う。angularやreact等を使ってHTMLをcomponentとして扱っている場合に有効。
json-loader
jsonファイルを読み込んで、jsのオブジェクトに変換して使うことが可能。
coffee-loader
coffee scriptで書かれたファイルをjsに変換して読み込むことが可能。
jade-loader
jadeで書かれたファイルをhtmlに変換して読み込むことが可能。
es6-loader
es6で書かれたコードをes5互換にして読み込むことが可能。
url-loader ファイルサイズが小さい場合data-uriに変換して読み込み、大きい場合はそのままpathとして読み込むことが可能。
expose-loader
指定したファイル内のオブジェクトをグローバル変数として外に公開することが可能。詳しくは下の例を見るとわかりやすい。
export-loader
commonJS形式に対応していない(module.exportsがない)jsファイルにmodule.exportsの記述を追加し、内部の変数を外に公開することが可能。
などなどwebpackを強力なツールたらしめているのがloaderです。是非使いこなしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
resolve
extention
読み込む際に拡張子を省略できるようにする。jsはデフォルトで入っている。個人的にはコンポーネントを作成した場合に名前を被らせることが多いのでjs以外の省略はしていない。
1 2 3 4 5 6 |
|
root
requireで読み込むときのrootのpathを指定できる。配列で複数の指定が可能。下記のようなディレクトリ構造の場合
1 2 3 4 5 6 |
|
通常であれば
1 2 3 |
|
としなければならないところを、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
のように記述することが可能。
alias
ファイル単位でaliasをはる。root使えばいらない子のようなきがする。用途としてはbowerでとってきたときのmainに設定されてないファイルを使いたい場合くらいかな。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
plugins
loaderと並んでwebpackの強力な機能。というか一部どっち使えばいいか迷うものもある。後述。
見てわかるように非常に沢山あるので自分も見切れてないです。なので便利なやつだけ一部紹介します。
ResolverPlugin
これとresolveのrootの設定と組わせると、bowerでとってきたmoduleをそのままrequireすることが可能。非常に便利。他の用途もあるんだろうけど知らない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
※ bowerモジュールの中にはbower.jsonをignoreに設定しているものがあるため、bowerが動的に作成する.bower.jsonを参照する方が安全。
ProvidePlugin
指定した変数を他のモジュール内で使用できるようにする。globalには置かない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
jQueryとかAngularのプラグインは、jQueryやangular変数に対してオブジェクトを追加する形となるため、その変数を参照できないとエラーになってしまいます。
そのときこのprovidePluginを使用するとこの問題を解決できるのですが、グローバル変数を生成するexpose-loaderでも解決することができます。
グローバル汚染しない分providePluginを使用する方が望ましいですが、別機能でwebpackに依存しないモジュールが画面内にあったりする場合はexpose-loaderで解決してもよいでしょう。
開発環境向けplugin
HotModuleReplacementPlugin
webpack-dev-serverを使っているときに、画面をリロードすることなくモジュールの差し替えを可能にする。(実験的機能)
NoErrorsPlugin
compile時にエラーが出たらskipする。
本番環境向けplugin
DedupePlugin
被ってるモジュールがいたらひとつにまとめる。
UglifyJsPlugin
compile時にuglifyでminimizeする。
OccurenceOrderPlugin
よく使われるモジュールに降るIDの桁数をより短くすることでよりコードを圧縮する。
AggressiveMergingPlugin
ファイルを細かく分析し、まとめられるところはできるだけまとめてコードを圧縮する。Closure CompilerのADVANCED_OPTIMIZATIONみたいなことはしない。
Webpack Deb Server
webpackが提供している開発環境向けのサーバー。compile時に時間がかかる問題を、修正がかかった箇所だけcompileする方式を採用することで高速化している。
上記で紹介したHotModuleReplacementPluginを使用するとreloadなしにモジュールの再読み込みが可能。
が、これ実際に使おうとすると情報が少なかったりして結構苦戦したので備忘録。
今回サンプルのアプリケーションを作成したときの開発環境、本番環境、webpack dev server、gulp-serveでたてたstaticサーバのそれぞれのconfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
|
ハマりポイント1
webpack dev serverでは”http://localhost:9000/webpack-dev-server/“のようなURLでページを確認することになるため、pathをどう通していいかわからない。
→ と思っていたらiframeで”/webpack-dev-server/“の部分を削ったページを表示していたので、pathに関してはやってみたら問題なかった。
ハマりポイント2
webpack dev serverでは、baseが一箇所しか指定できないため、開発環境でcompileしたファイルだけtmpディレクトリに置き、compileせずにそのまま使うファイルはappディレクトリに置いてどちらも参照する方法がわからない。
→ 別でたてたサーバをcontentBaseに設定することが可能。そのためgulp-serveを使って.tmpとappをrootにしたサーバを立ち上げ、webpack dev serverをそこに向けることができる。
contentBase: "http://localhost:3000",
のところ。
はまりポイント3
2の対策をした場合に、webpack dev serverが動的にcompileをかけるjsファイルの参照の仕方がわからない。
→ jsファイルのみwebpack dev serverの方のportを参照する必要あり。自分の場合”http://localhost:9000/js/top.bundle.js“を参照している。
またその場合に本番環境ではそのままにするわけにはいかないので、自分の場合はgulp-ectでhtmlも本番用と開発用を分けてbuildできるようにした。
ただ正直ここは自動で書き換えられるようにwebpack側で対応入れて欲しい。
はまりポイント4
webpack dev serverのwatchとgulp-watchの住み分けをどうすればいいかわからない。
→ webpack dev serverとwatchを並列で実行してしまえば問題ない。もちろんwatchの方でwebpackで管理しているファイルの監視をする必要はない。
以上、なかなか厄介でしたがとりあえず使えるようになりました。
gruntで使いたい場合はgenerator-react-webpackがやっているので参考にすると良いかもしれない。
AMD形式の読み込み
requireの第二引数のfunctionを入れれば、結果がcallbackで返ってくる。
この後紹介するsampleでは使っていないが、publicPathに設定したディレクトリに非同期で読み込むファイルが置かれる。
requirejsとは違い、非同期で読み込む先のファイルも最適なbuildがかかった状態になるためパフォーマンスがよい。また、複雑なファイル解析はせず、単純にjsonp形式で追加モジュールを読み込むため非常にシンプル。
その他
- webpackでは全てのモジュールが閉じたスコープ内で管理される。(もちろんexpose-loaderを使用しない限り)
- 何度requireしてもキャッシュされた同じ箇所を参照するのみなので、パフォーマンスに悪影響を与えない。例えばurl-loaderで何度画像をrequireしても、文字列として展開される箇所は一箇所。
サンプルアプリケーション
今回紹介したポイントをほとんど抑えたサンプルを作成しました。
やっていることは
- instagramのAPIで画像及びテキストデータ取得。
- pinterest風に並べる。
の2つです。環境は
- gulp
- webpack
- webpack-dev-server
- stylus
- ect
を用いて開発環境と本番環境をそれぞれ別々に構築していて、Backbone、Angular、react等のフレームワークには依存していないので、応用しやすいかなと思います。
ふー、なんとか日にち間に合った。駆け足だったので変なこと書いていたら教えて下さい。使い倒すとか言ってますがまだまだ使い倒せてません><
JavaScript Advent Calendar 2014の8日目は、muyuuさんです!よろしくお願いします!