CLOVER🍀

That was when it all began.

webpackを使ってJavaScriptのビルド/minify(+Bower)

最近のフロントエンド事情には全然詳しくないのですが、ちょっと以下のようなことをやろうかなと思いまして。

  • 複数のJavaScriptをまとめたい
  • まとめたJavaScriptはminifyしたい
  • SourceMapも作成したい
  • Bowerを使って依存関係も解決したい

Bowerで解決しようとしているのは、他に手段をよく知らないのとクライアントサイドのJavaScriptの依存関係を書くのは、Bowerが一般的なのかなと思いまして。

で、こういうことをやろうとした時、webpackとBrowserifyが目につくのですが、今回はwebpackでやってみました。

webpack module bundler

以降に、やったことを書いていきます。なお、Bowerで引き込むライブラリは、jQueryとします。

あとで、Browseriry版も書きました。

Broserifyを使ってJavaScriptのビルド/minify(+Bower) - CLOVER

※このエントリ公開後に、@makingさんから「Bowerいらないのでは?」というツッコミをいただいたので、最後にBowerなし版を追記しました

セットアップ

まずは、以下のコマンドを実行。

$ npm init
$ npm install -g gulp webpack bower
$ npm install gulp gulp-util bower webpack --save-dev
$ bower init
$ bower install jquery --save

バージョン情報。

$ node --version
v4.1.0
$ npm --version
2.14.3
$ bower --version
1.5.2

参考)
http://webpack.github.io/docs/installation.html

webpackを使い倒す - Thujikun blog

対象のスクリプトとHTML

動作確認用のソースコードとして、内容はちょっと微妙ですが、このようなものを用意。

エントリポイントです。
scripts/main.js

var $ = require("jquery");
var init = require("./init");
var messages = require("./messages");

init();

$(function() {
    $("#show-message1").on("click", function() {
        $("#message1").text(messages.get("message1"));
    });
    $("#show-message2").on("click", function() {
        $("#message2").text(messages.get("message2"));
    });
});

scripts/init.js

var $ = require("jquery");
module.exports = function() {
    $(function() {
        $("#title").text("webpackとBowerのテストです");
    });
};

scripts/messages.js

module.exports = {
    msgs: {
        "message1": "Hello Workd",
        "message2": "こんにちは、世界"
    },
    get: function(id) {
        return this.msgs[id];
    }
};

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>webpack &amp; Bower テストページ</title>
</head>
<body>
  <h1 id="title"></h1>
  <div>
    <input id="show-message1" type="button" value="メッセージ1を表示">
    <input id="show-message2" type="button" value="メッセージ2を表示">
  </div>
  <span id="message1"></span><br>
  <span id="message2"></span><br>
  <script type="text/javascript" src="dist/scripts/app.js"></script>
</body>
</html>

gulpfile.jsとwebpack.config.jsを書く

続いて、gulpとwebpackの設定を書いていきます。

まずは、gulpの設定から。
gulpfile.js

var gulp = require("gulp");
var gutil = require("gulp-util");
var webpack = require("webpack");
var webpackConfig = require("./webpack.config.js");

gulp.task("build", function(callback) {
    var config = Object.create(webpackConfig);
    webpack(config, function(err, stats) {
        if(err) throw new gutil.PluginError("webpack", err);
        gutil.log("[webpack]", stats.toString({
            // output options
        }));
        callback();
    });
});

gulp.task("default", ["build"]);

ほぼ、ここのまんまです。今回はwebpack-dev-serverは外していますが。

http://webpack.github.io/docs/usage-with-gulp.html

続いて、webpackの設定。

webpack.config.js 
var path = require("path");
var webpack = require("webpack");

module.exports = {
    cache: true,
    entry: {
        app: "./scripts/main.js"  // エントリポイント
    },
    output: {
		path: path.join(__dirname, "dist/scripts"),  // 出力先
		publicPath: "dist/scripts/",  // HTMLなどから参照する時のパス
		filename: "[name].js",  // 生成されるファイルの名前
		chunkFilename: "[chunkhash].js",
        // sourceMapFilename: "[file].map"
    },
    devtool: "#source-map",  // sourcemapの作成
    resolve: {
        root: [path.join(__dirname, "bower_components")]
    },
    plugins: [
        new webpack.ResolverPlugin(
            new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
        ),
        new webpack.optimize.UglifyJsPlugin()  // minify
    ]
};

