読者です 読者をやめる 読者になる 読者になる

Engineer's Way

主にソフトウェア関連について色々書くブログです。

BrowserifyからWebpack(バージョン2.2.1)に移行してみた

 

目次

今までのプロジェクトで、ずっとフロントの開発環境としてBrowserifyを使ってきたけど、gulpの記述が悪いのか、
とにかく重たいのでWebpackを試してみることにした。

f:id:matsnow:20170306024225p:plain

1 Webpackの特徴

事前に調べてみた範囲では以下のような特徴があるとのこと。

  1. JSの依存関係を解決できる。
  2. JS以外のファイルもまとめて扱える。
  3. gulpを置き換えることもできる。
  4. ビルド結果を複数にすることが容易。
  5. トランスパイラ機能あり(Sass、TypeScript、Babel)
  6. 全ファイルをjsにするので、cssなどを別扱いにするなら、gulpと組み合わせる方が良い。

2 今回やりたいこと

  • JavaScriptファイルを複数の単位でまとめる。
  • JSONもマージしたい。
  • 難読化、minifyする。
  • ソースマップを作る。
  • watchも機能させる。

3 インストール方法

  1. webpackをnpmインストールする。(以下ではnpm iの代わりにyarn addを使用)
    $ yarn [global] add webpack
    $ yarn [global] add ***-loader
    # ***の部分はbabelやsass、ts、jsonなど。読み込みたいファイルに対応したloaderをインストールする。
    
  2. 開発用サーバをインストールする。(任意)
    $ yarn add webpack-dev-server
    # node 4.7以上が必要。
    
  3. webpack.config.jsを作成する。
  4. コマンドを打つなり、gulpに組み込むなりで動かす。

4 動かし方

4-1 コマンドで動かす方法

  1. webpackを実行する。
    $ webpack
    
  2. watchを実行する。
    $ webpack --progress --colors --watch
    
  3. serverを起動する。
    $ webpack-dev-server --progress —colors
    

4-2 gulpで動かす方法

  1. 「webpack-stream」をnpmインストールする。
  2. 以下のようなgulpタスクを作成する。
    import webpackStream from 'webpack-stream';
    import webpack from 'webpack';
    import webpackConfig from '../webpack.config';  // webpack.config.jsのパスを指定する。
    
    gulp.task('webpack', () => {
    
      webpackConfig.watch = true;  // watchをしたい場合は必要。webpack.config.jsに直接書いてもOK。
    
      return webpackStream(webpackConfig, webpack)
        .on('error', function handleError() { // watch中のエラーで死なないようにする。
          this.emit('end');
        })
        .pipe(gulp.dest(config.sources.dest));
    });
    
  3. gulpを実行する。

5 webpack.config.jsの書き方

5-1 最低限必要な内容

const config = {
  // メインとなるJavaScriptファイル(エントリーポイント)
  entry: './app/js/index.js',

  // ファイルの出力設定
  output: {
    filename: 'bundle.js',
  }
}

5-2 難読化、最適化してみる。

  • webpack.config.jsに以下を追記。
plugins: [
    new webpack.optimize.UglifyJsPlugin({ minimize:true }),
    new webpack.optimize.OccurrenceOrderPlugin()
]

UglifyJsPluginは、gulp-uglifyと同じく難読化とminifyを行ってくれるプラグイン
OccurrenceOrderPluginは、Uglifyされたスクリプト中に出てくるIDの桁数を短くするプラグイン(らしい)。

5-3 ソースマップを使う。

  1. UgilifyJsPluginの引数のオブジェクトに「sourceMap: true」を追加する。
  2. webpack.config.jsに以下を追記。
  devtool: 'inline-source-map'

5-4 作成するファイルを複数に分割する。(browserifyとの大きな違い)

  1. entryをオブジェクトで記述する。
    1. 配列を使うと、マージする順番を指定できる。(GoogleAnalyticsをファイルの最後に追加するなど)
  2. outputのfilenameに[name]をつける。

5-5 共通処理を1つのJSファイルにまとめる。

  1. webpack.optimize.CommonsChunkPluginを使う。

5-6 JSONコンパイル対象にする。(同様に画像やCSSコンパイルできる)

  1. デフォルトで拡張子jsonコンパイル対象なので、気にしなくて良い。
  2. loaderに以下を追加。
  3. { test: /.json$/, loader: ‘json-loader’ },

5-7 CDNの外部ライブラリを使ってみる。

externalsを定義することで、CDNで取得した外部ライブラリを、import無しで使用可能となる。

  externals: {
    '$': 'jQuery',
    'jquery-ui': 'jquery-ui',
  },

5-8 一部のローカルライブラリのimportでエラーが出る場合

今回Amchartsを使おうとしたところ、名前が解決できないというエラーが出てしまった。 webpack.config.jsに以下のようにresolve.aliasを書くと解消できた。

resolve: {
    alias: {
      amcharts3$: path.resolve(__dirname, 'app/js/lib/amcharts/amcharts.js')
    }
  }

6 webpack.config.jsのサンプル

最終的に作って見たwebpack.config.jsはこんな感じ。

const webpack = require('webpack');
const path = require('path');
const env = process.env.NODE_ENV;

const config = {
  // メインとなるJavaScriptファイル(エントリーポイント)
  entry: {
    'bundle': path.resolve(__dirname, '../app/js/index.js'),
    'chart':  path.resolve(__dirname, '../app/js/chart.js'),
  },

  // ファイルの出力設定
  output: {
    filename: '[name].js',
  },

  // amcharts3のimport失敗への対応
  resolve: {
    alias: {
      amcharts3$: path.resolve(__dirname, '../app/js/lib/amcharts/amcharts.js')
    }
  },

  externals: {
    '$': 'jQuery',
    'jquery-ui': 'jquery-ui',
  },

  module: {
    rules: [
      { test: /\.js$/,   loader: 'babel-loader', exclude: /node_modules/ },
      { test: /\.json$/, loader: 'json-loader' },
    ]
  },

  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(env)
    }),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name:     'commons',
      filename: 'commons.js',
      minChunks: Infinity
    })
  ]
};

if (env === 'production') {
  config.plugins.push(new webpack.optimize.UglifyJsPlugin({
    compress: {
      warnings: false
    }
  }));
} else {
  config.plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      sourcemap: true, minimize: true
    })
  );
  config.devtool = 'source-map';
}

module.exports
 = config;