從零開始搭建react基礎開發環境(基於webpack5)

2022-08-28 18:00:48

前言

最近利用閒暇時間把webpack系統的學習了下,搭建出一個react環境的腳手架,寫篇文章總結一下,幫助正在學習webpack小夥伴們,如有寫的不對的地方或還有可以優化的地方,望大佬們指出,及時改正。

git專案地址:https://github.com/handsomezyw/my-webpack

初始化專案

  1. 新建資料夾起名為my-webpack(資料夾名字任意取),然後初始化專案,使用如下指令,會在你的專案根目錄生成package.json檔案,-y 引數是省去填寫初始化的package.json資訊。
npm init -y

接著安裝webpack、webpack-cli、webpack-dev-server

npm install webpack webpack-cli webpack-dev-server --save-dev
  1. 建立src資料夾存放程式碼等檔案,在src資料夾下建立webpack入口檔案app.tsx
  2. 建立config資料夾用來儲存我們的webpack組態檔,方便擴充套件管理,使用webpack-merge庫將我們寫的組態檔組合起來(記得用npm安裝webpack-merge)。
  • 建立基礎組態檔 webpack.base.js
  • 建立開發環境組態檔 webpack.dev.js
  • 建立生產環境組態檔 webpack.prod.js

組態檔編寫

webpack.base.js檔案(基礎設定)

const path = require("path");

// 當前專案工作目錄根路徑
const RootProject = process.cwd();
// 根據引數獲取需要的絕對路徑
const getPath = (pathStr) => {
  return path.resolve(RootProject, `${pathStr}`);
};

module.exports = {
  // 入口檔案路徑
  entry:getPath("./src/app.tsx"), 
  // 使用外掛
  plugins: [],
  module: {
    // 對模組(module)應用 loader
    rules: [],
  },
};

webpack.dev.js檔案

const path = require("path");
// 合併檔案設定
const { merge } = require("webpack-merge");
// 基礎設定
const baseConfig = require("./webpack.base");
// 當前專案工作目錄根路徑
const RootProject = process.cwd();
// 根據引數獲取需要的絕對路徑
const getPath = (pathStr) => {
  return path.resolve(RootProject, `${pathStr}`);
};

// 開發環境設定
const devConfig = {
  // 開發模式
  mode: "development",
  // 只在發生錯誤時輸出
  stats: "errors-only",
  // source map風格
  devtool: "inline-source-map",
  // webpack-dev-server 設定
  devServer: {
    // 從目錄提供靜態檔案的選項(預設是 'public' 資料夾)
    static: getPath("./public"),
    // 啟用 webpack 的 熱模組替換 特性
    hot: true,
    // 當使用HTML5 History API時,index.html頁面可能要代替404響應。
    // 解決了使用react-router-dom 使用 BrowserRouter 模式時,在
    // 瀏覽器輸入路由地址是會請求介面報錯的問題
    historyApiFallback: true, 
  },
};

module.exports = merge(baseConfig, devConfig);

webpack.prod.js檔案

const path = require("path");
// 合併檔案設定
const { merge } = require("webpack-merge");
// 基礎設定
const baseConfig = require("./webpack.base");
// 當前專案工作目錄根路徑
const RootProject = process.cwd();
// 根據引數獲取需要的絕對路徑
const getPath = (pathStr) => {
  return path.resolve(RootProject, `${pathStr}`);
};

// 生產環境設定
const prodConfig = {  
  // 生產模式
  mode: "production",
  // 打包輸出設定
  output: {
    // 打包輸出路徑
    path: getPath("./dist"),
    // 打包輸出檔名
    filename: "[name]_[chunkhash:8].js",
    // 開啟打包清理資料夾功能
    clean: true,
  },
  // 使用外掛
  plugins: [],
};

module.exports = merge(baseConfig, prodConfig);

接著我們在package.json檔案新增執行指令

--config引數是指定執行使用的組態檔路徑

