webpackを利用してみる(入門編)

UI開発者 湯浅

この記事はミツエーリンクスアドベントカレンダー2019の5日目の記事です。

Webサイト構築プロジェクトを進めていく中で、HTML/CSS設計やJavaScript開発を複数人で行うケースは多くあります。
そうした場合に、1つのファイルに複数の機能が集中しているとコードの可読性が悪くなり、開発作業の分担も難しくなってしまいます。

この問題の解決策として有効なのが、webpackなどのツールを使用することです。機能ごとにファイルを分割(モジュール化)して開発を進めることができれば、可読性も向上し開発作業の分担もしやすくなります。

webpackとは

webpackとはCSSやJavaScript、画像などWebサイトを構成するあらゆるファイルを1つにまとめるモジュールバンドラーのことです。

webpackを利用して複数ファイルをバンドルすることは以下のような利点があります。

  • 依存関係の解決
    • ファイル間の依存関係を解析し1つのファイルとしてまとめてくれる

  • リクエストの回数を減らす

    • ファイルを1つにまとめることで通信回数を減らすことができる

  • 開発作業の分担がしやすくなる

    • 機能ごとにファイルが分割されていると作業の分担やレビューがしやすくなる

今回は、JavaScriptファイルをwebpackでバンドルする方法を紹介します。

導入までの流れ

環境準備

まずは公式サイトからNode.js 推奨版をインストールします。

任意のフォルダーに移動して以下のコマンドを実行します。
プロジェクトの情報が書かれた、package.jsonが生成されれば成功です。

npm init

続いて、webpack本体をインストールするために以下のコマンドを実行します。

npm i -D webpack webpack-cli

フォルダー構成

以下のようにファイルを配置します。

├─ dist
│  ├─ index.html
│  └─ bundle.js
├─ package.json
├─ src
│  ├─ index.js   (エントリーポイント)
│  └─ modules    (JavaScriptモジュール群)
│    └─ getTime.js
└─ webpack.config.js  (webpackの設定ファイル)

エントリーポイントとは、依存関係を解決する上で基準となるファイルのことを指します。
webpackはエントリーポイントで指定したファイルが依存するモジュール・ライブラリを読み込みます。

これでwebpackを導入する環境が整いました!

webpackでファイルをまとめる手順

それでは、実際にwebpackでファイルをまとめる手順を紹介します。

webpack.config.js(webpackの設定ファイル)

webpackの設定は、webpack.config.jsに記述します。
主な設定として、modeentryoutputの3つがあります。

const path = require('path');

module.exports = {
  // モードの設定
  mode: 'development',

  // エントリーポイントの設定
  entry: `./src/index.js`,

  // ファイルの出力設定
  output: {
    // 出力するファイル名
    filename: "bundle.js",

    //  出力先のパス
    path: path.join(__dirname, 'dist')
  }
};

各設定項目の詳細は以下の通りです。

  • mode
    • developmentproductionnoneが選択可能

  • entry

    • エントリーポイントの設定(今回はindex.js

  • output

    • 出力先のパスやファイル名を指定

modeの指定を変えることで、各種最適化された設定を利用できるようになっています。
例えば、本番を意味するproductionでは圧縮されたファイルが出力されたり、開発を意味するdevelopmentではソースマップが有効になります。

package.jsonで以下のように記述するとnpm-scriptsでwebpackが実行できるようになります。

"scripts": {
  // ...
  "dev": "webpack --mode development --watch",
  "build": "webpack --mode production"
  // ...
}

index.js(エントリーポイント)

エントリーポイントになるindex.jsを記述します。

ES6のimport文を使い、モジュールを読み込むことができます。

// getTime.jsをインポート
import GetTime from './modules/getTime';

// getTime.jsに定義された機能を実行。
const getTime = new GetTime();
getTime.show();

getTime.js(モジュール)

サンプルとして現在時刻のアラートを出すモジュールを作成しました。ソースコードは以下です。

importで読み込んで利用するために、export defaultでモジュールとして定義します。

// export文を使ってGetTimeを定義。
export default class GetTime {
  constructor() {
    this.now = new Date();
    this.hour = this.now.getHours();
    this.min = this.now.getMinutes();
    this.sec = this.now.getSeconds();
  }

  // 現在時刻を表示
  show() {
    alert(`${this.hour}時${this.min}分${this.sec}秒`);
  }
}

残念ながらJavaScriptモジュール(importexport)はレガシーなブラウザでは使用できません。
webpackを使用してそういったブラウザでも解釈できる形に変換する必要があります。
これを解決するのが後述するローダーです。

バンドルされたファイルを出力

コマンドラインで以下のビルドコマンドを入力します。

npm run dev

distディレクトリーの中にbundle.jsが出力されたら成功です。

無事に出力はされましたが、このままではclass構文がレガシーなブラウザではサポートされていないのでエラーになってしまいます。
そこで、ローダーという機能を使います。

ローダー

ローダーとは、画像やCSSといったJavaScript以外のファイルをJavaScriptで扱えるように変換したり、各ファイルに対してバンドル前に処理を実行する機能です。ローダーは自作可能で、さまざまな種類のローダーが存在します。

今回はBabel Loaderを使用して、レガシーなブラウザでも動作するようにコードを変換します。

まずは必要なローダーをインストールするために、以下のコマンドを実行します。

npm i -D babel-loader @babel/core @babel/preset-env

インストールできたら、webpack.config.jsに設定を追記します。

// ローダーの設定
module: {
  rules: [
    {
      // 処理対象ファイル
      test: /\.js$/,

      // 処理対象から外すファイル
      exclude: /node_modules/,
      use: [
        {
          // 利用するローダー
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env', {
                  modules: false
                }
              ]
            ]
          }
        }
      ]
    }
  ]
}

