捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:正式環境用的 webpack 設定

忙了幾天之後,你感覺專案稍微有模有樣了,打算先部署一版測試站讓其他人看看,所以——今天的主題就是分享個人在正式環境用的 webpack 設定。

相較於開發版,部署用的設定移除了 source map 並導入壓縮、分塊(chunking)的功能。有興趣歡迎參考,當然也很歡迎你留言分享自己的愛用設定或 plugin 唷 (・∀・)

完整設定

/* Package */
import { EsbuildPlugin } from 'esbuild-loader';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import Webpack from 'webpack';

/* Tool */
import { resolvePath } from '@/tool/resolvePath';

/* Data */
import alias from './data/alias';
import env from './data/env';
import type { WebpackConfiguration, EnvForBuildApp } from './data/types';

const envForBuildApp = env['process.env'] as EnvForBuildApp;

/* Main */
const webpackProductionConfig: WebpackConfiguration = {
  mode: 'production',
  entry: resolvePath('src/index'),
  output: {
    filename: 'static/js/[name].[contenthash:8].js',
    path: resolvePath(JSON.parse(envForBuildApp.BUILD_DESTINATION)),
    clean: true,
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      maxSize: 20000, // 單位 bytes ,這裡是 20kb
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
        },
        default: {
          minChunks: 10,
        },
      },
    },
    minimizer: [
      new EsbuildPlugin({
        target: 'es2015',
        css: true,
      }),
    ],
  },
  module: {
    rules: [
      {
        test: /\.(js|ts|tsx)$/,
        loader: 'esbuild-loader',
        options: {
          loader: 'tsx',
          target: 'es2015',
        },
      },
      {
        test: /\.(png|svg|jpg)$/i,
        type: 'asset/resource',
        resourceQuery: /url/,
      },
      {
        test: /\.svg$/i,
        issuer: /\.tsx?$/,
        resourceQuery: { not: [/url/] },
        use: [
          {
            loader: '@svgr/webpack',
            options: {
              svgo: false,
              ref: true,
            },
          },
        ],
      },
      {
        test: /\.css$/,
        exclude: /\.module\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          {
            loader: 'css-loader',
            options: {
              sourceMap: false,
            },
          },
        ],
      },
      {
        test: /\.module\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          {
            loader: 'css-loader',
            options: {
              sourceMap: false,
              modules: {
                mode: 'local',
                localIdentName: '[hash:base64]',
              },
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      inject: true,
      template: resolvePath('public/index.html'),
      publicPath: '/',
    }),
    new Webpack.DefinePlugin({ ...env }),
    new MiniCssExtractPlugin({
      filename: 'static/css/[name].[contenthash:8].css',
      chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
    }),
  ],
  resolve: {
    extensions: ['.js', '.ts', '.tsx'],
    alias,
  },
};

export default webpackProductionConfig;

解說

Package 區塊:

此區列出部署時會用到的套件。相較於開發用設定,追加了 mini-css-extract-plugin 協助處理 css 相關內容。


Tool 區塊:

使用第 8 天寫好的小工具 resolvePath 來處理檔案路徑。


Data 區塊:

引用第 7 天第 8 天處理好的 envalias 內容,並透過 const envForBuildApp = env['process.env'] as EnvForBuildApp; 取用驗證過的部署用環境變數。


Main 部分:

設定 mode: production 並指定打包入口點為 entry: resolvePath('src/index')

output 內透過 path 設定最終輸出目的地,上方設定翻譯成白話文就是:透過 resolvePath() 解出環境變數 BUILD_DESTINATION 的絕對路徑,打包完畢的東西請送到這個資料夾。

而因為打算做 code splitting,所以指定輸出的 bundle 檔案名稱為 static/js/[name].[contenthash:8].js。詳細說明可參考 webpack 5 output.filename 的內容。

設定 output.clean: true 代表每次打包前都先清空目的地資料夾。個人選擇這樣設定是因為:

  1. 避免任何檔案殘留影響的可能性
  2. 評估當下專案的靜態檔案沒有多到需要做檔案快取來加速打包流程,故選擇全刪

optimization 分成 splitChunksminimizer 兩部分。分塊設定大部分參考自 webpack 5: optimization.splitChunks 內容。

設定 chunks: 'all' 代表所有的分塊都會被納入優化處理。因設定後實測打包速度還能維持 5 秒左右結束,故這裡選擇使用極限優化。

maxSize: 20000(單位為 bytes 所以實際上代表 20kb)指示 webpack 盡量將大於這個尺寸的分塊繼續往下分解。

Using maxSize tells webpack to try to split chunks bigger than maxSize bytes into smaller parts.

minChunks: 10 代表「至少要有 10 處以上的引用,才會被分塊」:

splitChunks.minChunks: The minimum times must a module be shared among chunks before splitting.

minimizer 中選擇使用既有的 EsbuildPlugin 來協助壓縮 css 輸出內容、不再另外安裝套件。完整介紹可參考官方說明

module.rules 與開發設定不同的地方在於取消 sourceMap,並搭配 MiniCssExtractPlugin.loader.css 內容獨立為一份檔案。

mini-css-extract-plugin: This plugin extracts CSS into separate files.

可看到 plugins 納入了 new MiniCssExtractPlugin({ ... }) 設定。與最上方的 output 類似,這裡透過 mini-css-extract-plugin 指定 .css檔案都要包進輸出資料夾中的 ./static/css 中。

輸出結果

執行(明天會說明細節)以上打包設定後,專案根目錄會出現一個 ./build 資料夾,結構基本如下:

./build
├── index.html
├── (輸出後的根目錄會包含一些被 index.html 引用的靜態檔案,會於後續鐵人賽中說明)
└── static
    ├── css
   └── main-31743c5a.b5b18614.css
    └── js
        ├── (省略了非常多的分塊檔案,不然內容會太長)
        ├── main-f16aa68b.5879c5ca.js
        └── main-f6eac33a.2dac1962.js

這時已經可以透過 vs code 的 Live Server 或 npm 套件 http-server 來預覽打包成果了。

最後,謝謝你撐過今天的解說 👍

Speedwagon