當我們在命令列使用npm run start命令的時候就會執行webpack-dev-server開啟 web server服務,並具有實時重新載入的功能,當我們修改檔案時,不需要手動重新整理瀏覽器,就能看到修改後的效果。

當我們在命令列使用npm run start命令的時候就會執行webpack構建打包我們的專案

至此webpack的基礎設定結構就寫好了,接下來就是開始完善組態檔功能。

使用html-webpack-plugin

這個外掛可以幫我們生成一個html檔案,在 body 中使用 script 標籤引入你所有 webpack 生成的 bundle,也即是我們output輸出的檔案。這樣就不需要每次手動建立一個html檔案,在手動引入我們打包好的js檔案。
安裝依賴

npm install --save-dev html-webpack-plugin

在我們的webpack.base.js加入使用

// webpack.base.js
// 生成一個 HTML5 檔案, 在 body 中使用 script 標籤引入你所有 webpack 生成的 bundle
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
    new HtmlWebpackPlugin({
      // 模版檔案
      template: getPath("./public/index.html"),
      filename: "index.html",
      inject: true,
      minify: {
        html5: true, // 根據HTML5規範解析輸入
        collapseWhitespace: true, // 摺疊構成檔案樹中文位元組點的空白
        preserveLineBreaks: false, // 當標籤之間的空格包含換行符時,總是摺疊為1個換行符(永遠不要完全刪除它)。必須與collapseWhitespace=true一起使用
        minifyCSS: true, // 在樣式元素和樣式屬性中縮小CSS(使用clean-css)
        minifyJS: true, // 在指令碼元素和事件屬性中最小化JavaScript(使用Terser)
        removeComments: false, // 帶HTML註釋
      },
    }),
}

解析jsx語法

  • 將es2015+的語法程式碼轉換為向後相容的 JavaScript 語法
  • 解析jsx語法

為了提升webpack構建速度,我們可以在使用loader的時候限定解析的範圍,使用include,這裡需要注意的是,
一般設定src,但是有時node_modules的第三方依賴打包好的庫,可能含有es6的語法,為了更好的相容性,我們可以把第三方的依賴庫通過include加入到解析名單,比如:include:["./src","./node_modules/react-router-dom"],不過覺得麻煩的話,又不在意構建速度的話,可以不限定解析範圍。

這裡我們使用babel-loader來幫助我們解析jsx和es2015以後的版本語法。

使用core-js給我們新的js api打修補程式(polyfill)。

關於babel可前往babel官網檢視更多設定用法。

安裝所需依賴

npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react core-js

接著在webpack.base.js檔案中新增babel-loader解析js,jsx檔案

// webpack.base.js
//...
 module: {
    // 對模組(module)應用 loader
    rules: [
      {
        test: /\.jsx?$/,
        // include限定解析範圍
        include: getPath("./src"),
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: [
                [
                   // 解析js預設
                  "@babel/preset-env",
                  {
                    // 自動打修補程式
                    useBuiltIns: "usage",
                    // 指定core-js版本
                    corejs: { version: "3.24.1", proposals: true },
                  },
                ],
                // 解析jsx預設
                "@babel/preset-react",
              ],
              plugins: [],
            },
          },
        ],
      },
    ],
  },
//...

解析css、less檔案

  • 解析css、less
  • 提取css成單獨檔案
  • css瀏覽器字首補齊
  • 開啟css modules
  • 引入全域性less變數

安裝所需依賴

npm install --save-dev css-loader less less-loader mini-css-extract-plugin postcss-loader postcss style-resources-loader

這裡需要注意的是css字首補齊,還需要在package.json檔案新增browserlist屬性指定版本,不然css補齊字首不生效。

