webpackでVSCodeの拡張機能を模倣してみた

# はじめに

こんにちは。ばーんです。

webpackを触り始めていた時に、

「あれ？これもしかしてVSCodeの拡張機能作れんじゃね？」
と思い自身の理解を深めるためにも作成しました。

- VSCode拡張機能のLive serverっぽいの
- VSCode拡張機能のeasy-sassっぽいの
- 画像圧縮(番外編)

を作成しています。それぞれ詰まったポイントとか思い出しながら書いていきます〜

**※主にwebpackの入門的な内容になります
※文中のコードは動作確認していません。GitHubの方は確認しているので [こちら](https://github.com/hashibadaiki/qiita-webpack) をご利用ください**
## webpack使った所感

- とても便利
慣れれば の話ですが。
やってかないと慣れもクソもないので、今後はCRAに頼らず書いていきたいと思います(白目)

- バージョンの管理/把握がとてもしんどい…
最初にReact触る時に言われた「環境構築の難しさ」ってこれなんかな…と思うぐらいにはちょこちょこ詰まりました。
2020/4に公開されてる記事通りにやったらエラーが出るぐらいの感じでした。

- 他のドキュメントに比べてやや見難い
完全に主観ですが。単純に情報量が多いのと他のドキュメントはバージョン移行の時に
「これ変えたからこうやって書き換えてね！」みたいなのわかりやすく配置されてた感があるのですが、
全体を通して少し探しにくいな〜と感じました(僕が未熟だけだった説はあります)

## 基本的なところ

- npm で webpack/webpack-cli の install が必要。save/dev を使用
- `module.exports = {}`の{}内に設定を記述していく
- package.json の scripts を書き換えておく。開発、本番で分けておくと良い

### コアコンセプト

公式にも書かれてますが、下記の6つのポイントさえ抑えればあとはそこからの発展なのかなと感じてます。

1. Entry
2. Output
3. Loaders
4. Plugins
5. Mode
6. Browser Compatibility

#### Entry

エントリーポイントを示す。

#### Output

出力先を指定する。

#### Loaders

webpack はデフォルトでは js と json しか把握できない。
loader を使うとその他の拡張子もバンドルできる。
babel loader/eslint-loader は js を js にしてる。やや例外

```js
// (例) webpack.config.json
const path = require('path');

module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js',
  },
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
};
```

この場合は、
`.txt拡張子のファイルにlaw-loaderを適用して〜`
と指示している。

#### Plugins

そのままの意味。追加の拡張機能。
plugins のプロパティに追加して new する必要がある。

#### Mode

本番か開発かを分ける。
`mode: "production"`
`mode: "development"`
どちらかの記述だけでだいたいいい感じにしてくれる

#### Browser compatibility

ブラウザの互換性について。
ES5まではカバーしてます。
IE8以下は無理。

### 最初に詰まったところ

#### webpack cli

デフォルトでは`webpack.config.js`を起動します。
ファイル名を変えたければ `webpack --config webpack.hoge.js`と --configの記述が必要。

#### [name] の挙動

これはダメ

```js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/js/app.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
  },
};
// => main.jsが出力
// というかこの場合 filename: '[name].js' が filename: 'hoge.js'
// でも main.js で出力されます。
```

これが name の想定通りの挙動

```js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: { app: './src/js/app.js' },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
  },
};
// => app.jsが出力
```

entryに文字列が入ってたりするとmain.jsが出力されちゃいます。

#### バージョン問題

5 系では dev server 立ち上がらず
https://teratail.com/questions/300230

https://zenn.dev/sakito/articles/7002f4d1097a85cb2b31

2020/10にバージョン5出てたから慣れる意味でも5使おう！
と思ったのですが、まだ懸念点がある模様。

世に出てる記事は4系がメインなので「とりあえず触る」程度であれば無理せず4がいいかも。
(イキって5でいくんじゃい！と思ったら無事憤死しました)

## VSCode拡張機能のLive server

`npm i webpack-dev-server`でインストールが必要です。
またcliのコマンドで`webpack-dev-server`が必要です。

設定ファイルの内容は、最低限下記内容があれば動きます。

```js
// webpack.dev.js
const path = require("path");

module.exports = {
  mode: "development",
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    // watchContentBase: trueでcontentBaseの配下にあるファイルの変更を検知します
    watchContentBase: true,
    // open: trueにしておけば起動時にブラウザが開きます
    open: true,
    port: 9000,
  },
};
```

この設定だとページ全体で更新がかかってしまうので、`hot: true`にしてcliにも`--hot`を追加するとhot reloadにできます。

注意点としてはdevServer起動中はファイル出力がされているわけではないので、確認後は出力忘れずに実行が必要です。

あともう1つ注意点が。最初は下記ファイルを共通に書いてマージしてたのですが、上手く動きませんでした。

```js
// webpack.common.js
const path = require("path");

module.exports = {
  entry: {app:"./src/js/app.js"},
  output: {
    // path: path.resolve(__dirname, "dist"),
    // filename: "js/[name].js",
    // にすればOKです
    path: path.resolve(__dirname, "dist/js"),
    filename: "[name].js",
  },
};
```

## VSCode拡張機能のeasy-sass

sass(scss)のコンパイルについてですね。
npm installは各種ローダーとnode-sass本体が必要です。

https://qiita.com/t-motoki/items/4eab2fcd46198c2377b3

設定ファイルはこんな感じ↓

```js
// webpack.common.js
const path = require("path");
// CSSを出力するためのモジュール
// これがあればstyle-loaderはなくてもOKです
// 公式だと三項演算子で使われてましたが今回は省きます
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: { app: "./src/js/app.js" },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        // loaderは逆順から読まれるのでこの順番で。
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      // ここの[name]はentry/outputに準拠します。なのでapp.cssが出力されます
      filename: "[name].css",
    }),
  ],
};

```

ちなみに、下記ファイルも追加しておくと本番と開発でいい感じに出力してくれます。

```js
// webpack.dev.js
const { merge } = require("webpack-merge");
const commonWebpack = require("./webpack.common");
const path = require("path");

module.exports = merge(commonWebpack, {
  mode: "development",
  // こうしておいて`webpack --config webpack.dev.js`すると、scssを変更するたびにコンパイルしてくれます
  watch: true,
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    watchContentBase: true,
    open: true,
    port: 9000,
    hot: true,
  },
});
```

```js
// webpack.prod.js
const { merge } = require("webpack-merge");
const commonWebpack = require("./webpack.common");

// production modeでcssをミニファイっぽくしてくれます。
// webpack --config webpack.prod.js で動きます。
// 両方scriptsに登録しておくと楽ですね
module.exports = merge(commonWebpack, {
  mode: "production",
});
```

## 画像圧縮(番外編)

上記2つは「最初すごく便利で使ってたな〜壊れやすかった(すぐ止まる)し作ったろ！」
のテンションで作ったのですが、そーいえば画像圧縮もちまちまやってたな…
と思い出しまして、webpack使ったら一気に圧縮できるんちゃう？

と思って作りました。

https://tinyjpg.com/

ここでちまちまドラッグして作ってましたが、今見るとDev APIってあるしFreeで500って書いてるから、普通にこれ利用した方がいいですね。
(実行結果もTinyjpgの方が軽かった)

ただせっかく作ったので設定ファイル載せときます↓

```js
// webpack.config.js
const path = require("path");
// ファイルをコピーするプラグイン
const CopyPlugin = require("copy-webpack-plugin");
// 写真を圧縮するプラグイン
const ImageminPlugin = require("imagemin-webpack-plugin").default;

module.exports = {
  mode: "production",
  entry: { app: "./src/js/app.js" },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "js/[name].js",
  },
  plugins: [
    new CopyPlugin({
      // ネットの記事このpatternsが抜けてるやつありました。最近追加されたみたい
      // ここでコピー元と出力先を定義してます。outputに依存してます。
      patterns: [{ from: "src/img", to: "img" }],
    }),
    new ImageminPlugin({
      bail: false,
      cache: true,
      imageminOptions: {
        // この辺りもう少し細かく設定すればtinyより軽くできますが品質が下がります
        plugins: [
          ["gifsicle", { interlaced: true }],
          ["jpegtran", { progressive: true }],
          ["optipng", { optimizationLevel: 5 }],
          [
            "svgo",
            {
              plugins: [
                {
                  removeViewBox: false,
                },
              ],
            },
          ],
        ],
      },
    }),
  ],
};
```

## さいごに

最後まで読んでいただきまことにありがとうございました！

あくまで今回は最小限の設定なのでここから追加、修正していくのが基本かなと思います。

- cssのベンダープレフィックスを指定したバージョンだけつけれる
- eslintの設定をチーム間で統一できる & buildコマンドと合わせることで、強制的にlintの検閲ができる
- 開発はconsole.log()残して本番は削除する
- cssの無駄な(重複や意味のないコード)記述を修正してくれる

などなど。たくさんのことができるのでここを起点に広げていきたいと思います。

今回書いたコードは [こちら](https://github.com/hashibadaiki/qiita-webpack) で公開してますので、ご自由にお使いください〜

それでは！

## 参考サイト一覧

公式サイト
https://webpack.js.org/

Udemyの元記事 分かりやすすぎました
https://qiita.com/soarflat/items/28bf799f7e0335b68186

hot reload の参考記事
https://blog.websandbag.com/entry/2018/01/24/230000

全体的に参考にした
https://qiita.com/riversun/items/d27f6d3ab7aaa119deab

easy-sass の参考サイト
https://qiita.com/t-motoki/items/4eab2fcd46198c2377b3