resolveの部分と、ResolverPluginの中身でBowerとの連携、UglifyJsPluginでminifyの設定となります。

SourceMapを出力するのは、devtoolの指定です。

参考)
http://webpack.github.io/docs/usage-with-bower.html

https://github.com/webpack/webpack-with-common-libs/blob/master/gulpfile.js
https://github.com/webpack/webpack-with-common-libs/blob/master/webpack.config.js

http://webpack.github.io/docs/configuration.html

webpackでbower使って外部ライブラリの依存解決する

webpack + bowerで依存解決がうまくいかない時はaliasをつかうとよいかも - フロントエンドとコーヒー

webpackを使い倒す - Thujikun blog

実行

この状態でビルドしてみます。

$ gulp
[16:08:31] Using gulpfile ~/xxxxx/gulpfile.js
[16:08:31] Starting 'build'...
[16:08:34] [webpack] Hash: 4cc1371f9c4dbb2c89f2
Version: webpack 1.12.2
Time: 3314ms
     Asset     Size  Chunks             Chunk Names
    app.js  86.1 kB       0  [emitted]  app
app.js.map   704 kB       0  [emitted]  app
chunk    {0} app.js, app.js.map (app) 248 kB [rendered]
    [0] ./scripts/main.js 348 bytes {0} [built]
    [1] ./bower_components/jquery/dist/jquery.js 248 kB {0} [built]
    [2] ./scripts/init.js 136 bytes {0} [built]
    [3] ./scripts/messages.js 167 bytes {0} [built]

WARNING in app.js from UglifyJs
Condition always true [./bower_components/jquery/dist/jquery.js:9170,0]
[16:08:34] Finished 'build' after 3.34 s
[16:08:34] Starting 'default'...
[16:08:34] Finished 'default' after 11 μs

ビルド後のファイルと、SourceMapができています、と。

$ ls -l dist/scripts
合計 776
-rw-rw-r-- 1 xxxxx xxxxx  86161  921 16:08 app.js
-rw-rw-r-- 1 xxxxx xxxxx 704141  921 16:08 app.js.map

あとは、一緒に置いていたindex.htmlをブラウザで開いて確認すれば、動作していることが確認できます。

Bowerなし版

このエントリを公開後、@makingさんにこんなツッコミをいただきました。

ふむ、npmだけで完結できる場合はBowerなくてもいいんですか。それならそれでもよいですね。

ただし…

手順としては、構築時のコマンドがこうなって
※Bower関連がなくなり、jQueryがnpm installになりました

$ npm init
$ npm install -g gulp webpack
$ npm install gulp gulp-util webpack jquery --save-dev

gulpfile.jsは変わりません。
gulpfile.js

var gulp = require("gulp");
var gutil = require("gulp-util");
var webpack = require("webpack");
var webpackConfig = require("./webpack.config.js");

gulp.task("build", function(callback) {
    var config = Object.create(webpackConfig);
    webpack(config, function(err, stats) {
        if(err) throw new gutil.PluginError("webpack", err);
        gutil.log("[webpack]", stats.toString({
            // output options
        }));
        callback();
    });
});

gulp.task("default", ["build"]);

webpackの設定から、Bowerまわりをごっそり落としました。
webpack.config.js

var path = require("path");
var webpack = require("webpack");

module.exports = {
    cache: true,
    entry: {
        app: "./scripts/main.js"  // エントリポイント
    },
    output: {
		path: path.join(__dirname, "dist/scripts"),  // 出力先
		publicPath: "dist/scripts/",  // HTMLなどから参照する時のパス
		filename: "[name].js",  // 生成されるファイルの名前
		chunkFilename: "[chunkhash].js",
        // sourceMapFilename: "[file].map"
    },
    devtool: "#source-map",  // sourcemapの作成
    plugins: [
        new webpack.optimize.UglifyJsPlugin()  // minify
    ]
};

@makingさん、ありがとうございました!