//webpack.base.js
// 將 CSS 提取到單獨的檔案中,為每個包含 CSS 的 JS 檔案建立一個 CSS 檔案,並且支援 CSS 和 SourceMaps 的按需載入
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//...
plugins: [
    new MiniCssExtractPlugin({
      // 提取css檔案 檔名
      filename: "[name]_[contenthash:8].css",
    }),
]
 module: {
    // 對模組(module)應用 loader
    rules: [
       {
        test: /\.css$/,
        use: [
          // "style-loader",
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              modules: {
                auto: true,
                localIdentName: "[path][name]__[local]--[hash:base64:5]",
              },
              importLoaders: 1,
            },
          },
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: ["postcss-preset-env"],
              },
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          // "style-loader", //將style插入到head中
          MiniCssExtractPlugin.loader, // 提取css成單獨檔案
          {
            loader: "css-loader",
            options: {
              modules: {
                auto: true, // 允許根據檔名中含有module的檔案開啟css modules
                localIdentName: "[path][name]__[local]--[hash:base64:5]", // 允許設定生成的本地識別符號(ident)
              },
              // css-loader之前載入多少個loader
              importLoaders: 3,
            },
          },
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                // 包含新增css字首的預設
                plugins: ["postcss-preset-env"],
              },
            },
          },
          {
            loader: "less-loader",
            options: {
              // 生成source map
              sourceMap: true,
            },
          },
          {
            // 將定義全域性的less變數注入到其他樣式檔案,無須手動引入
            loader: "style-resources-loader",
            options: {
              patterns: [getPath("./src/global.less")],
            },
          },
        ],
      },
    ],
  },
//...

解析圖片資源

// webpack.base.js
//...
module:{
    rules: [
      {
        test: /\.(png|jpg|gif|jpeg)$/,
        type: "asset/resource",
      },
    ]
}
//...

解析字型資源

// webpack.base.js
//...
module:{
    rules: [
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        type: "asset/resource",
      },
    ]
}
//...

解析ts、tsx

安裝所需依賴

npm install --save-dev typescript ts-loader fork-ts-checker-webpack-plugin

安裝完依賴後,初始化ts生成ts組態檔tsconfig.json,使用如下指令(確保當前命令列所處位置為專案根目錄):

 node_modules/.bin/tsc --init


修改tsconfig.json檔案的設定(關於設定可前往ts官網查詢)

{
    "compilerOptions": {
        "target": "ES5",
        "module": "ES6",
        "jsx": "react",
        "paths": {
          "@/*": ["./src/*"]
        },
        "sourceMap": true,
    }
}
// webpack.base.js
// 在一個獨立程序上執行TypeScript型別檢查器的Webpack外掛。
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
//...
plugins: [
    new ForkTsCheckerWebpackPlugin()
],
module:{
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        include: getPath("./src"),
      },
    ]
}
//...

命令列顯示的資訊優化

通常我們執行專案時都會顯示很多構建資訊

每次重新編譯都會有一大堆資訊,顯然不利於我們專注研發,為此我們可以通過stats設定顯示資訊的風格。

在使用一個外掛優化顯示命令列顯示。

npm install --save-dev @soda/friendly-errors-webpack-plugin
// webpack.base.js
// 命令列提示優化外掛
const FriendlyErrorsWebpackPlugin = require("@soda/friendly-errors-webpack-plugin");
module.exports = {
// ...
// 只在發生錯誤時輸出
stats: "errors-only",
plugins: [new FriendlyErrorsWebpackPlugin()]
// ...
}

效果如下

eslint prettier設定

使用eslint和prettier幫助我們檢查程式碼格式是否正確和統一程式碼格式。

vscode安裝ESLint、Prettier外掛。

這裡我們使用airbnb公司的eslint基礎規則。

先安裝依賴

npm install --save-dev eslint-config-airbnb eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y

然後在建立.eslintrc.js檔案,設定我們的eslint規則。設定選項可前往eslint官網檢視。

