Webpack 5の設定


Webpackを設定するために検索した内容を整理したいです.

1.webpack-dev-serverの設定


devServerバインドの成果物は、通常のwebpackコマンドとは異なり、メモリに格納されます.そのため、開発サーバをオフにすると、結果も消えてしまいます.
ソース:Webspack-dev-server 3版(feat.proxy)
Webpack devserverオプションの静的設定は、静的ファイルを提供するために必要なパスです.
ソース:Webpack公式ドキュメント-devserver。static
私の場合、htmlファイルとjs、css、imgフォルダのすべてのファイルは静的ファイルなので、「dist」に設定して静的ファイルを検索すればいいです.
       static: [
        {
          directory: path.resolve(__dirname, "/dist"),
        },
      ],

構築時に構築されたファイルはpath設定に基づいて「dist」フォルダに作成されます.publicPathは空の値であるため、構築されたファイルのurlは「http://127.0.0.1:5500/dist/index.html「わあ」などの「dist」パスの後ろに他のパスを追加していません.
    output: {
      path: path.resolve(__dirname, "dist"),
      publicPath: "",
      filename: "js/[name].[chunkhash].js",
      chunkFilename: "js/[name].chunk.js",
      assetModuleFilename: "img/[name][ext]",
    },

ブロック・ファイルに名前を付ける場合は、「chunkFileName」オプションを使用して、他のブロック・ファイル名を設定します.デフォルト設定:[id]ブロック順0、1、2、3...に与えるここで、ブロック(block)は、コードのスキップ時に生成されるJavaScriptファイルセグメントを表すブロックである.
ソース:Web Pack 5を使用してブロックと印刷コードを管理する
assetModuleFilenameは、ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot拡張子で終了するリソースのパスを設定します.
        {
          test: /\.(ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
          type: "asset/resource",
        },

2.cssファイルとバックグラウンドurlのパスを設定する


まず,cssモジュールのルールでは,開発モードと生産モードを区別することによってローダを指定する.
      {
          test: /\.(css|s[ac]ss)$/i,
          use: [
            prod
              ? {
                  loader: MiniCssExtractPlugin.loader,
                  options: {
                       publicPath: "../",
                  },
                }
              : "style-loader",
            "css-loader",
            {
              loader: "sass-loader",
              options: {
                implementation: require.resolve("sass"),
              },
            },
          ],
        },
開発モードではcssを継続的に変更するためstyle-loaderを使用する.
本番モードでは、MiniCssExtractPluginを使用します.MiniCssExtractPluginは、構築前にjsからインポートしたcssファイルをjsファイルから分離し、リンクタグとしてhtmlに格納します.
/src/js/index.js
import "../css/index.css";
import("./common.js")
  .then(({ default: common }) => {
    common();
  })
  .catch((err) => {
    console.error("common error", err);
  });
/dist/index.html
<Head>
    <script
      defer="defer"
      src="js/index.860ef49ae25452bc0eea.js?ea4a9077a3de00c3236e"
    ></script>
    <link
      href="css/index.860ef49ae25452bc0eea.css?ea4a9077a3de00c3236e"
      rel="stylesheet"
    />
</Head>    
MiniCssExtractPluginのオプションでは、filenameは構築後にcssファイルのパスと名前を設定できます.
publicPathオプションは、すべてのcssファイルのバックグラウンドurlパスに「.../」追加します.
構築前に、相対パスを次のように設定します(「img/main banner.png」).
.main_banner div {
  width: 880px;
  margin: auto;
  height: 100%;
  background: url("img/main_banner.png") 50% calc(100% - 40px) no-repeat;
  background-size: contain;
}
構築後のcssファイルのバックグラウンドurlパスは以下のように設定されます.

注意:WEB ACK画像パス処理

3.コードを分割して、コード量を減らして性能を向上させる


ソース:動的ImportによるWebページのパフォーマンスの向上
注意:
Webpackでコードを印刷する
すべてのWebページの速度を決定する最初の要因は、最初のページを描画するために必要なリソースの量です.
必要なリソースが多ければ多いほど、ネットワークからダウンロードする時間が長くなります.
Webpackのようなbundlerは、すべてのJSコードを巨大なファイルに生成し、Webページにページを描画するために必要なリソース量も大幅に増加します.
したがって,ウェブサイトの速度を速めるために,大JSファイルの容量を減らすことができ,コードサイズを大幅に小さくする方法と,コードを分割する方法がある.

(1)削減コード


コードを減らす方法はwebpackです.最適化ではminimizerオプションを使用して圧縮するかuglify-jsを使用します.圧縮の形式は以下の通りです.
venders.22ba66c5797265730272.css

index.860ef49ae25452bc0eea.js

(2)分割コード


サイトには多くの情報が含まれており、複数のページが存在する可能性が高い.Webpackで構築されたファイルは、ページ全体の内容を1つのファイルに含めます.したがって、すべてをレンダリングしても、ユーザが表示するページは多くありません.
複数のページに必要なファイルに分割し、各ページにオンデマンドでロードする場合は、最初のレンダリングに必要なファイルのサイズを小さくすることで、レンダリング速度を速めることができます.
  • Webpackでは、コードを分割するために、目的に応じて複数のEntryに分割することができる.適切な設定セクションは次のとおりです.
  • const entry = {
        load_HTML: path.resolve(__dirname, "src/js/load_HTML.js"),
        load_common: path.resolve(__dirname, "src/js/load_common.js"),
        common: path.resolve(__dirname, "src/js/common.js"),
        review_slider: path.resolve(__dirname, "src/js/review_slider.js"),
        top_banner_slider: path.resolve(__dirname, "src/js/top_banner_slider.js"),
      };
  • は、重複コードを分割して管理するwebpackオプションを最適化した.くずがある.ブロック間で重複するパッケージを個別のファイルとして抽出する場合は、次の設定で「仕入先」という名前のファイルに重複するコードをパッケージ化します.
  •     optimization: {
          usedExports: true,
          minimize: true,
          minimizer,
          runtimeChunk: "single",
          splitChunks: {
            chunks: "all",
            maxInitialRequests: Infinity,
            minSize: 0,
            cacheGroups: {
              vendor: {
                chunks: "initial",
                name: "vendor",
                enforce: true,
              },
            },
          },
        },
    optimization.userExportsがtrueに設定されている場合、未使用のimportはバインドされません.
    optimization.minimizerではminimizer関連のプラグインを設定できます.
    BundleAnalyzerPluginでは、現在使用されているビームのサイズを示す可視化されたツリー図が表示されます.TerserPluginはJavascriptコードを縮小/最小化できます.CSMinimizerPluginはCSSファイルを縮小できます.
    ソース:構築の最適化
  • のほとんどのコードは、ユーザーが見た最初のページでは必要ありません.最初のページへのアクセスに必要な最小限のコードのみをダウンロードし、ユーザーがページまたは場所に到着するたびにコードをロードすると、最初のページの初期パフォーマンスが向上します.この方法をlazy-load怠惰ロードと呼ぶ.動的Importを使用すると、実行時に必要なモジュールをインポートできます.動的Importでは、Promiseオブジェクトに戻るdefaultパラメータとしてjsファイルにモジュールをインポートするための形式でjsファイルをインポートできます.
  •  import("./common.js")
      .then(({ default: common }) => {
        common();
      })
      .catch((err) => {
        console.error("common error", err);
      });
    Webpackで動的Importを使用するには、babel-plugin-syntax-dynamic-importプラグインが必要です.
    /.babelrc
    {
      "plugins": ["@babel/plugin-syntax-dynamic-import"],
      "presets": [
        [
          "@babel/preset-env",
          { "targets": { "browsers": ["last 2 versions", ">= 5% in KR"] } }
        ],
        "@babel/typescript"
      ]
    }
    
    構築時にimport()モジュールをブロックファイルとして作成し、必要に応じてヘッダにスクリプトを設定してJSファイルをダウンロードします.
    このとき、ブロックファイル名は、webpackの出力で設定されたファイル名に従います.
     output: {
      filename: '[name].[chunkhash].js'
    }
    ソース:Web Pack 5を使用してブロックと印刷コードを管理する
    私の場合、無知で最も簡単な方法entryでは、必要なjsファイルをページごとに作成してインポートしているので、実際に動的Import関連の設定は必要ないと思います.それでも適用される理由は、Webpack HtmlWebpackPluginグループksSortModeを使用して実行順序を調整で議論されているホットスポット共通jsファイル(common.js)や各ページのサブメニュー(本プロジェクトではsub 2.js、sub 3.ts、sub 6.js)のようなロード順序を調整するためである.

    4.webpack全体の設定と構築前後のフォルダ構造の比較


    (1)webpack完全設定


    /src/webpack.config.js
    const path = require("path");
    const webpack = require("webpack");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const { CleanWebpackPlugin } = require("clean-webpack-plugin");
    const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
    const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
    const TerserPlugin = require("terser-webpack-plugin");
    const BundleAnalyzerPlugin =
      require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
    
    module.exports = (env, options) => {
      const prod = options.mode === "production";
    
      const htmlPageNames = [
        "index",
        "sub1",
        "sub2",
        "sub2_2", 
        "sub2_3",
        "sub2_4", 
        "sub2_5", 
        "sub3", 
        "sub3_2",
        "sub3_3",
        "sub4", 
        "sub5", 
        "sub6",
        "sub6_2",
        "sub6_2_detail",
        "sub6_3",
        "service_use_term",
        "privacy_info_use_term", 
        "header_nav",
        "footer_float_menu",
        "top_banner",
      ];
    
      const entry = {
        load_HTML: path.resolve(__dirname, "src/js/load_HTML.js"),
        load_common: path.resolve(__dirname, "src/js/load_common.js"),
        common: path.resolve(__dirname, "src/js/common.js"),
        review_slider: path.resolve(__dirname, "src/js/review_slider.js"),
        top_banner_slider: path.resolve(__dirname, "src/js/top_banner_slider.js"),
      };
      
      const multipleHtmlPlugins = htmlPageNames.map((name, idx) => {
        const splited = name.split("_")[0];
        let chunks = idx < 18 ? ["load_HTML", "top_banner_slider"] : [];
    
        // console.log("splited", splited);
    
        if (splited !== "header" && splited !== "footer" && splited !== "top") {
          if (splited === "privacy" || splited === "service") {
            (entry[splited] = path.resolve(__dirname, `src/js/privacy.js`)),
              chunks.push("privacy");
          } else {
            if (splited === "sub3") {
              (entry[splited] = path.resolve(__dirname, `src/js/${splited}.ts`)),
                chunks.push(splited);
            } else {
              (entry[splited] = path.resolve(__dirname, `src/js/${splited}.js`)),
                chunks.push(splited);
            }
          }
        }
    
        (idx === 3 ||
          idx === 5 ||
          idx === 6 ||
          idx === 7 ||
          idx === 10 ||
          idx === 11) &&
          chunks.push("review_slider");
    
        // console.log(name, chunks);
    
        return new HtmlWebpackPlugin({
          template: path.resolve(`src/${name}.html`),
          filename: `${name}.html`,
          chunks,
          hash: true,
          chunksSortMode: "manual",
        });
      });
    
      const plugins = [
        new webpack.ProvidePlugin({ $: "jquery" }),
        new CleanWebpackPlugin(),
            new MiniCssExtractPlugin({
          filename: "css/[name].[chunkhash].css",
        }),
        new WebpackManifestPlugin(),
        new FaviconsWebpackPlugin({
          logo: path.resolve(__dirname, "src/img/modument-logo.png"),
          mode: "light",
          devMode: "light",
          inject: true,
          cache: true,
        }),
      ].concat(multipleHtmlPlugins);
    
      if (!prod) {
        plugins.push(new webpack.HotModuleReplacementPlugin());
      }
    
      const minimizer = [new CssMinimizerPlugin(), new TerserPlugin()];
       if (!prod) {
         minimizer.push(new BundleAnalyzerPlugin());
       }
    
      const config = {
        mode: prod ? "production" : "development",
        devtool: prod ? "eval" : "hidden-source-map",
        resolve: {
          extensions: [".js", ".ts", ".css", ".scss"],
          modules: [path.resolve(__dirname, "src"), "node_modules"],
        },
        entry,
        output: {
          path: path.resolve(__dirname, "dist"),
          publicPath: "",
          filename: "js/[name].[chunkhash].js",
          chunkFilename: "js/[name].chunk.js",
          assetModuleFilename: "img/[name][ext]",
        },
        plugins,
        module: {
          rules: [
            {
              test: /\.(ts|js)$/,
              use: [
                "babel-loader",
                {
                  loader: "ts-loader",
                },
              ],
              exclude: path.join(__dirname, "node_modules"),
            },
    
            {
              test: /\.(ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
              type: "asset/resource",
            },
            {
              test: /\.(css|s[ac]ss)$/i,
              use: [
                prod
                  ? {
                      loader: MiniCssExtractPlugin.loader,
                      options: {
                        filename: "css/[name].[chunkhash].css",
                        publicPath: "../",
                      },
                    }
                  : "style-loader",
                "css-loader",
                {
                  loader: "sass-loader",
                  options: {
                    implementation: require.resolve("sass"),
                  },
                },
              ],
            },
            {
              test: /\.(woff|woff2|eot|ttf|otf)$/i,
              type: "asset/resource",
            },
          ],
        },
        devServer: {
          static: [
            {
              directory: path.resolve(__dirname, "/dist"),
            },
          ],
          open: true,
          compress: true,
        },
        optimization: {
          usedExports: true,
          minimize: true,
          minimizer,
          runtimeChunk: "single",
          splitChunks: {
            chunks: "all",
            maxInitialRequests: Infinity,
            minSize: 0,
            cacheGroups: {
              vendor: {
                chunks: "initial",
                name: "vendor",
                enforce: true,
              },
            },
          },
        },
      };
    
      return config;
    };
    
    /src/package.json
    {
      "name": "modument_homepage2022",
      "version": "1.0.0",
      "description": "",
      "main": "/index.html",
      "scripts": {
        "build": "webpack --mode production",
        "dev": "webpack serve --env development",
        "start": "webpack --mode production"
      },
      "repository": {
        "type": "git",
        "url": "git+https://github.com/maliethy/modument_homepage2022.git"
      },
      "author": "maliethy",
      "license": "ISC",
      "bugs": {
        "url": "https://github.com/maliethy/modument_homepage2022/issues"
      },
      "homepage": "https://github.com/maliethy/modument_homepage2022#readme",
      "devDependencies": {
        "@babel/core": "^7.17.5",
        "@babel/plugin-syntax-dynamic-import": "^7.8.3",
        "@babel/preset-env": "^7.16.11",
        "@babel/preset-typescript": "^7.16.7",
        "@types/jquery": "^3.5.14",
        "babel-loader": "^8.2.3",
        "clean-webpack-plugin": "^4.0.0",
        "css-loader": "^6.6.0",
        "css-minimizer-webpack-plugin": "^3.4.1",
        "favicons": "^6",
        "favicons-webpack-plugin": "^5.0.2",
        "html-webpack-plugin": "^5.5.0",
        "mini-css-extract-plugin": "^2.5.3",
        "sass": "^1.49.9",
        "sass-loader": "^12.6.0",
        "style-loader": "^3.3.1",
        "ts-loader": "^9.2.6",
        "typescript": "^4.6.2",
        "webpack": "^5.69.1",
        "webpack-bundle-analyzer": "^4.5.0",
        "webpack-cli": "^4.9.2",
        "webpack-dev-server": "^4.7.4",
        "webpack-manifest-plugin": "^4.1.1"
      },
      "dependencies": {
        "core-js": "^3.21.1",
        "jquery": "^3.6.0",
        "regenerator-runtime": "^0.13.9",
        "swiper": "^8.0.6"
      }
    }
    
    /src/js/sub2.js
    import "../css/sub2.css";
    
    import("./common.js")
      .then(({ default: common }) => {
        common();
        $(function () {
          sub2();
        });
      })
      .catch((err) => {
        console.error("common error", err);
      });
    
    function sub2() {
      const prod = process.env.NODE_ENV === "production";
    
      if ($(".sub2_menu").hasClass("active") === false) {
        $(".sub2_menu").addClass("active");
        $(".sub3_menu").removeClass("active");
        $(".sub6_menu").removeClass("active");
        for (let i = 2; i < 6; i++) {
          if (window.location.href.split("/")[prod ? 4 : 3] === `sub2_${i}.html`) {
            $(".sub_header > ul > li").removeClass("on");
            $(`.sub_header > ul > li:nth-child(${i})`).addClass("on");
          }
        }
      }
      
      생략...
      }

    (2)フォルダ構造の事前構築




    (3)構築後に作成されたdistフォルダ構造とmanifest.json, html



    /dist/manifest.json
    {
      "load_HTML.css": "css/load_HTML.0dee04ab7503e5087798.css",
      "load_HTML.js": "js/load_HTML.0dee04ab7503e5087798.js",
      "load_common.js": "js/load_common.b455fd96e01db80d68b3.js",
      "common.js": "js/common.4b8b1c68e2e0bc8a0c69.js",
      "review_slider.css": "css/review_slider.d2616e5358c99fedfdbf.css",
      "review_slider.js": "js/review_slider.d2616e5358c99fedfdbf.js",
      "top_banner_slider.js": "js/top_banner_slider.b9ed1ac041e6665bd01f.js",
      "index.css": "css/index.860ef49ae25452bc0eea.css",
      "index.js": "js/index.860ef49ae25452bc0eea.js",
      "sub1.css": "css/sub1.c5b54d705d4847039a48.css",
      "sub1.js": "js/sub1.c5b54d705d4847039a48.js",
      "sub2.css": "css/sub2.d32f76861cd271c02f5e.css",
      "sub2.js": "js/sub2.d32f76861cd271c02f5e.js",
      "sub3.css": "css/sub3.02ece1f07edac4beee54.css",
      "sub3.js": "js/sub3.02ece1f07edac4beee54.js",
      "sub4.css": "css/sub4.39f23416de687109ab8f.css",
      "sub4.js": "js/sub4.39f23416de687109ab8f.js",
      "sub5.css": "css/sub5.4d43762ac2dbcc9e13c9.css",
      "sub5.js": "js/sub5.4d43762ac2dbcc9e13c9.js",
      "sub6.css": "css/sub6.a73b488593679a4cf20c.css",
      "sub6.js": "js/sub6.a73b488593679a4cf20c.js",
      "service.js": "js/service.4e45ff3047cf76721d80.js",
      "privacy.js": "js/privacy.d9b50e2d1d8d1e505f7d.js",
      "runtime.js": "js/runtime.c8e2b5046570adef20ac.js",
      "js/283.chunk.js": "js/283.chunk.js",
      "js/199.js": "js/199.d79f0cfd69e9e6602269.js",
      "venders.css": "css/venders.22ba66c5797265730272.css",
      "venders.js": "js/venders.22ba66c5797265730272.js",
      "css/778.css": "css/778.622fc862883010c45312.css",
      "js/778.js": "js/778.622fc862883010c45312.js",
      "img/notice1.jpg": "img/notice1.jpg",
     
       생략... 
    
    }
    /dist/index.html
    <!DOCTYPE html>
    <html lang="ko">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta
          name="viewport"
          content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1"
        />
        <title>MODUMENT</title>
        <link rel="icon" href="assets/favicon.png" />
        <script
          defer="defer"
          src="js/runtime.c8e2b5046570adef20ac.js?ea4a9077a3de00c3236e"
        ></script>
        <script
          defer="defer"
          src="js/venders.22ba66c5797265730272.js?ea4a9077a3de00c3236e"
        ></script>
        <script
          defer="defer"
          src="js/load_HTML.0dee04ab7503e5087798.js?ea4a9077a3de00c3236e"
        ></script>
        <script
          defer="defer"
          src="js/top_banner_slider.b9ed1ac041e6665bd01f.js?ea4a9077a3de00c3236e"
        ></script>
        <script
          defer="defer"
          src="js/index.860ef49ae25452bc0eea.js?ea4a9077a3de00c3236e"
        ></script>
        <link
          href="css/venders.22ba66c5797265730272.css?ea4a9077a3de00c3236e"
          rel="stylesheet"
        />
        <link
          href="css/load_HTML.0dee04ab7503e5087798.css?ea4a9077a3de00c3236e"
          rel="stylesheet"
        />
        <link
          href="css/index.860ef49ae25452bc0eea.css?ea4a9077a3de00c3236e"
          rel="stylesheet"
        />
      </head>
       <body>
        <div class="swiper-container top_banner"></div>
        생략...
        <div id="footer_float_menu"></div>
      </body>
    </html>
    /dist/sub2.html
    <!DOCTYPE html>
    <html lang="ko">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta
          name="viewport"
          content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1"
        />
        <title>MODUMENT</title>
       <link rel="icon" href="assets/favicon.png" />
        <script
          defer="defer"
          src="js/runtime.c8e2b5046570adef20ac.js?ea4a9077a3de00c3236e"
        ></script>
        <script
          defer="defer"
          src="js/venders.22ba66c5797265730272.js?ea4a9077a3de00c3236e"
        ></script>
        <script
          defer="defer"
          src="js/load_HTML.0dee04ab7503e5087798.js?ea4a9077a3de00c3236e"
        ></script>
        <script
          defer="defer"
          src="js/top_banner_slider.b9ed1ac041e6665bd01f.js?ea4a9077a3de00c3236e"
        ></script>
        <script
          defer="defer"
          src="js/sub2.d32f76861cd271c02f5e.js?ea4a9077a3de00c3236e"
        ></script>
        <link
          href="css/venders.22ba66c5797265730272.css?ea4a9077a3de00c3236e"
          rel="stylesheet"
        />
        <link
          href="css/load_HTML.0dee04ab7503e5087798.css?ea4a9077a3de00c3236e"
          rel="stylesheet"
        />
        <link
          href="css/sub2.d32f76861cd271c02f5e.css?ea4a9077a3de00c3236e"
          rel="stylesheet"
        />
      </head>
      <body>
        <div class="swiper-container top_banner"></div>
           생략...
        <div id="footer_float_menu"></div>
      </body>
    </html>