【Webpack】Webpackについてまとめてみた

# はじめに
## 0. 感想

- Webpackは簡単にいうとフロントエンドのソースコード管理ツールで、CSSやJS等のコードを圧縮または変換する。（SASS → CSS, ECMAScript2015 → JavaScriptなど）
    - Railsエンジニアからすると、Asset Pipelineとやっていることは近い。


- ↑を行うために、様々な **ローダー** や **プラグイン** というものを使用する。



# 概要
## 1. webpackとは？

- JS, CSS, 画像などを一つにまとめるためのツール。
    - モジュールバンドラーと呼ぶ。
        - バンドル ≒ ビルド

### webpack以前

- 各.htmlファイルで `<script>` 要素を列挙していた。
    - 以下のような問題点があった。
        - `<script>` 要素は順番に実行されるので、並び順に注意を払う必要がある。
        - ライブラリ同士やアプリ本体のコードが互いに依存関係にあるような状況が発生してしまう。

```html
<script src='lib.js'></script>
<script src='app.js'></script>
```

### webpack以降

- モジュールバンドラーの利用によって、以下のようなメリットがあった
    1. 自動的に依存性を解決する
        - 一つのファイルにバンドルしてくれるので、HTML側でコードの依存関係を意識することがなく、まとめられたファイルをインポートさえすれば良くなった。
    2. リクエストの回数を抑える
        - 複数ファイルを一つにまとめることで、ブラウザ/サーバー間のリクエスト数を減らすことができた。
    3. 大規模な開発に向いている
        - コードをクラス/関数の単位でモジュール（ファイル）として細かく分割できるので、ここのファイルの見通しが改善され、名前空間を明確に分離できるようになった。

## 2. webpackの仕様

### 2-1. さまざまなモジュール形式を解釈できる。

- CommonJS
    - `module.exports.add = require('hoge')` みたいなやつ。
- AMD
- ES Modules

### 2-2. ローダー/プラグインが豊富。

- ローダー
    - リソースを読み込むためのライブラリ。
        - 画像やスタイルシートなどをバンドルすることが可能。
        - TypeScriptなどのaltJSコンパイルした上でバンドルすることが可能。
- プラグイン
    - webpackそのものの拡張。
        - コードの圧縮（minify）。
        - バンドルしたファイルを実行するためのページの生成。

#### タスクランナー・ビルドツール

- Grunt, gulpなどがある。

### 2-3. フレームワークでの採用実績が増えている。

- Angular, React, Vue.js などのJavaScriptフレームワークのコマンドラインツールでは、内部的にwebpackが利用されている。

## 3. モジュールバンドラーの基本

- 「モジュールを束ねる」という性質上、モジュールの理解が必須。
    - JavaScriptにおけるモジュールとは、即ちファイルのこと。
        - ファイル名がそのままモジュール名を意味する。

- export が指定されていないメンバー（変数/関数/クラス）は、モジュールの外からは参照できない
    - これによって名前の競合を防げる。

- import/export を全てのブラウザーで認識できるわけではないため、モジュール間の依存関係を解釈し、一つに束ねるのがモジュールバンドラーwebpack。

## 4. webpackを利用する

### 4-1. Node.jsのインストール

- webpackは、Node.js上で動作するコマンドラインツール。
    - webpackを利用した開発では、アプリをNode.jsプロジェクトで管理するのが一般的。

### 4-2. package.jsonの作成

- package.jsonはNode.jsの設定ファイル。
    - アプリの基本情報、プロジェクト固有のコマンド、利用しているパッケージ（ライブラリ）などを管理する。
- `npm init -y` というコマンドによって生成される。

### 4-3. webpackのインストール


#### Node.jsプロジェクトにwebpackをインストールする。

- `npm install --save-dev webpack webpack-cli`
    - `--save-dev` オプションは、インストールするパッケージの情報をpackage.jsonに記録しなさい、という意味。 `--save` オプションでインストールされたパッケージは、 package.json内の `dependencies` ブロックに記録される。
        - package.jsonでパッケージ情報をまとめておくことで、あとから別の環境で必要なライブラリを準備したい場合にも `npm install` コマンド一つで再現できる。
            - `--save-dev` オプションは、アプリ開発で利用するツールをインストールする場合に利用する。 `--save` オプションはアプリそのものの実行で利用する。
    - インストールに成功すると、プロジェクトルートに `/node_modules` フォルダーが生成され、配下にパッケージ本体（ライブラリ）が保存される。