module.exports = {
  env: {
    node: true, // 啟用node中全域性變數
    browser: true, // 啟用瀏覽器中全域性變數
  },
  // extends: "eslint:recommended",
  extends: ["airbnb", "airbnb/hooks", "plugin:prettier/recommended", "prettier"],

  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: "latest",
    sourceType: "module",
  },
  settings: {
    "import/resolver": {
      webpack: {
        config: "./config/webpack.base.js",
      },
    },
  },
  rules: {
    // "off" or 0 - 關閉規則
    // "warn" or 1 - 將規則視為一個警告(不會影響退出碼)
    // "error" or 2 - 將規則視為一個錯誤 (退出碼為1)

    // 要求或禁止在類成員之間出現空行
    "lines-between-class-members": [0, "always"],
    // 允許的擴充套件集是可設定的 jsx可以出現在js中
    "react/jsx-filename-extension": [
      1,
      {
        extensions: [".js", ".jsx"],
      },
    ],
    // 函陣列件宣告方式
    "react/function-component-definition": [
      2,
      {
        namedComponents: ["function-declaration", "function-expression", "arrow-function"],
        unnamedComponents: ["function-expression", "arrow-function"],
      },
    ],
    // 禁用 console
    "no-console": "off",
    // 要求 require() 出現在頂層模組作用域中
    "global-require": "off",
    "import/no-extraneous-dependencies": "off",
  },
};

接著安裝eslint-webpack-plugin外掛,該外掛使用 eslint 來查詢和修復 JavaScript 程式碼中的問題。

npm install eslint-webpack-plugin --save-dev

接著在我們webpack組態檔中使用eslint-webpack-plugin外掛

// webpack.base.js
const path = require("path");
// 使用 eslint 來查詢和修復 JavaScript 程式碼中的問題
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 當前專案工作目錄根路徑
const RootProject = process.cwd();
// 根據引數獲取需要的絕對路徑
const getPath = (pathStr) => {
  return path.resolve(RootProject, `${pathStr}`);
};
module.exports = {
    plugins: [
         new ESLintWebpackPlugin({
          // 指定檢查檔案的根目錄
          context: getPath("./src"),
        }),
    ]
}

eslint設定完後,在建立.prettierrc.js檔案設定Prettier。設定選項可前往Prettier官網檢視。

module.exports = {
  // 一行最多 100 字元
  printWidth: 100,
  // 關閉 tab 縮排
  useTabs: false,
  // 使用 2個tab 縮排
  tabWidth: 2,
  // 行尾需要有分號
  semi: true,
  // 使用單引號
  singleQuote: false,
  // 物件key是否使用引號 
  // as-needed 僅在需要的時候使用
  // consistent 有一個屬性需要引號,就都需要引號
  // preserve 保留使用者輸入的情況
  quoteProps: "as-needed",
  // jsx 使用單引號代替雙引號
  jsxSingleQuote: false,
  // 末尾不需要逗號 
  trailingComma: "all",
  // 大括號內的首尾需要空格
  bracketSpacing: true,
  // jsx 標籤的反尖括號需要換行
  jsxBracketSameLine: false,
  // 箭頭函數,只有一個引數的時候,也需要括號 <always|avoid>
  arrowParens: "always",
};

需要注意的是ESLint和Prettier設定可能會產生衝突,這時我們可以安裝eslint-config-prettier外掛和eslint-plugin-prettier外掛,來解決衝突。

eslint-config-prettier外掛 關閉所有可能與Prettier衝突的ESlint規則 使用要在eslint組態檔的extends陣列最後加"prettier"

eslint-plugin-prettier外掛 將Prettier作為ESLint的規則來使用,相當於程式碼不符合Prettier的規範時,會有提示資訊 使用要在eslint組態檔的extends陣列加入"plugin:prettier/recommended"

安裝依賴

npm install --save-dev eslint-config-prettier eslint-plugin-prettier prettier

這樣設定就完成了,如果有什麼需要修改的規則可在.eslintrc.js檔案中修改rules來修改elslint規則。

單元測試

我們可以寫一些單元測試檢測我們腳手架基礎功能是否有問題,比如是否生成js檔案,css檔案,index.html。

這裡我們使用mocha測試框架來編寫我們的測試用例。

安裝依賴

npm install --save-dev mocha

接著在我們專案下建立test資料夾

