category

ES2015で静的なライブラリ塊をバンドルする

複数のJavascriptモジュール(ファイル)から、単一のライブラリを作りたい場合向けに、webpack・Babel・ES2015のimport / exportを活用してビルドする方式のアウトラインを紹介します。

なお、React.jsを利用した場合の例を記載していますが、React.jsを用いないケースでも構造は同じです。

JSコードのメソッド定義(各実装ファイル)

Reactの公式サンプルなどでは、以下のようにスクリプトが読み込まれると即座に実行される形式になっています(mountNodeはよく使われそうなgetElementById()の例で定義)。

var mountNode = document.getElementById('react_node');

ReactDOM.render(<HelloMessage name="John" />, mountNode);

これをES2015のexportで関数定義する形式に書き換えることで、自動実行ではなく明示的に呼び出す方式になります。

なお、以下の例では変更点を明確にするため、propsを固定文字列のままとしていますが、name={params}という指定に変更することで、関数の引数経由で外部からパラメータを受け取ることも可能です。

export default (params) => {
  var mountNode = document.getElementById('react_node');

  ReactDOM.render(<HelloMessage name="John" />, mountNode);
}

複数のJSファイルをまとめる

複数のJSファイルをバンドルして1つのライブラリにまとめる際、インターフェースとして、複数のファイルに散らばった関数やクラスの実装をまとめるファイルが必要となります。

以下のlib_bundle.jsx(名称はとくに問いません。jsxでも動作している例としてあえてjsxを多様しています)のように、実装を定義した各ファイルからimportして、スルーパスでexportします。

import hello_component from './hello_component.jsx';
import goodbye_component from './goodbye_component.jsx';

export { hello_component, goodbye_component }

webpack.config.jsのライブラリ出力設定

ライブラリとしてのパッケージ化はwebpack.config.jsで設定します。
ライブラリ化に直接関係する設定には以下のようなものがあります。

  • entryでアウトプット、インプットのファイルの対応を設定。ファイル名は任意。インターフェースを定義したファイルでimportしているため、各実装を定義しているJSファイル群を記載する必要はない
  • output.libraryにライブラリのクラス名を指定。
  • output.libraryTargetにモジュール形式を指定。この例では汎用性の高いumdを指定
var webpack = require('webpack');
module.exports = {
  entry: {
    lib_bundle: ['./src/lib_bundle.jsx']
  },
  output: {
    path: __dirname + "/dist",
    filename: "[name].js",
    library: ["Samplelib"],
    libraryTarget: "umd"
  },
  resolve: {
    extensions: ['', '.js', 'jsx']
  },
  module: {
    loaders: [
      {
        test: /.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react']
        }
      }
    ]
  },
};

HTMLからの呼び出し方

webpackのコマンドは、ライブラリ作成のケースで特に追加で必要なオプションなどはありません。単にwebpackなどでビルド可能です。

ライブラリを利用するHTMLでは、以下のようにスクリプトを読み込んだうえで、ライブラリ内に設定した関数を明示的に実行する必要があります。

<script src="dist/lib_bundle.js"></script>
<script>Samplelib.hello_component()</script>

このHTML例からこれまでの設定箇所をふりかえると、lib_bundle.jsとSamplelibがwebpack.config.js、hello_component()がインターフェース用のJSコード(lib_bundle.jsx)で指定したものに対応しています。

また、ブラウザのJavascriptコンソール(ページを表示して、右クリック→「検証」など)で、コマンドラインにSamplelibと入力・実行すると、ライブラリオブジェクトの状態を確認できます。今回のケースではhello_component: (e)のような関数が戻り値オブジェクト内に並んでいれば意図した状態になっています。

本番環境向けの補足

1つのファイルにバンドルしているため、本番環境向けにuglifyなどを利用することでコード量を圧縮できます。

uglifyは、以下のようにwebpack.config.jsのpluginsで設定できます。
また、今回の例のようにReact.jsをライブラリごとバンドルする場合、React.jsじたいもminifyする必要があります。そのための設定は、pluginsのNODE_ENVにproductionを指定している部分です。

var webpack = require('webpack');
module.exports = {
  entry: {
    lib_bundle: ['./src/lib_bundle.jsx']
  },
  output: {
    path: __dirname + "/dist",
    filename: "[name].js",
    library: ["Samplelib"],
    libraryTarget: "umd"
  },
  resolve: {
    extensions: ['', '.js', 'jsx']
  },
  module: {
    loaders: [
      {
        test: /.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react']
        }
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify('production')
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        drop_console: true
      }
    })
  ]
};