## 5. webpackの基本

- /dist/index.html
    - JavaScriptファイルを呼び出すためのページ
- /src/index.js
    - 各モジュールを呼び出し、実行するコード。エントリーポイント。
        - index.htmlがindex.jsを参照する。

- `npx webpack` 
    - プロジェクトローカルにインストールされたパッケージのコマンドを実行。
        - webpacの既定では、/src/index.jsがエントリーポイントとなり、バンドルの結果は/dist/main.jsに出力される。
            - 生成されたmain.jsには
                - モジュールを解決するためのコード
                - エントリーポイント
                - エントリーポイントが依存するコード
                    - が一式含まれており、ページからは最終的な出力ファイル(main.js)をインポートするだけでアプリを実行できる。
                        - `<script src='./main.js'></script>`

## 6. 設定ファイルの基本

- 実際の開発では、#5 で述べたような規定の動作だけでは不足で、「入出力先を変更したい」「ビルドにあたって任意の処理を追加したい」「そもそも処理対象のファイルを限定したい」など、細やかな挙動を指示する必要が出てくる。
    - これらの挙動は `npx webpack` コマンドのオプションとして指定することもできるが、あとで繰り返し実行することを考えれば、設定ファイルとしてまとめておくのが便利かつ一般的。

### 6-1. 設定ファイルの骨組み

- エントリーポイントと出力先ファイルを設定ファイルで指定し、ビルドする。

```js
// webpack.config.js

// 設定ファイルの外枠
module.exports = {
  // エントリーポイント
  entry: './src/index.js',
  output: {
    // 出力先のフォルダ
    path: `${__dirname}/dist`,
    // 出力先のファイル名
    filename: 'main.js'
  },
};
```

```bash
> npx webapck --config webpack.config.js
```

コマンドを実行後、dist/main.jsにエントリーポイントをビルドした内容が出力されていれば、ビルドは成功。

#### ビルドコマンドのショートカット 

- package.json内で設定できる。

```js
...
'script': {
  'build': 'webpack --config webpack.config.js'
}
...
```

```bash
// 以下は同じ
> npm run build
> npx webpack --config webpack.config.js
```

### 6-2. 開発サーバーの導入

- コードを修正する度にビルドコマンドを再実行するのは面倒。
    - webpack-dev-serverを使って自動で再ビルド、ブラウザーのリロードを実行できる。

#### webpack-dev-serverのインストール

```bash 
> npm install --save-dev webpack-dev-server
```

```js
// webpack.config.js

module.exports = {
  entry: './src/index.js',
  output: {
    ...
  },
  devSerevr: {
    contentBase: './dist' // コンテンツの基底パス（検索先）の宣言
  },
};
```

```js
// package.json

'script': {
  'start': 'webpack-dev-server --open', // --open: サーバー起動時にブラウザを開く
  'build': 'webpack --config webpack.config.js'
},
```

```bash
> npm start // npm run startとどちらでも良い。
```

##### 開発サーバーによるビルドについて

- 開発サーバーは、ビルドの結果をメモリー上で管理している。
    - よって、元からある/dist/main.jsには再ビルドの結果は反映されない。ビルドの結果をファイルに保存したい場合には、 `npm run build` コマンドを利用する。

##### watchモードによるファイルの監視

コードの編集時に再コンパイルするだけならば、webpack-dev-serverではなく、webpack本体のwatchモードを利用してもよい。

```js

// package.json
{
  'name': 'basic',
  ...
    'build': 'webpack --watch --config webpack.config.js',
  ...
}
```

- `npm run build` コマンドを実行し、適当なコードを変更してみると、確かに自動的に再ビルドが実施されることが確認できる。
    - 一般的には、開発サーバーを利用した方がブラウザーまで制御できるため便利。
        - ビルド結果をファイルできちんと確認したいなどの状況では、watchモードは重宝される。

##### 本番/開発モードの選択

- webpack.config.jsで `mode` パラメータを指定する。
    - webpack4では必須。

- 開発モード
    - よりデバッグに適したコードを高速に生成する
- 本番モード
    - 不要なコードを削除し、できるだけサイズが小さく(minificationされる)、実行効率の良いコードを生成する。

##### ソースマップの生成