以上で設定は完了です。

再度ビルドコマンドを入力すると、JavaScriptファイルがトランスパイルされ、bundle.jsが出力されます。

他にもeslint-loaderを使ってJavaScriptのコードを検証することもできます。
ローダーは他にもたくさんあるので、状況に応じて使い分けましょう。

パフォーマンス管理

非常に便利なwebpackですが、管理するモジュールが増えたり使用するローダー・プラグインが増えていくとビルドに時間がかかったり、ファイルの容量が肥大していくケースがあります。
そこで現状を可視化するためのツールをいくつかご紹介します。

処理にかかる時間を探る

webpackはビルド時コマンドプロンプトに、処理にかかった総計時間を表示しますが各処理の内訳までは分かりません。

そこでspeed-measure-webpack-pluginを使用します。
以下のコマンドを実行し、必要なパッケージをインストールしましょう。

npm i -D speed-measure-webpack-plugin

webpack.config.jsに以下のように設定します。

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin()

module.exports = smp.wrap({
  entry: {
    // webpack.config.jsの設定を記述
})

この状態でビルドすると、各プラグインとローダーの処理にかかっている時間が表示されます。

speed-measure-webpack-plugin 実行画面

時間がかかっている処理があれば、それがビルド時間遅延の原因かもしれません。

各モジュールの容量を探る

ビルド後のファイルの容量が大きい場合も、各モジュールのサイズが分かればファイルサイズ肥大の原因特定ができます。

そこでwebpack-visualizer-pluginというプラグインを使用すると、バンドルファイルの中身を可視化できます。
webpackにはプラグイン機能というものがあり、バンドル時にさまざまな処理を追加できます。

npm i -D webpack-visualizer-plugin

webpack.config.jsに以下のように設定します。

const Visualizer = require('webpack-visualizer-plugin');

module.exports = {
  // 各種設定を記述

  plugins: [
    new Visualizer({
      filename: './statistics.html'
    })
  ]
}

statistics.htmlが出力され、各モジュールなどの各ファイルサイズと、各ファイルサイズの占める割合が表示されます。

statistics.html 表示画面

カーソルを合わせると以下のように表示されます。

statistics.html 詳細表示画面

パフォーマンス管理についてのツールを2つ紹介しましたが、現状を可視化できるツールは他にもたくさんあります。
パフォーマンス計測関連のローダーやプラグインを早期から導入することで、パフォーマンス低下の要因にいち早く気づくことができ、開発の段階から改善することができるでしょう。

おわりに

今回は入門編ということで登場しませんでしたが、webpack-dev-serverを使用すれば自動的にブラウザがローカル環境を立ち上げ、ファイル保存時に自動的に更新してくれるので、開発作業が楽に進められます。

モジュールバンドラーはwebpackだけでなく、他にもツール(rollup.jsParcelなど)が存在してるので調べてみてもおもしろいかもしれません。

今後も大規模または複雑なフロントエンド開発に遭遇する場面は増えていくと思うので、普段からモジュール化を考慮した開発は意識しておいて損はないでしょう。

webpackには他にもたくさんの機能があるので、これを読んで気になった方は公式のドキュメントをご覧ください。