接著在test資料夾下建立index.js檔案編寫測試用例。

// index.js
// 這裡我們使用了glob-all庫來判斷是否有檔案(記得安裝這個庫)
const glob = require("glob-all");

describe("檢查是否生成了html檔案", () => {
  it("生成html檔案", (done) => {
    const files = glob.sync(["./dist/index.html"]);

    if (files.length > 0) {
      done();
    } else {
      throw new Error("生成html檔案失敗");
    }
  });
});

然後在package.json檔案加入執行測試指令

先執行指令npm run build指令打包,成功後在執行npm run test指令

這樣一個簡單的測試用例就完成了。

接下來就是檢視測試覆蓋率,可以使用nyc這個庫,有興趣可以去看看。

構建速度優化

隨著專案檔案的增多,webpack打包和執行專案的速度必然會下降,我們可以優化一下。

限定解析範圍(include,exclude)

我們可以在使用loader的時候,使用include,exclude去控制解析檔案的範圍,比如只解析src資料夾下的檔案include: ["./src"]。

開啟多程序構建

使用 thread-loader 來開啟多程序構建,提升我們的構建速度。

安裝依賴

npm install --save-dev thread-loader

然後在我們使用的loader前面加入(也就是放在use陣列第一位),因為loader的解析是從右往左解析的,所以放在第一個。

需要注意的是ts-loader配合thread-loader,我們得多一點額外的設定

// 在一個獨立程序上執行TypeScript型別檢查器的Webpack外掛。
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");

module.exports = {
    plugins: [
            new ForkTsCheckerWebpackPlugin({
              typescript: {
                diagnosticOptions: {
                  semantic: true,
                  syntactic: true,
                },
          },
        })
    ],
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: [
                    {
                        loader: "ts-loader",
                        options: {
                          happyPackMode: true,
                        },
                    }
                ]
            }
        ]
    }
}
// webpack官網thread-loader的例子
use: [
  {
    loader: "thread-loader",
    // 有同樣設定的 loader 會共用一個 worker 池
    options: {
      // 產生的 worker 的數量,預設是 (cpu 核心數 - 1),或者,
      // 在 require('os').cpus() 是 undefined 時回退至 1
      workers: 2,

      // 一個 worker 程序中並行執行工作的數量
      // 預設為 20
      workerParallelJobs: 50,

      // 額外的 node.js 引數
      workerNodeArgs: ['--max-old-space-size=1024'],

      // 允許重新生成一個僵死的 work 池
      // 這個過程會降低整體編譯速度
      // 並且開發環境應該設定為 false
      poolRespawn: false,

      // 閒置時定時刪除 worker 程序
      // 預設為 500(ms)
      // 可以設定為無窮大,這樣在監視模式(--watch)下可以保持 worker 持續存在
      poolTimeout: 2000,

      // 池分配給 worker 的工作數量
      // 預設為 200
      // 降低這個數值會降低總體的效率,但是會提升工作分佈更均一
      poolParallelJobs: 50,

      // 池的名稱
      // 可以修改名稱來建立其餘選項都一樣的池
      name: "my-pool"
    },
  },
  // 耗時的 loader(例如 babel-loader)
];

開啟多程序壓縮

webpack5預設是使用TerserWebpackPlugin外掛來壓縮js,可使用多程序並行執行以提高構建速度。
把下面的設定加入到我們的webpack.prod.js組態檔中,開啟多程序並行執行壓縮。

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        // 開啟多程序並行執行
        parallel: true,
      }),
    ],
  },
};

預編譯資源

預編譯資源就是我們可以把不需要經常變動的第三方依賴,如react,react-dom等,提前打包好,在webpack構建時不用在去編譯已經編譯好的依賴了,這樣就可以大幅提升構建速度。

在webpack中,我們使用DllPlugin 和 DllReferencePlugin這兩個webpack自帶的外掛,幫助我們完成資源預編譯。

首先在我們的config資料夾下建立webpack.dll.js,寫入如下程式碼