- ビルド前後のコードをマッピングするためのファイル。
    - ソースマップを利用することで、デバッグ時にも、エラー箇所を（バンドル前の）オリジナルのソースコード によって参照できるようになるので、問題の特定がしやすくなる。

- devtoolパラメーターで 'eval-source-map' を使用すると、オリジナルのソースを得ることができる。

## 7. ローダー

- webpackを学ぶということは、ローダーを学ぶことである、と言い換えても良い。

- ローダーはリソースをモジュールに変換する。
    - ローダーを利用することで、JavaScript以外のコード（スタイルシート、画像ファイル）でもバンドルできるように、モジュール（JavaScriptで処理できる形式）に変換してくれる。

### スタイルシートのバンドル - css-loader/style-loader

- css-loader
    - スタイルシートを読み込むためのローダー。
- style-loader
   - スタイルを `<style>` 要素としてページに反映させるためのローダー。

```bash
> npm install --save-dev webpack webpack-cli css-loader style-loader
```

#### ローダーの有効化

- 設定ファイルに対して `module - rules` パラメータを追加する。

```js
// webpack.config.js
module.exports = {
  ...
  modules: {
    rules: [
      {
        test: /.\css$/,  // ローダーを適用するファイル
        use: [  // 適用するローダー
          'style-loader',  // 2. モジュールをページに組み込む
          'css-loader'  // 1. *.cssファイルがモジュール化
        ]
      }
    ]
  }
}
```

### スタイルシートを<link>要素で出力する - ExtractTextPlugin

- style-loader/css-loaderの組み合わせでは、バンドルされたスタイルは `<style>` 要素としてページに埋め込まれる。
    - 一般的には、スタイルシートはページとは別ファイルとし、 `<link>` 要素で埋め込むのが普通。
        - ExtractTextPluginによって、バンドルしたリソースをファイルとして切り出す。

```bash
> npm install --save-dev webpack webpack-cli css-loader style-loader extract-text-webpack-plugin@next
```

```js
// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
  ...
  modules: {
    rules: [
      {
        test: /.\css$/, 
        use: ExtractTextPlugin.extract({ use: 'css-loader' })  // 2. プラグインを割り当てる。
        ]
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('style.css'),  // 1. スタイルシートの出力ファイル名を指定。
  ]
}
```

- プラグインを有効化するには、require関数でインポートした後、pluginsパラメーター配下でプロジェクトに登録する。
- 出力ファイル名には `[name].css` のようにプレイスホルダーを含めることもできる。
- ExtractTextPluginそのものはスタイルシートをモジュール化する機能は持たないので、extractメソッドの引数（useパラメーター）に、そのためのローダーを指定する。

### その他のローダー

#### url-loader

- 画像をバンドルする。
    - 無条件に全ての画像を束ねてしまうのは望ましくない。
        - 理由1: オリジナルファイルよりもサイズが大きくなる
        - 理由2: 解析のためのオーバーヘッドが発生する

- リソースをファイルとして出力し、そのパスを管理する。
    - url-loaderの `optins - limit` パラメーターで、画像サイズが閾値を超えたらファイル出力する、というような設定ができる。

- 本番環境で画像ファイルをCDNに配置する場合、 `output - publicPath `パラメーターを設定する。
    - これによって、ビルド時にurl関数の値をリライトできる。

#### file-loader

- フォントファイルをバンドルする
    - ネット経由でフォントを取得し利用する、Webフォントをバンドルする。


#### babel-loader

- ES2015以降のコードを、バンドル前にBabelによってトランスコンパイルする。
    - ブラウザのサポート率が低いES2015以降の構文を、より低いバージョンのJavaScriptに変換する。

#### ts-loader

- TypeScriptのコードをコンパイルし、JavaScriptコード（既定ではES5のコード）に変換する。

#### sass-loader

- Sass（Syntactically Awesome Stylesheets。制御構文、関数、ミックスインなど、スタイルシートをよりコンパクトにまとめるための言語機能を備えた言語）をコンパイルし、標準的なCSSスタイルシートに変換するためのローダー。
    - css-loader , style-loader , node-sass（Node.js上でSassをコンパイルするためのライブラリ）とのセットで利用する。

#### es-lint-loader

- ESLint（静的コード解析ツール）をwebpack上で実行するためのローダー。
    - いろいろ設定があるらしい。

# 参考

- [速習Webpack](https://wings.msn.to/index.php/-/A-03/WGS-JST-001/)