17
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

このままやったらフロントエンドエンジニアになれへんで!Advent Calendar 2022

Day 14

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

Last updated at Posted at 2022-12-13

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

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

この記事は

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

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

webpackってなに?

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

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

例えば…

  • Sassの変換
  • CSSやJavaScriptのチェック
  • ライブラリの利用
  • ファイルの圧縮

などです。

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

webpackを使ってみる

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

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

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

webpackのコア・コンセプト

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

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

説明していきます。

エントリー(Entry)

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

出力(Output)

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

ローダー

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

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

プラグイン(Plugins)

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

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

モード(Mode)

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

後で詳しく説明しますが、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を利用するのが推奨されています。
そうすること記述を重複させることがなくシンプルに記述できます。

基本の設定は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で判別するための値を送ります。

package.json
 "build:sp": "DEVICE=sp webpack --config webpack/webpack.prod.js",

webpack側ではscriptsで定義した値を受け取ります。

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]で定義したものを呼び出すことが可能です!

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

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ファイルがコンパイルされません。

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

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

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

webpack.common.js
const entry = {
  sp: {
    common: './common.js',
-    common: './common.scss',
+    commonscss: './common.scss',
  },
};

コンパイルはされるようになりますが、MiniCssExtractPluginの設定でfilenameを[name].cssで指定しているためcommon.cssではなくcommonscss.cssで出力されてしまいます。

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

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で出力することが可能です!

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

まとめ

そんなこともできるのか!?と発見があったなら幸いです。

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

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

17
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?