webpackはちゃんと理解しておいたほうがいい（導入時のTips付き）

最近webpackと仲良くなれたかな…と感じる私ですが、**まだまだわからないことも多いとも感じます。**
そんな奥深いwebpackですが、レガシーな環境にこそ導入していってほしいと考えています。

:::note info
webpackはいろんなことができるので学んでおくことで業務の効率化につながることも多いと思います！
:::

この記事は
- レガシーな環境に導入してみたい！
- もっと早く勉強しておけばよかった…と後悔する人を減らす

を目的に書いております。
1人でも多くの方のお役に立てることを願っています。

## webpackってなに？
JavaScriptのモジュールバンドラーです。
複数あるJavaScriptのファイルを1つにまとめてくれるすごいやつです。
**やってくれることはとてもシンプル**です。

https://webpack.js.org/

JavaScriptをバンドルしてくれるだけではなく、node_modulesを組み合わせることでいろんなことができます。

例えば…
- Sassの変換
- CSSやJavaScriptのチェック
- ライブラリの利用
- ファイルの圧縮

などです。

バージョンが上がる時に破壊的な変更がある場合は少し手間がかかるかもしれません。
ただ、**導入のメリットも多いので使っていない場合に、導入した方が良いと考えます**。

## webpackを使ってみる

さっと使いたいなら公式サイトにある`npx webpack init`を実行すれば順番に質問されます。
回答していくことでいい感じの設定にしてくれるのですぐに利用可能になります。

https://webpack.js.org/configuration/

Node.jsがインストールされている場合npmコマンドが使えると思うので`npm install`でwebpackをインストールできます。

https://nodejs.org/ja/

npmについては別の記事で書いたのでもし気になった方はご覧ください！

https://qiita.com/tanimoto-hikari/private/223e9922e99436a40beb

## webpackのコア・コンセプト
webpackにはコア・コンセプトがあります。

- エントリー
- 出力
- ローダー
- プラグイン
- モード
- ブラウザーの互換性

説明していきます。

### エントリー(Entry)
エントリーポイントはどのファイルを対象にwebpackの処理を走らせるのかを指定できます。
単一はもちろん複数のエントリーポイントを設定できます。

### 出力（Output）
バンドルしたファイルの出力先とファイル名を指定できます。

### ローダー
webpackはJavaScriptとJSONファイルしか理解できません。
他の種類のファイル（CSSファイル）を処理して変換することが可能です。

この時点では概要にとどめておきますが、もっと詳しく知りたい方は下記のページをご覧ください！

https://webpack.js.org/concepts/loaders/

### プラグイン（Plugins）
webpackにはいろんなプラグインがあります！
プラグインはページ上部で`require()`で呼び出して利用します。
呼び出して`plugins:[]`の配列の中に`new()`演算子でインスタンスを作成すると利用できます。

下記はwebpackが提供しているプラグインの一覧です。
https://webpack.js.org/plugins/

### モード（Mode）
モードは「development」「production」「none」があります。
デフォルト値は「production」です。

:::note info
後で詳しく説明しますが、webpackは3つのファイルで構成するのが一般的です。
開発中はdevelopmentで、リリースする前にproductionモードでバンドルできるようにスクリプトを分けておきます。
:::

### ブラウザーの互換性（Browser Compatibility）
ES5に準拠したブラウザーを全てサポートしてくれており、古いブラウザーに対応する場合はpolyfillを読み込むことで昔の記述にも対応することが可能です。

## webpackの書き方
文字だけではわかりづらいと思いますのでここからは実際のコードで説明していきます。

### webpackの基本構成
基本構成は下記のような構造になると思います。

```
.
├── dist
│   ├── js
│   │   └── bundle.js
│   └── css
│       └── bundle.scss
├── node_modules
├── src
│   ├── js
│   │   └── index.js
│   └── css
│       └── index.scss
├── webpack
│   ├── webpack.common.js
│   ├── webpack.dev.js
│   └── webpack.prod.js
└── .eslintrc.js
└── .stylelintrc
└── .env
└── yarn.lock
```

srcはコンパイル前のコードを管理しているフォルダーです。
webpackフォルダーと同じ階層に`package.json`などを管理する設計です。
`.eslintrc.js`や`.stylelintrc`、`.env`ファイルなどの環境変数も同階層で管理する想定です。

#### 各webpackファイルの記述内容
webpackの設定ファイルは分割して`webpack-marge`を利用するのが推奨されています。
そうすること記述を重複させることがなくシンプルに記述できます。

https://webpack.js.org/guides/production/#setup

基本の設定は`webpack.common.js`に記述します。
記事に記述してあるコードを利用される場合は状況に合わせて書き換えて利用してください。

```webpack.common.js
// ファイルやディレクトリのパスを操作することが多いと思うので、nodeのpathモジュールにアクセスできるようにしておきます。
const path = require('path');

// 利用したいプラグインがある場合はファイル上部で読み込みます。（下記は例）
const webpack = require('webpack');
const sass = require('sass');
const CompressionPlugin = require('compression-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: '',
  output: {
    path: '../dist/js/',
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          // ここにloaderとかの設定を記述する
        ],
      },
      {
        test: /\.scss$/,
        use: [
          // ここにloaderとかの設定を記述する
        ],
      },
    ],
  },
  plugins: [
    new webpack.IgnorePlugin({resourceRegExp: /^\.\/locale$/}),
    new MiniCssExtractPlugin({
      filename: '../css/bundle.css',
    }),
    new CompressionPlugin(),
  ]
};
```

```webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
});

```

```webpack.prdod.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'production',
});
```

## こんな時どうするの？webpack設定
私が関わるプロジェクトはさまざまな事情で複雑な設定です。
部分的に導入する時やイレギュラーな実装の参考にしていただければ幸いです！

### デバイス別に処理を走らせたい
`package.json`のscriptsとエントリーポイントの指定を工夫します。
scriptsには`DEVICE=sp`のようにwebpack.common.jsで判別するための値を送ります。
```javascript:package.json
 "build:sp": "DEVICE=sp webpack --config webpack/webpack.prod.js",
```

webpack側ではscriptsで定義した値を受け取ります。
```JavaScript:wenpack.common.js
//　ファイル上部に追記
const DEVICE = process.env.DEVICE;

// pcとspでそれぞれ指定を記述します。
const entry = {
  pc: {
    index: '',
  },
  sp: {
    index: '',
  },
}

// entryの部分を下記のように変更します
module.exports = {
  entry: entry[DEVICE],
}
```

プラグインを分ける時もentryと同じように分けてmodule.exportsの中で`plugins: plugin[DEVICE]`で定義したものを呼び出すことが可能です！

:::note info
コンパイルするscssファイルが複数存在する場合、pluginsのところで下記のように指定するとkeyがそのままファイル名としてコンパイルされます。

```javascript
const entry = {
  pc: {
    index: '',
    common: '',
    bundle: '',
  },
}

new MiniCssExtractPlugin({
  filename: '[name].css',
});

/* コンパイル後のファイル
  index.css
  common.css
  bundle.css
*/
```
:::

### JSファイルとCSSファイルのkeyが被る場合の対処
エントリーポイントを複数設定している場合に発生します。
SCSSファイルをCSSファイルとしてコンパイルさせたい時、JSファイルとCSSファイルでkeyが被るとJSファイルがコンパイルされません。

```javascript:webpack.common.js
// common.jsがコンパイルされない例
const entry = {
  sp: {
    common: './common.js',
    index: './index.js',
    common: './common.scss',
    bundle: './bundle.scss',
  },
};
```

:::note alert
原因は、MiniCssExtractPluginでファイル名が被った場合JSファイルを除外してしまいます…
:::

なので下記のようにscssだけkeyを変えたとします。

```diff_javascript:webpack.common.js
const entry = {
  sp: {
    common: './common.js',
-    common: './common.scss',
+    commonscss: './common.scss',
  },
};
```
コンパイルはされるようになりますが、MiniCssExtractPluginの設定でfilenameを`[name].css`で指定しているため`common.css`ではなく`commonscss.css`で出力されてしまいます。

これを回避するためにMiniCssExtractPluginの設定を書き直します。

```diff_javascript:webpack.common.js
new MiniCssExtractPlugin({
-  filename: '[name].css',
+  filename: (pathData) => {
+     if (pathData.chunk.name === 'commonscss') {
+       return 'common.css';
+     } else {
+       return '[name].css';
+     }
+   },
});
```
filenameはwebpack5だと`pathData => string`の形式で指定できるのでkeyが`commonscss`の時には`common.css`を返すようにしてあげればCSSファイルもcommon.cssで出力することが可能です！

https://webpack.js.org/plugins/mini-css-extract-plugin/#filename

## pathの指定を省略するテクニック
エントリーポイントを複数にすることによってパス指定が冗長的になると思います。
下記のようなにentryを指定されている場合です。

```webpack.common.js
const entry = {
  pc: {
    index: './src/pc/js/index.js',
    common: './src/pc/js/common.js',
    bundle: './src/pc/js/bundle.js',
    style: './src/pc/scss/style.scss',
    commonscss: './src/pc/scss/common.scss',
  },
}
```

同じpathを何度も記述することになり冗長的な印象を受けます。
pathsを定義して同じpathであれば使いまわせるようにします。

```webpack.common.js
const paths = {
  pc: {
    entry: path.join(process.cwd(), 'src', 'pc', 'js'),
    css: path.join(process.cwd(), 'src', 'pc', 'sass'),
  },
}

const entry = {
  pc: {
    index: `${paths[DEVICE].entry}/index.js`,
    common: `${paths[DEVICE].entry}/common.js`,
    bundle: `${paths[DEVICE].entry}/bundle.js`,
    style: `${paths[DEVICE].css}/style.scss`,
    commonscss: `${paths[DEVICE].css}/common.scss`,
  },
}
```

`path.join(process.cwd(), 'src', 'pc', 'js')`だと`./src/pc/js`のようなパスが出来上がります。
これで1つ定義して別のカ所で利用できるので、冗長的で見づらい状態を回避できます。

> `process.cwd()`メソッドは、Node.js プロセスの現在の作業ディレクトリを返します。
https://nodejs.org/api/process.html#processcwd

## まとめ
そんなこともできるのか！？と発見があったなら幸いです。

:::note warn
ただ、webpackの設定は本来もっとシンプルであるべきだと思っています…。
:::

さまざまな記述ができるからこそ、複雑な構造になっていてもwebpackが導入可能です！
構造が複雑なレガシー環境の場合でも諦めず導入にチャレンジしてください！