📎

Webpackについて理解を深める(その1)

2022/08/08に公開

どうもoreoです。

何回かに分けてWebpackについて記載していきたいと思います。今回はそもそもWebpackとは?というところから、基本的な設定について触ります。この記事で基本を抑え、続編ではWebpackのarchitectureやconceptなどを深掘りしたいと思います。

1 Webpackとは?

Webpackは複数のJavaScriptファイルを一つのファイルにまとめて出力するツールです。この「一つのファイルにまとめる」ことをバンドル(bundle)と言い、また「モジュールを一つのファイルにまとめて出力するツール」のことをモジュールバンドラー(module bundler)と呼びます。

公式DocumentでWebpackは、👇のように静的モジュールバンドラーと紹介されています。

webpack is a static module bundler for modern JavaScript applications

Concepts | webpack

公式には👇のような図が記載されています。バンドルの雰囲気がわかりやすいですね。

この図には、JavaScript以外のファイルも記載されています。Lodaerと呼ばれる機能を使えば、CSSファイルなどもバンドル可能です。また、設定次第で複数ファイルにバンドルすることも可能です。

2 なぜWebpackを使うのか?

複数のJavaScriptファイルを一つにまとめることで、ブラウザからのリクエスト数を減らし、ファイル転送の最適化が可能です。これがWebpackなどのモジュールバンドラーを使う大きな理由です。

JavaScriptファイルが複数存在することでリクエスト回数が増えて転送効率が落ちるのであれは、最初から一つのファイルに記載することも可能ですが、可読性や保守性などの観点から現実的でありません。また、機能を複数ファイルに分割したままだと、ブラウザからのリクエスト回数が増えてファイル転送効率が落ちます。こういったつらみに対して、Webpackを利用することで機能をファイルごとに分割しながら開発し、実行時は一つのファイルとしてブラウザに提供することが可能となります。

👇の記事では、devツールで実際のファイルサイズを細かく見ながら、Webpackのメリットについて言及されていますので、参考までに。

https://dev.to/bruno8moura/why-am-i-using-the-webpack-tool-4h14

3 Webpackを実際に触る

3-1 環境

node:16.3.0

yarn:1.22.19

webpack:5.74.0

3-2 webpackのインストール

任意のディレクトリでプロジェクトを作成します。-yオプションをつけると、package.jsonに記載されるnameversionの設定などがデフォルト設定となります。

yarn init -y

webpackをインストールします。--devまたは-Dオプションをつけることで、devDependenciesにパッケージを追加できます。

yarn add webpack webpack-cli --dev

devDependenciesについては👇ご参照。

https://qiita.com/chihiro/items/ca1529f9b3d016af53ec

3-3 サンプルファイルの作成

srcディレクトリを作成し、index.jssub.jsを作成します。

├── node_modules
├── package.json
├── src
│   ├── index.js
│   └── sub.js
└── yarn.lock

index.js

import { sayBow } from "./sub"

sayBow()

sub.js

export function sayBow() {
    console.log("bow!")
}

3-4 バンドルしてみる

yarn webpack

👆のコマンドでバンドルすると、index.jssub.jsが一つのmain.jsというファイルにまとめられ、distフォルダ配下に出力されます。

├── dist
│   └── main.js
├── node_modules
├── package.json
├── src
│   ├── index.js
│   └── sub.js
└── yarn.lock

3-5 webpackの設定をカスタマイズする

webpack.config.jsでwebpackの各種設定が可能です

エントリーポイントと出力先の設定

エントリーポイントつまりバンドルの起点となるファイルは、entryで設定できます。下記例では、./src/index.jsを起点にバンドルされます。

バンドルファイルの出力先は、outputで設定できます。pathで出力先のディレクトリを指定し、filenameで出力ファイル名を設定できます。path.resolve(__dirname,'public')では、ルートパス/publicという絶対パスが返されます。

webpack.config.js

const path = require("path")

module.exports = {
    entry: "./src/index.js",
    output: {
        path: path.resolve(__dirname,'public'),
        filename: "main.js"
    },
};

このような設定だと、publicフォルダ配下にバンドルファイルが出力されます。

├── public
│   └── main.js
├── node_modules
├── package.json
├── src
│   ├── index.js
│   └── sub.js
└── yarn.lock

ここでpublicフォルダ配下にindex.htmlを追加してバンドルファイルを読み込んでみます。

├── public
│   ├── index.html
│   └── main.js
├── node_modules
├── package.json
├── src
└── yarn.lock

public/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>テスト</h1>
    <div class="bg"></div>
    <script src="./main.js"></script>
</body>
</html>

index.htmlを確認するとバンドルファイルが読み込まれていることが確認できます。

modeオプションで環境を使い分ける

modeオプションで、production環境とdevelopment環境の使い分けが可能です。

module.exports = {
		・
		・
		・
    mode: "development"
};

developmentでバンドルした場合、バンドルファイルには下記のようにソースマップが出力され、デバックが簡単になります。

一方で、productionでバンドルした場合は、下記のように圧縮されてファイル容量が小さくなります。

通常は環境ごとにwebpackの設定ファイルを用意して使用します。