const path = require("path");
const webpack = require("webpack");

// 當前專案工作目錄路徑
const RootProject = process.cwd();

module.exports = {
  entry: {
    // 根據你的需要新增,這裡是把react,react-dom預編譯
    library: ["react", "react-dom"],
  },
  output: {
    filename: "[name].dll.js",
    path: path.resolve(RootProject, "./library"),
    library: "[name]_[hash]",
  },
  plugins: [
    new webpack.DllPlugin({
      // 與output.library保持一致
      name: "[name]_[hash]",
      path: path.resolve(RootProject, "./library/[name]-manifest.json"),
    }),
  ],
};

然後在package.json加入執行構建指令

然後我們預編譯資源的設定就寫好了,接著安裝react,react-dom,

npm install react react-dom 

接著在命令列執行npm run dll指令生成預編譯資源資源

可以看到成功生成了library資料夾及資料夾下的預編譯資源library.dll.js,資料夾中json檔案是給DllReferencePlugin使用告訴webpack哪些是編譯好的資源,不用在編譯了。

最後在我們的webpack.prod.js檔案中使用DllReferencePlugin。還有幫我們把預編譯資源引入到專案中的外掛add-asset-html-webpack-plugin(其實就是把我們生成的預編譯檔案複製一份到webpack的ouput輸出目錄,並把它通過script標籤引入到output輸出目錄下的index.html檔案中)

npm install --save-dev add-asset-html-webpack-plugin
// webpack.prod.js
const webpack = require("webpack");
// 將指定js檔案提取至壓縮目錄,並用script標籤引入
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");

module.exports = {
    plugins: [
        new webpack.DllReferencePlugin({
          // 編譯時用於載入 JSON manifest 的絕對路徑
          manifest: require(getPath("./library/library-manifest.json")),
        }),
        new AddAssetHtmlPlugin({
          filepath: getPath("./library/library.dll.js"),
          publicPath: "./",
        }),
    ]
}

這樣就完成了預解析資源。

構建體積優化

隨著專案檔案的增多,打包出來bundle體積會越來越大,我們可以做些優化。

使用cdn引入第三方庫

防止將某些import的包(package)打包到 bundle 中,而是在執行時(runtime)再去從外部獲取這些擴充套件依賴。
比如下面的例子,lodash不會被打包到專案中,而是通過cdn的形式引入。

// 官方檔案例子
module.exports = {
  // ...
  externalsType: 'script',
  externals: {
    lodash: ['https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js', '_'],
  },
};

// 使用
import _ from 'lodash';
console.log(_.head([1, 2, 3]));

包體積分析,針對優化

我們可以使用webpack-bundle-analyzer外掛,分析打包好的檔案大小,便於我們分析優化哪個檔案過大,針對的去做一些優化。

安裝依賴

npm install --save-dev webpack-bundle-analyzer
// webpack.prod.js
// 分解構建體積外掛
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = {
    plugins: [
        new BundleAnalyzerPlugin()
    ]
}

執行完構建效果如下

圖片壓縮

圖片壓縮我們可以使用image-minimizer-webpack-plugin外掛(下載可能有點慢,多下幾次),這裡我們使用無失真壓縮。

npm install --save-dev image-minimizer-webpack-plugin imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo
// webpack.prod.js
// 圖片壓縮外掛(使用無失真壓縮設定)
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
module.exports = {
    new ImageMinimizerPlugin({
      minimizer: {
        // implementation: ImageMinimizerPlugin.imageminMinify,
        implementation: ImageMinimizerPlugin.imageminGenerate,
        options: {
          // Lossless optimization with custom option
          // Feel free to experiment with options for better result for you
          plugins: [
            ["gifsicle", { interlaced: true }],
            ["jpegtran", { progressive: true }],
            ["optipng", { optimizationLevel: 5 }],
            // Svgo configuration here https://github.com/svg/svgo#configuration
            [
              "svgo",
              {
                plugins: [
                  {
                    name: "preset-default",
                    params: {
                      overrides: {
                        // customize default plugin options
                        inlineStyles: {
                          onlyMatchedOnce: false,
                        },
                        // or disable plugins
                        removeDoctype: false,
                      },
                    },
                  },
                ],
              },
            ],
          ],
        },
      },
    }),
}

