Multi-Page Application dengan Webpack 5

Published 16 November 2022

Pada sebuah project saya pernah menemukan kasus dimana saya harus memisahkan beberapa file css dan js untuk di gunakan di banyak halaman. Sedangkan hasil output nya di tempatkan di folder yang berbeda dari file utama (ex: app.css, app.js). Untuk folder output dari style saya tempatkan di folder ./pages/ sehingga akan mendapatkan struktur berikut ./pages/homepage.css, ./pages/about.css, dll.

Langsung saja sebelum kita mulai, install terlebih dahulu plugin yang di butuhkan seperti berikut

Webpack

Sekarang kita perlu menginstal Webpack untuk kebutuhan dasarnya. Versi webpack yang di gunakan adalah versi 5.

npm install webpack webpack-cli --save-dev

Dotenv

Nantinya kita akan menggunakan .env untuk setup mode build nya production atau development

npm install dotenv --save-dev

MiniCssExtractPlugin

Plugin ini mengekstrak CSS ke dalam file terpisah. Itu membuat file CSS per file JS yang berisi CSS. Mendukung On-Demand-Loading CSS dan SourceMaps.

npm install mini-css-extract-plugin --save-dev

TerserWebpackPlugin

Seperti css yang perlu di ekstrak dan di minimize file js juga perlu di lakukan agar ukurannya lebih kecil.

npm install terser-webpack-plugin --save-dev

Selain plugin di atas kita perlu juga menambahkan plugin berikut, karena kita akan menggunakan Bootstrap & jQuery. Selain itu juga kita menggunakan SCSS untuk compiler styling nya.

npm install sass-loader style-loader css-loader node-sass post-css babel-loader babel-core jquery url-loader bootstrap svg-url-loader lqip-loader img-loader --save-dev

Setelah semuanya berhasil di install kita buat satu file dengan nama webpack.config.js dan copy code berikut

const path = require("path");
const Dotenv = require("dotenv-webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");

let appConf = (env) => {
  return {
    mode: env.NODE_ENV,
    devtool: "nosources-source-map",
    entry: {
      "app.style": "@nstyle/app.scss",
      "app.script": "@nscript/app.js",
    },
    output: {
      path: path.resolve(__dirname, "public/assets"),
      filename: "js/[name].js",
    },
    resolve: {
      alias: {
        "@nstyle": path.resolve(__dirname, "./resources/scss"),
        "@nscript": path.resolve(__dirname, "./resources/js"),
      },
    },
    devServer: {
      port: 8000,
      headers: {
        "Access-Control-Allow-Origin": "*",
      },
    },
    performance: {
      hints: false,
    },
    stats: {
      warnings: false,
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /(node_modules)/,
          use: {
            loader: "babel-loader",
          },
        },
        {
          test: /\.(sc|c)ss$/,
          use: [
            {
              loader: MiniCssExtractPlugin.loader,
            },
            {
              loader: "css-loader",
              options: {
                url: false,
              },
            },
            {
              loader: "sass-loader",
            },
            {
              loader: "postcss-loader",
            },
          ],
        },
        {
          test: /\.(?:ico|gif|png|jpg|jpeg|webp|svg)$/i,
          type: "asset/resource",
          use: [
            "url-loader?limit=10000",
            {
              loader: "img-loader",
            },
            {
              loader: "lqip-loader",
              options: {
                base64: true,
                palette: false,
              },
            },
            {
              loader: "url-loader",
              options: {
                limit: 8000,
              },
            },
            {
              loader: "svg-url-loader",
              options: {
                encoding: "base64",
                iesafe: true,
              },
            },
          ],
        },
        {
          test: /\.(ttf|eot|woff|woff2)|(arunicon\.svg)$/,
          type: "asset/inline",
        },
      ],
    },
    optimization: {
      minimize: true,
      minimizer: [
        new CssMinimizerPlugin({
          minimizerOptions: {
            preset: [
              "default",
              {
                discardComments: {
                  removeAll: true,
                },
              },
            ],
          },
        }),
        new TerserPlugin({
          parallel: true,
          terserOptions: {
            format: {
              comments: false,
            },
          },
          extractComments: true,
        }),
      ],
      splitChunks: {
        cacheGroups: {
          styles: {
            test: /\.css$/,
            name: "styles",
            chunks: "all",
            enforce: true,
          },
        },
      },
    },
    plugins: [
      new Dotenv(),
      new MiniCssExtractPlugin({
        filename: "css/[name].css",
        chunkFilename: "css/[name].[chunkhash:4].css",
      }),
    ],
  };
};