devサーバーを使う

devサーバーを設定すると、HMR(Hot Module Replacement)が有効となります。HMRとは、ソースコード内のCSS/JSに変更があった場合にフルリロードすることなく、差分をブラウザに適用してくれる機能です。

webpack-dev-serverをインストールします。

yarn add webpack-dev-server --dev

webpack.config.jsdevServerを設定します。opentrueにすることで、devサーバーを立ち上げると自動でブラウザが立ち上がるようになります。staticにdevサーバーで表示するリソースのディレクトリを指定します。

module.exports = {
		・
		・
		・
    devServer: {
        open: true,
        static: path.join(__dirname, "public"),
    },
};

👇のコマンドで、devサーバーを起動できます。

yarn webpack serve

scssをバンドルできるようにする

JavaScript以外のファイルをバンドルする際は、loaderをインストールする必要があります。

ここでは、sassそのもの、sassをバンドルするsass-loader、cssをバンドルするcss-loader、style要素としてHTMLにcssを注入するstyle-loaderをインストールします。

yarn add --dev sass sass-loader css-loader style-loader

webpack.config.jsに、loaderを設定する際は、moduleを追加します。rulesプロパティの配列にオブジェクトの形でloaderの設定を記載します。testには、対象ファイルを記載し、useには使用するloaderを記載します。useに記載するloaderは下から順番に実行されるので、実行させたい順番に記載しなければならない点、注意が必要です。

module.exports = {
		・
		・
		・
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: [
                'style-loader',
                'css-loader',
                'sass-loader'
                ]
            },
        ]
    }
};

index.scsssrcフォルダ配下に作成して、index.jsに読み込ませます。

├── src
    ├── index.js
    ├── index.scss
    └── sub.js

index.scss

h1{
    text-decoration:underline;
}

index.js

import { sayBow } from "./sub"
import "./index.scss"

sayBow()

devサーバーを起動すると、underlineが追加されており、scssが有効になっていることがわかります。devtoolのElementsタグを確認すると、head要素のstyle要素にcssが注入されていることがわかります。

画像をバンドルできるようにする

画像をバンドルするには、webpack4まではfile-loaderをインストールする必要がありましたが、webpack5ではネイティブサポートのAsset Modulesで対応が可能となりました。

src/images/dog.pngを追加

├── src
    ├── images
    │   └── dog.png
    ├── index.js
    ├── index.scss
    └── sub.js

index.scssで画像ファイルを表示するようにする。

h1{
    text-decoration:underline;
}

.bg{
    height: 100px;
    width: 100px;
    background-image: url('./images/dog.png');
    background-size: cover;
}

webpack.config.jsでの設定の追加。testにはバンドル対象のファイル拡張子、generatorには、バンドルファイルの出力したいディレクトリを設定します。

module.exports = {
		・
		・
		・
    module: {
        rules: [
	            ・
		    ・
		    ・
            {
                test: /\.png$/i,
                generator: {
                    filename: `./image/[name][ext]`,
                },
            }
        ]
    }
};

バンドルしてdevサーバーを立ち上げると、画像が反映されています。

マルチエントリーポイント対応をする

マルチエントリーポイントに対応したい場合は、entryで配列の形でエントリーポイントを渡しますと、一つのファイルにバンドルできます。

module.exports = {
    entry: ["./src/index.js","./src/sub.js"],
		・
		・
		・
};

マルチエントリーポイントで、出力ファイルを分けたい場合は、entryにオブジェクトの形で、エントリーポイントを渡します。また、outputに渡すファイル名を[name]とするとentryオブジェクトに渡したキーが自動でファイル名に補完されバンドルファイルが出力されます。

module.exports = {
    entry: {
        index: "./src/index.js",
        sub: "./src/sub.js"
    },
    output: {
        path: path.resolve(__dirname,'public'),
        filename: "[name].js"
    },
		・
		・
		・
};

バンドルすると以下のように、index.jssub.jsという形で出力されます。

├── public
    ├── image
    ├── index.html
    ├── index.js
    └── sub.js

public/index.htmlscriptの読み込みを./index.jsに変更するとバンドルファイルの読み込みをブラウザで確認できます。

エイリアスを設定する

resolvealiasを設定するとpathのエイリアスを設定できます。以下では、src/imagesのpathに対して@というエイリアスが設定ができます。

module.exports = {
		・
		・
		・
    resolve: {
        alias: {
            "@" :path.resolve(__dirname,"src/images")
        }
    }
};

index.scssbackground-imageでエイリアスが使用できます。

h1{
    text-decoration:underline;
}

.bg{
    height: 100px;
    width: 100px;
    background-image: url('@/dog.png');
    background-size: cover;
}

4 最後に

今回はWebpackの基本的な部分についてまとめてみました。環境ごとの設定ファイルの分割、最適化手法などさらに細かい設定が可能ですので、気になる方は公式などをご覧ください。

続編では、設定方法の深掘りというよりかは、architectureの深掘りをしたいと思います!

5 参考

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

https://zenn.dev/antez/articles/58307946cf4f3e#webpackがわからない

Discussion