css壓縮

我們可以使用css-minimizer-webpack-plugin這個外掛來幫我們壓縮css檔案。

需要注意的是,使用這個外掛會導致terser-webpack-plugin外掛壓縮js失效,解決辦法是再次引入terser-webpack-plugin

安裝依賴

npm install --save-dev css-minimizer-webpack-plugin
// webpack.prod.js
// 這個外掛使用 cssnano 優化和壓縮 CSS。
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 壓縮js外掛
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
    optimization: {
        minimize: true,
        minimizer: [
          new CssMinimizerPlugin(),
          new TerserPlugin({
            parallel: 4
          }),
        ],
    }
}

SplitChunksPlugin

我們可以通過SplitChunksPlugin來抽取公共資源包,如lodash等,達到減小打包的體積。
使用如下設定,當我們專案中參照到了lodash庫的時候,lodash會被單獨抽取出來,在名為vendor開頭的js檔案中。

module.exports = {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](lodash)[\\/]/,
          name: "vendor",
          chunks: "all",
        },
      },
    },
  },
}

關於自動部署

之前買了個騰訊雲的伺服器,最近打算寫寫express練練手,但是發現個問題,我打包好的檔案怎麼弄到伺服器上去,然後就去百度瞭解了下ssh傳輸檔案,前提是你的伺服器要安裝openssh-server,可以百度一下安裝方法,安裝好了後,就可以使用sftp(安全檔案傳輸)通過登入密碼驗證的形式連線你的伺服器傳輸檔案。也可以設定金鑰連線省去每次輸入密碼。但是還是有個問題,伺服器端我用pm2管理我的專案資料夾,只要每次把新的打包檔案重新拷貝一份到伺服器端的專案資料夾下,就可以完成專案更新,這導致伺服器端的專案更新每次都得通過ssh去把檔案傳上去,這樣才能完成伺服器端的專案檔案更新,感覺有點麻煩,如果能執行完npm run build後自動把我的檔案傳到伺服器端該多省事呀,基於這個想法,查了查檔案寫了個自動部署的外掛@handsomezyw/auto-deploy-webpack-plugin完成這件事。

先安裝外掛

npm install --save-dev @handsomezyw/auto-deploy-webpack-plugin

使用方法也很簡單

// webpack.prod.js
// 自動部署到伺服器端外掛
const AutoDeployWebpackPlugin = require("@handsomezyw/auto-deploy-webpack-plugin");

module.exports = {
    plugins: [
        new AutoDeployWebpackPlugin({
          // 伺服器設定
          serverOptions: {
            // 伺服器使用者名稱
            username: "administrator",
            // 伺服器ip地址
            host: "xxx.xx.x.xxx",
            // 伺服器登入密碼
            password: "123456",
          },
          // 本地要上傳到伺服器端的資料夾路徑
          localPath: "/Users/zengyongwen/Desktop/study/webpack-study/dist",
          // 上傳到伺服器資料夾路徑(這裡需要注意的是,會把該資料夾先清理一遍,再上傳)
          serverPath: "Desktop/my-system/public",
        }),
    ]
}

最後我們在安裝下react、react-dom的宣告檔案

npm install --save-dev @types/react @types/react-dom

至此一個基礎的react環境就搭建成功了。

總結

學習webpack的時候,我也是雲裡霧裡,一開始就對著官方檔案看,越看越頭大,於是就去找一些關於webpack的文章和視訊學習,發現好多都是webpack4的,於是我就對著官方檔案的例子改改,找一些webpack5的實現方案等,多動手實踐,慢慢的就對webpack的設定有了一點點了解,總的來說,搭建出一個自己的腳手架,還是有點成就感,哈哈。