let pageConf = (env) => {
  return {
    mode: env.NODE_ENV,
    devtool: "nosources-source-map",
    entry: {
      homepage: "@nstyle/pages/_homepage.scss",
      about: "@nstyle/pages/_about.scss",
      contact: "@nstyle/pages/_contact.scss",
    },
    output: {
      path: path.resolve(__dirname, "public/assets"),
      filename: "js/pages/[name].js",
    },
    resolve: {
      alias: {
        "@nstyle": path.resolve(__dirname, "./resources/scss"),
      },
    },
    devServer: {
      port: 8000,
      headers: {
        "Access-Control-Allow-Origin": "*",
      },
    },
    performance: {
      hints: false,
    },
    stats: {
      warnings: false,
    },
    module: {
      rules: [
        {
          test: /\.(sc|c)ss$/,
          use: [
            {
              loader: MiniCssExtractPlugin.loader,
            },
            {
              loader: "css-loader",
              options: {
                url: false,
              },
            },
            {
              loader: "sass-loader",
            },
            {
              loader: "postcss-loader",
            },
          ],
        },
        {
          test: /\.(?:ico|gif|png|jpg|jpeg|webp|svg)$/i,
          type: "asset/resource",
          use: [
            "url-loader?limit=10000",
            {
              loader: "img-loader",
            },
            {
              loader: "lqip-loader",
              options: {
                base64: true,
                palette: false,
              },
            },
            {
              loader: "url-loader",
              options: {
                limit: 8000,
              },
            },
            {
              loader: "svg-url-loader",
              options: {
                encoding: "base64",
                iesafe: true,
              },
            },
          ],
        },
        {
          test: /\.(ttf|eot|woff|woff2)|(arunicon\.svg)$/,
          type: "asset/inline",
        },
      ],
    },
    optimization: {
      minimize: true,
      minimizer: [
        new CssMinimizerPlugin({
          parallel: true,
          minimizerOptions: {
            preset: [
              "default",
              {
                discardComments: {
                  removeAll: true,
                },
              },
            ],
          },
        }),
      ],
      splitChunks: {
        cacheGroups: {
          styles: {
            test: /\.css$/,
            name: "styles",
            chunks: "all",
            enforce: true,
          },
        },
      },
    },
    plugins: [
      new Dotenv(),
      new MiniCssExtractPlugin({
        filename: "css/[name].css",
        chunkFilename: "css/[name].[chunkhash:4].css",
      }),
    ],
  };
};

module.exports = (env) => {
  return [appConf(env), pageConf(env)];
};

NPM scripts

Tambahkan beberapa skrip ke package.json untuk menyederhanakan proses build:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "node_modules/webpack/bin/webpack.js --node-env=production --progress",
    "watch": "node_modules/webpack/bin/webpack.js --node-env=development --watch --progress"
  }

Jika semua proses sudah di lakukan dan berhasil maka kita akan mendapatkan file css dan js di dalam folder yang sudah kita tentukan sebelumnya.

Cukup sampai disini sedikit sharing pengalaman tentang penggunaan multi entry pada webpack. Jika ada masukan atau cara yang lebih baik lagi bisa share di komentar.

Terimakasih!