gulp.js編:一歩踏み出すフロントエンド入門
Git編:一歩踏み出すフロントエンド入門
デザインのプロセス
ES6のある星に生まれて
最新バックエンド技術つまみ食い
情熱の大陸
bot開発日記
かとえみのRaspberryPiロボット入門
21cafeイベントレポート
大宇宙アプリ講座
LINEスタンプを作ろう
【Webライター・編集者】最初の一歩
動画で学びます。
ランボー 怒りのマーケティング
マーケティングツール100本ノック
【評判のいい転職サイト】忙しい人にもおすすめ
働き方インタビュー(オフィス編)
働き方インタビュー(マーケター編)
なるほど労務
働き方インタビュー(経営者編)
みっちー・よすけの「俺のラーメン道」
断食、はじめました
スポットライトのうらがわ
お役立ち!旅系まとめ
勢古口のよくわかるシリーズ
LIGブログ編集部のロングバケーション
パーヤンぶたゑの無料案内所
台湾は最高だ…わん
LIGブログにダメ出ししてください
LIGのハロウィン物語
先日Developers Summitデビューしました。こんにちは、先生です。
前回公開した記事「エンジニアがいい感じにフロントエンド開発を爆速化できる環境構築の手順」の反響が大きかったので、そこで使われているWebPackというModule Bundlerをもう少し深く掘り下げていきたいと思います。
WebPackは静的なファイルの依存関係を解決しつつ結合したり分割したりするツールです。非常に多機能でカスタマイズの幅が広いのが特徴です。
http://webpack.github.io/docs/
個人的な経緯ですが、require.js -> Browserifyを経てWebPackに落ち着いたところです。
WebPackはnpmを使ってインストールします。
npm install webpack -g
※ npmが使えない方はまずNode.jsをインストールしてください。
【参考】「Gulp入門 – Node.jsをインストール」
今日はWebPackでできる代表的な以下の機能をご紹介します。
今回のdemo用のファイルをGithubに用意してあります。
こちらのソースと合わせてご覧いただけると良いかと思います。
上のソースの中の[demo1]をご覧ください。
https://github.com/frontainer/ligblog-sample/tree/master/webpack/demo1/
以下のような分割されたJavaScriptを用意します。
var sub = require('./sub'); sub('test');
module.exports = function(str) { alert(str); };
以下のコマンドでJSを結合します。
webpack app.js dist/app.js
distディレクトリにapp.jsができました。
これを実行するとalertでtestと表示されます。
このようにパッと結合だけ使うだけならすごく簡単です。
次に[demo2]をご覧ください。
https://github.com/frontainer/ligblog-sample/tree/master/webpack/demo2/
今度は結合させつつ、複数ファイルを出力したいと思います。
想定としては以下のような感じです。
dist/app.js <- サイト全体で使う dist/index.js <- indexページで使う dist/detail.js <- detailページで使う
全体で使うものと、個別のページで使うものと分けたものを作ります。
以下のようなスクリプトを用意します。
var sub = require('./sub'); sub('app');
var sub = require('./sub'); sub(‘index');
var sub = require('./sub'); sub(‘detail');
module.exports = function(str) { alert(str); };
WebPackでは毎回コマンドでオプションを指定しなくても良いように、設定ファイルを作っておくことができます。
スクリプトと同じ階層にwebpack.config.jsという名前で、以下のように記述したものを配置します。
module.exports = { entry: { app: './app.js', index: './index.js', detail: './detail.js' }, output: { path: __dirname + "/dist", filename: "[name].js" } };
そして以下のコマンドを実行します。
webpack
WebPackコマンドを実行したときに、実行時のディレクトリにwebpack.config.jsがあった場合には自動的に読みに行ってくれます。
これで複数のJSファイルが出力されます。
dist/app.js dist/index.js dist/detail.js
実行結果
このように複数のファイルを同時に出力できるところもWebPackの魅力の1つです。
しかし、出力されたファイルの中を見てみるとapp.jsとindex.js、detail.jsには同じwebpackで出力された関数が記述されています。
/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/
<script src="dist/app.js"></script> <script src="dist/index.js"></script>
このように使いたいのに、ちょっと無駄ですよね。
そこでプラグインを使ってこの共通関数を出力する先を限定してみます。
webpack.config.jsを以下のように書き換えます。
var webpack = require('webpack'); module.exports = { entry: { app: './app.js', index: './index.js', detail: './detail.js' }, output: { path: __dirname + "/dist", filename: "[name].js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin('app','app.js') ] };
このままではWebPackがないよと怒られてしまいますので、現在のディレクトリにWebPackをインストールします。
npm install webpack —save-dev
そして再びWebPackコマンドを実行すると…
webpackJsonp([2],[ /* 0 */ /***/ function(module, exports, __webpack_require__) { var sub = __webpack_require__(1); sub('index'); /***/ } ]);
app.jsにだけwebpack関数が記述され、index.jsからは消えました。
webpack.config.jsにはたくさん設定項目があります。
すべて把握するまで時間がかかるかと思いますが、soucemapを出力したりエイリアスを設定したりなど便利な設定がたくさんあるのでぜひ目を通してみてください。
Webpack – configuration
http://webpack.github.io/docs/configuration.html
次に[demo3]をご覧ください。
https://github.com/frontainer/ligblog-sample/tree/master/webpack/demo3/
さて、ここまではすべて1つのファイルにしてきましたが、大きなシステムになってくるとサイズがどんどん大きくなってしまい、初期のレスポンスが悪くなってしまいます。
そこで今度は分割したJSファイルを呼ばれたときに非同期で読み込んで実行するようにしたいと思います。(要するにrequire.js的に使うということです)
以下のようなスクリプトを用意します。
window.setTimeout(function() { require.ensure([],function(sub) { var sub = require('./sub'); sub('test'); }); },3000);
module.exports = function(str) { alert(str); };
webpack.config.jsはこのようにしておきます。
module.exports = { entry: './app.js', output: { path: __dirname + "/dist", filename: "bundle.js" } };
WebPackコマンドを実行します。
webpack
3秒後にtestとalertが表示されます。demo1との違いは、Chrome Dev Toolを開いてみると分かります。
若干分かりにくいですが、3秒後の処理を実行したタイミングでスクリプトが読み込まれています。
WebPackでは結合するところと分割するところを分けて出力することができるので、パフォーマンスの調整などが非常にやりやすくなっています。
次に[demo4]をご覧ください。
https://github.com/frontainer/ligblog-sample/tree/master/webpack/demo4/
今度はWebPackのloaderを使って、JS内にHTMLを埋め込んでみます。HTMLを読み込むためにhtml-loaderを使います。
npm install html-loader —save-dev
html-loaderをインストールしたらwebpack.config.jsを以下のように修正します。
module.exports = { entry: './app.js', output: { path: __dirname + "/dist", filename: "bundle.js" }, module: { loaders: [ { test: /\.html$/, loader: 'html-loader' }, ] }, };
続いて埋め込みたいHTMLを作成します。
今回はこんな感じのものを用意しました。
<div> <header> <h1>title</h1> <nav> <ul> <li><a href="">a</a></li> <li><a href="">b</a></li> <li><a href="">c</a></li> <li><a href="">d</a></li> <li><a href="">e</a></li> </ul> </nav> </header> <section> <h2>sub title</h2> <p>content</p> </section> <footer> copyright </footer> </div>
そしてapp.jsに以下のように記述します。
var html = require('./static.html'); document.body.innerHTML = html;
記述したらWebPackコマンドを実行します。
webpack
すると、以下のように実行されます。
生成されたJSを見ると、HTMLが文字列にされているのが分かります。
module.exports = "<div>\n <header>\n <h1>title</h1>\n <nav>\n <ul>\n <li><a href=\"\"></a></li>\n <li><a href=\"\"></a></li>\n <li><a href=\"\"></a></li>\n <li><a href=\"\"></a></li>\n <li><a href=\"\"></a></li>\n </ul>\n </nav>\n </header>\n <section>\n <h2>sub title</h2>\n <p>content</p>\n </section>\n <footer>\n copyright\n </footer>\n</div>";
これによって外部のHTMLとして編集し、 WebPackを使ってJSに埋め込んでテンプレートなどとして使うことができます。
Emmetやエディタのハイライトが活用できますし、unserscore.jsのテンプレートをJSに直接埋め込むこともできて大変便利です。
AngularJSを使っている場合にはdirectiveのtemplateに渡したり$templateCacheに渡したりが簡単になります。
次に[demo5]をご覧ください。
https://github.com/frontainer/ligblog-sample/tree/master/webpack/demo5/
次はHTMLだけでなくCSSも埋め込んでみます。今度はcss-loaderとstyle-loaderを使います。
npm install css-loader —save-dev npm install style-loader —save-dev
webpack.config.jsを以下のように修正します。
module.exports = { entry: './app.js', output: { path: __dirname + "/dist", filename: "bundle.js" }, module: { loaders: [ { test: /\.html$/, loader: 'html-loader' }, { test: /\.css$/, loaders: ['style-loader','css-loader'] }, ] }, };
埋め込みたいCSSも用意しましょう。
header { border-bottom: 1px solid #ccc; } nav ul { list-style: none; padding: 0; } nav ul li { float: left; } section { clear: both; } footer { font-size: 10px; }
そしてapp.jsに以下のように記述します。
require('./style.css'); var html = require('./static.html'); document.body.innerHTML = html;
記述したらWebPackコマンドを実行します。
webpack
すると、以下のように実行されます。
埋め込まれたHTMLにスタイルがあたっているのが分かります。生成されたJSを見ると、CSSも文字列にされています。
exports.push([module.id, "header {\n border-bottom: 1px solid #ccc;\n}\nnav ul {\n list-style: none;\n padding: 0;\n}\nnav ul li {\n float: left;\n}\nsection {\n clear: both;\n}\nfooter {\n font-size: 10px;\n}", ""]);
css-loaderでcssを文字列としてJSに埋め込み、style-loaderを使うことでhead内にstyleとして出力されるようになります。
これを活用するとHTML/CSS/JSを1セットにしたコンポーネントを作ることができます。
コンポーネントを組み合わせて作るようなコンテンツで力を発揮する機能です。IDをうまく振ればWebComponentsライクに使うこともできると思います。
次に[demo6]をご覧ください。
https://github.com/frontainer/ligblog-sample/tree/master/webpack/demo6/
最後に画像もJSファイルに埋め込んでみます。画像ファイルを埋め込むためにurl-loaderを使います。
npm install url-loader —save-dev
webpack.config.jsを以下のように修正します。
module.exports = { entry: './app.js', output: { path: __dirname + "/dist", filename: "bundle.js" }, module: { loaders: [ { test: /\.html$/, loader: 'html-loader' }, { test: /\.css$/, loaders: ['style-loader','css-loader'] }, { test: /\.png$/, loaders: ['url-loader'] }, ] }, };
埋め込みたい画像を用意します。
そしてapp.jsに以下のように記述します。
require('./style.css'); var html = require('./static.html'); document.body.innerHTML = html; var img = new Image(); img.src = require('./image.png'); document.body.appendChild(img);
記述したらWebPackコマンドを実行します。
webpack
生成されたJSを見ると、Base64化された画像が文字列として埋め込まれています。
これをimg要素のsrcに渡してあげると表示させることができます。
module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAeIElEQVR42u2dDa8d1XWG+Qf+CfkJ+QmRDZQSotzKKRSwwKEIDIVwHVs1Hw6+JCStTB2uKHETmhbXoUVqG+eq(略)"
DevToolで見てみると画像がリクエストを投げずに表示されているのが分かります。
画像の埋め込みは使う場面を選びますが、例えばローディング画像など決まったものや要素を覆うカバー画像などを埋め込んでしまい、HTTPリクエストを減らすなどの活用が考えられます。
いかがでしたでしょうか。
今回紹介した機能以外にもJSを圧縮したり、画像を圧縮したり、CoffeeScriptやTypeScriptをコンパイルしてみたり、React.jsを使った場合にはjsxをコンパイルしたりなど、多くのことができます。
多機能ゆえの学習コストはかかりますが、これを使いこなしてあらゆる状況に対応していけると面白いのではないでしょうか。
ぜひ試してみてください。
Enjoy WebPack!!
【先生のフロントエンド講座】
※ エンジニアがいい感じにフロントエンド開発を爆速化できる環境構築の手順
※ フロントエンド開発を裏から支えるデバッグアプリケーション4選
※ Gulp.js入門 – コーディングを10倍速くする環境を作る方法まとめ
殿堂入りの記事