Re:用webpack從零開始的vue-cli搭建'生活'

會飛的一棵樹 發表於 2022-07-16
Webpack Vue

有了vue-cli的幫助,我們建立vue的專案非常的方便,使用vue create然後選擇些需要的配置項就能自動幫我們建立配置好的webpack專案腳手架了,實在是‘居家旅行’必備良藥。這次藉著學習webpack的機會,不用vue-cli搭建一個vue專案。

注:基於webpack5,其執行於 Node.js v10.13.0+ 的版本。

完整程式碼:https://github.com/mashiro-cat/learn_webpack

webpack基礎

webpack官網:https://webpack.js.org/
webpack中文官網:https://webpack.docschina.org/
安裝:

npm i webpack webpack-cli -D

執行:

npx webpack ./src/main.js --mode=development
# 根目錄有配置檔案
npx webpack

開啟文件就能看到五個核心配置點:

  1. 入口(entry)
  2. 輸出(output)
  3. loader
  4. 外掛(plugin)
  5. 模式(mode)

webpack本身只提供了對js中ES Module和壓縮的支援,很多功能都要通過使用loader或者plugin擴充。

webpack.config.js配置檔案編寫:

module.exports = {
  // 入口 多入口則配置成物件形式
  entry:"",
  // 輸出 需使用絕對路徑
  output:{},
  // loader
  module:{
    rules:[]
  },
  // 外掛
  plugins:[],
  // development 或者 production
  // 生產模式預設開啟js和html壓縮
  mode:"development"
}

樣式資源處理

配置資源輸出的路徑和名稱

輸出:

output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'static/js/main.js' // 將js輸出到 static/js 目錄中
  }

module中:

generator: {
          // 將圖片檔案輸出到 static/imgs 目錄中
          // 將圖片檔案命名 [hash:8][ext][query]
          // [hash:8]: hash值取8位 直接[hash]則不擷取
          // [ext]: 使用之前的副檔名
          // [name]: 會使用之前的名字
          // [query]: 新增之前的query引數
          filename: "static/imgs/[hash:8][ext][query]",
        },

css處理

安裝兩個loader,其使用順序是css-loader會處理css,而將編譯的css經style-loader後會動態建立style標籤。
css-loader

# 安裝
npm i css-loader style-loader -D
rules: [
      // 兩個loader順序按此 它會先使用後面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] }
    ]
提取css到單獨檔案

現在是css全部是打包到js中,然後動態插入的。若需要提取到單獨檔案,則可以藉助外掛。

// 安裝外掛
npm i mini-css-extract-plugin -D

// 配置外掛
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// 將styel-loader改為MiniCssExtractPlugin.loader
{
  // 用來匹配 .css 結尾的檔案
  test: /\.css$/,
  // use 陣列裡面 Loader 執行順序是從右到左
  use: [MiniCssExtractPlugin.loader, "css-loader"],
},

plugins:[
new MiniCssExtractPlugin({
      // 定義輸出檔名和目錄
      filename: "static/css/main.css",
    }),
]
css相容處理
// 安裝
npm i postcss-loader postcss postcss-preset-env -D

// 配置

{
        // 用來匹配 .css 結尾的檔案
        test: /\.css$/,
        // use 陣列裡面 Loader 執行順序是從右到左
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          { // 在css-loader之後,前處理器loader之前
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env", // 能解決大多數樣式相容性問題
                ],
              },
            },
          },
        ],
      },

控制相容性:
package.json 檔案中新增 browserslist 來控制樣式的相容性的程度:

{
  // 其他省略
  //"browserslist": ["ie >= 8"]
  // 實際開發中我們一般不考慮舊版本瀏覽器了,所以我們可以這樣設定:
  // 所有瀏覽器的最新兩個版本 支援市面上99%瀏覽器 還沒死的瀏覽器
  "browserslist": ["last 2 version", "> 1%", "not dead"]
}
css壓縮

安裝外掛:

npm i css-minimizer-webpack-plugin -D

webpack配置:

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

plugins:[
  // css壓縮
  new CssMinimizerPlugin(),
]

前處理器

使用less,scss等預處理都要安裝對應的loader進行編譯,webpack才能識別處理。

less的使用:

// 安裝less-loader
npm i less-loader -D

// 配置
// less-loader將less轉為css後還是要交給css-loader處理的
{
  test: /\.less$/,
  use: ["style-loader", "css-loader", "less-loader"]
}

scss, sass的使用:

// 安裝
npm i sass-loader sass -D

// 配置
{
  test: /\.s[ac]ss$/,
  use: ["style-loader", "css-loader", "sass-loader"],
},
點選檢視完整配置

const path = require('path')

module.exports = {
  // 入口 多入口則配置成物件形式
  entry: "./src/main.js",
  // 輸出 需使用絕對路徑
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  // loader
  module: {
    rules: [
      // 兩個loader順序按此 它會先使用後面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] },
      // less-loader將less轉為css後還是要交給css-loader處理的
      { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ]
  },
  // 外掛
  plugins: [],
  // development 或者 production
  mode: "development"
}

圖片資源處理

Webpack4使用file-loader 和 url-loader處理圖片資源,而webpack5將那倆都內建了,直接配置開啟就可。

{
  test: /\.(png|jpe?g|gif|webp)$/,
  type: "asset",
},

將小於某個大小的圖片轉化成Base64可新增此配置:

{
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小於10kb的圖片會被base64處理
          }
        }
},
點選檢視完整配置

const path = require('path')

module.exports = {
  // 入口 多入口則配置成物件形式
  entry: "./src/main.js",
  // 輸出 需使用絕對路徑
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  // loader
  module: {
    rules: [
      // 兩個loader順序按此 它會先使用後面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] },
      // less-loader將less轉為css後還是要交給css-loader處理的
      { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小於10kb的圖片會被base64處理
          }
        }
      },
    ]
  },
  // 外掛
  plugins: [],
  // development 或者 production
  mode: "development"
}

其它資源處理

若專案中引用了字型,視訊等資源,則是希望不要處理它,直接輸出就好了。配置為type: "asset/resource"它就會原封不動的輸出了。

{
        // 處理字型圖示或者視訊等其它資源
        test: /\.(ttf|woff2?|map4|map3)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      }
點選檢視完整配置

const path = require('path')

module.exports = {
  // 入口 多入口則配置成物件形式
  entry: "./src/main.js",
  // 輸出 需使用絕對路徑
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'static/js/main.js', // 將js輸出到 static/js 目錄中
    clean: true
  },
  // loader
  module: {
    rules: [
      // 兩個loader順序按此 它會先使用後面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] },
      // less-loader將less轉為css後還是要交給css-loader處理的
      { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小於10kb的圖片會被base64處理
          }
        },
        generator: {
          // 將圖片檔案輸出到 static/imgs 目錄中
          // 將圖片檔案命名 [hash:8][ext][query]
          // [hash:6]: hash值取6位
          // [ext]: 使用之前的副檔名
          // [query]: 新增之前的query引數
          filename: "static/imgs/[hash:6][ext][query]",
        },
      },
      {
        // 處理字型圖示或者視訊等其它資源
        test: /\.(ttf|woff2?|map4|map3)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
    ]
  },
  // 外掛
  plugins: [],
  // development 或者 production
  mode: "development"
}

js資源處理

程式碼質量檢測 Eslint

安裝:

npm i eslint-webpack-plugin eslint -D

在webpack配置中使用eslint外掛

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

plugins: [
    new ESLintWebpackPlugin({
      // 指定檢查檔案的根目錄
      context: path.resolve(__dirname, "src"),
    }),
  ],

編寫配置:
配置檔案由很多種寫法:.eslintrc.*:新建檔案,位於專案根目錄

  • .eslintrc
  • .eslintrc.js
  • .eslintrc.json

區別在於配置格式不一樣package.json 中 eslintConfig:不需要建立檔案,在原有檔案基礎上寫,ESLint 會查詢和自動讀取它們,所以以上配置檔案只需要存在一個即可

根目錄建立.eslintrc.js配置檔案

// .eslintrc.js
module.exports = {
  // 解析配置項
  parserOptions: {
    ecmaVersion: 6, // ES 語法版本
    sourceType: "module", // ES 模組化
  },
  env: {
    node: true, // 啟用node中全域性變數
    browser: true, // 啟用瀏覽器中全域性變數 不開啟則像 console Math 等全域性變數無法使用
  },
  // 繼承規則
  extends: ['eslint:recommended'],
  // 檢測規則
  // 自定義的規則會覆蓋繼承的規則
  // "off" 或 0 - 關閉規則
  // "warn" 或 1 - 開啟規則,使用警告級別的錯誤:warn (不會導致程式退出)
  // "error" 或 2 - 開啟規則,使用錯誤級別的錯誤:error (當被觸發的時候,程式會退出)
  rules: {
    semi: "off", // 禁止使用分號
    'array-callback-return': 'warn', // 強制陣列方法的回撥函式中有 return 語句,否則警告
    'default-case': [
      'warn', // 要求 switch 語句中有 default 分支,否則警告
      { commentPattern: '^no default$' } // 允許在最後註釋 no default, 就不會有警告了
    ],
    eqeqeq: [
      'warn', // 強制使用 === 和 !==,否則警告
      'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少數情況下不會有警告
    ],
  },
}

babel相容處理

安裝:

npm i babel-loader @babel/core @babel/preset-env -D

babel配置編寫:
配置檔案由很多種寫法:

  1. babel.config.*:新建檔案,位於專案根目錄
  • babel.config.js
  • babel.config.json
  1. .babelrc.*:新建檔案,位於專案根目錄
  • .babelrc
  • .babelrc.js
  • .babelrc.json

package.json 中 babel:不需要建立檔案,在原有檔案基礎上寫

presets 預設:
簡單理解:就是一組 Babel 外掛, 擴充套件 Babel 功能
@babel/preset-env: 一個智慧預設,允許您使用最新的 JavaScript。
@babel/preset-react:一個用來編譯 React jsx 語法的預設
@babel/preset-typescript:一個用來編譯 TypeScript 語法的預設

// 建立.babelrc.js
module.exports = {
  presets: ["@babel/preset-env"],
};

webpack增加babel

{
  test: /\.js$/,
  exclude: /node_modules/, // 排除node_modules程式碼不編譯
  loader: "babel-loader",
},

html自動匯入處理

安裝html-webpack-plugin

npm i html-webpack-plugin -D

webpack配置, 配置好後就會自動的引入所需的js了。

const HtmlWebpackPlugin = require("html-webpack-plugin");

plugins: [
    new HtmlWebpackPlugin({
      // 以 public/index.html 為模板建立檔案
      // 新的html檔案有兩個特點:1. 內容和原始檔一致 2. 自動引入打包生成的js等資源
      template: path.resolve(__dirname, "public/index.html"),
    }),
]

webpackSever

使用webpacksever後,在開發時就能自動檢測檔案變化,並實時編譯展示出來了。

// 安裝
npm i webpack-dev-server -D

// 配置
devServer: {
    host: "localhost", // 啟動伺服器域名
    port: "3000", // 啟動伺服器埠號
    open: true, // 是否自動開啟瀏覽器
  },

執行:

// 此時不會打包生成檔案,都是在記憶體中進行編譯的
npx webpack serve
點選檢視完整配置

const path = require('path')
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  // 入口 多入口則配置成物件形式
  entry: "./src/main.js",
  // 輸出 需使用絕對路徑
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'static/js/main.js', // 將js輸出到 static/js 目錄中
    clean: true
  },
  // loader
  module: {
    rules: [
      // 兩個loader順序按此 它會先使用後面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] },
      // less-loader將less轉為css後還是要交給css-loader處理的
      { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小於10kb的圖片會被base64處理
          }
        },
        generator: {
          // 將圖片檔案輸出到 static/imgs 目錄中
          // 將圖片檔案命名 [hash:8][ext][query]
          // [hash:6]: hash值取6位
          // [ext]: 使用之前的副檔名
          // [query]: 新增之前的query引數
          filename: "static/imgs/[hash:6][ext][query]",
        },
      },
      {
        // 處理字型圖示或者視訊等其它資源
        test: /\.(ttf|woff2?|map4|map3)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
      { // babel配置
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules程式碼不編譯
        loader: "babel-loader",
      },
    ]
  },
  // 外掛
  plugins: [
    new ESLintWebpackPlugin({
      // 指定檢查檔案的根目錄
      context: path.resolve(__dirname, "src"),
    }),
    new HtmlWebpackPlugin({ // html處理的外掛
      // 以 public/index.html 為模板建立檔案
      // 新的html檔案有兩個特點:1. 內容和原始檔一致 2. 自動引入打包生成的js等資源
      template: path.resolve(__dirname, "public/index.html")
    })
  ],
  devServer: {
    host: "localhost", // 啟動伺服器域名
    port: "666", // 啟動伺服器埠號
    open: true, // 是否自動開啟瀏覽器
  },
  // development 或者 production
  mode: "development"
}

webpack進階

使用sourcemap

SourceMap(原始碼對映)是一個用來生成原始碼與構建後程式碼一一對映的檔案的方案。
通過檢視Webpack DevTool文件可知,SourceMap 的值有很多種情況.
開發時我們只需要關注兩種情況即可:
開發模式:cheap-module-source-map,優點:打包編譯速度快,只包含行對映,缺點:沒有列對映
生產模式:source-map,優點:包含行/列對映,缺點:打包編譯速度更慢。

配置:

devtool: "cheap-module-source-map",

提升打包速度

HotModuleReplacement:它(HMR/熱模組替換):在程式執行中,替換、新增或刪除模組,而無需重新載入整個頁面。

module.exports = {
  // 其他省略
  devServer: {
    host: "localhost", // 啟動伺服器域名
    port: "3000", // 啟動伺服器埠號
    open: true, // 是否自動開啟瀏覽器
    hot: true, // 開啟HMR功能(只能用於開發環境,生產環境不需要了)
  },
};

此時 css 樣式經過 style-loader 處理,已經具備 HMR 功能了。 但是 js 可以使用vue-loader, react-hot-loader實現。

OneOf配置(開發和正式都能用):
匹配到一條規則就不繼續匹配了

module: {
    rules: [
        {
            oneOf: [
                { test: /\.css$/, use: ["style-loader", "css-loader"] },
                .........
            ]
        }
    ]
}

Include/Exclude
如在配置babel排除node_moudels資料夾

使用快取Cache
每次打包時 js 檔案都要經過 Eslint 檢查 和 Babel 編譯,速度比較慢。我們可以快取之前的 Eslint 檢查 和 Babel 編譯結果,這樣第二次打包時速度就會更快了

// babel
{
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules程式碼不編譯
            include: path.resolve(__dirname, "../src"), // 也可以用包含
            loader: "babel-loader",
            options: {
              cacheDirectory: true, // 開啟babel編譯快取
              cacheCompression: false, // 快取檔案不要壓縮
            },
          },


// Eslint
new ESLintWebpackPlugin({
      // 指定檢查檔案的根目錄
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 預設值
      cache: true, // 開啟快取
      // 快取目錄
      cacheLocation: path.resolve(
        __dirname,
        "../node_modules/.cache/.eslintcache"
      ),
    })

打包啟用多執行緒:
開啟執行緒也需要時間,小專案可能提升不明顯。
npm i thread-loader -D安裝loader,然後配置:

// nodejs核心模組,直接使用
const os = require("os");
const TerserPlugin = require("terser-webpack-plugin"); // webpack自帶的js壓縮模組

// cpu核數
const threads = os.cpus().length;

// babel使用多執行緒
{
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules程式碼不編譯
            include: path.resolve(__dirname, "../src"), // 也可以用包含
            use: [
              {
                loader: "thread-loader", // 開啟多程式
                options: {
                  workers: threads, // 數量
                },
              },
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true, // 開啟babel編譯快取
                },
              },
            ],
          },

// Eslint使用多執行緒
new ESLintWebpackPlugin({
      // 指定檢查檔案的根目錄
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 預設值
      cache: true, // 開啟快取
      // 快取目錄
      cacheLocation: path.resolve(
        __dirname,
        "../node_modules/.cache/.eslintcache"
      ),
      threads, // 開啟多程式
    }),

// js壓縮使用多執行緒
optimization: {
    minimize: true,
    minimizer: [
      // css壓縮也可以寫到optimization.minimizer裡面,效果一樣的
      new CssMinimizerPlugin(),
      // 當生產模式會預設開啟TerserPlugin,但是我們需要進行其他配置,就要重新寫了
      new TerserPlugin({
        parallel: threads // 開啟多程式
      })
    ],
  },

減少的程式碼體檢

Tree Shaking: 預設開啟,通常用於描述移除 JavaScript 中的沒有使用上的程式碼。

Babel優化:
@babel/plugin-transform-runtime: 禁用了 Babel 自動對每個檔案的 runtime 注入,而是引入 @babel/plugin-transform-runtime 並且使所有輔助程式碼從這裡引用。

// 安裝
npm i @babel/plugin-transform-runtime -D

// 配置
{
                loader: "babel-loader",
                options: {
                  cacheDirectory: true, // 開啟babel編譯快取
                  cacheCompression: false, // 快取檔案不要壓縮
                  plugins: ["@babel/plugin-transform-runtime"], // 減少程式碼體積
                },
              },

優化程式碼執行效能

打包程式碼分塊
Preload / prefetch
使用Core-js
使用PWA

從零開始搭建vue-webpack專案

使用的庫:
設定環境變數:
https://www.npmjs.com/package/cross-env

// 安裝
npm install --save-dev cross-env

// package.json

vue-loder文件: https://vue-loader.vuejs.org/zh/
安裝vue-loader

npm i vue
npm install -D vue-loader vue-template-compiler

module: {
    rules: [
      // ... 其它規則
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // 請確保引入這個外掛!
    new VueLoaderPlugin()
  ]

樣式處理

style-loader 替換為 vue-style-loader,並安裝前處理器loader

npm i -D css-loader vue-style-loader less-loader less-loader sass-loader stylus-loader

設定擴充名自動佈局

resolve: {
    extensions: [".vue", ".js", ".json"], // 自動補全副檔名,讓vue可以使用
  },

Eslint配置指定為vue的

npm i -D @babel/eslint-parser

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
  parserOptions: {
    parser: "@babel/eslint-parser",
  },
};

Bable配置

npm i -D @vue/cli-plugin-babel

// babel.config.js
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"],
};

提供對js的變數,解決頁面警告

// 解決頁面警告
new DefinePlugin({
  __VUE_OPTIONS_API__: "true",
  __VUE_PROD_DEVTOOLS__: "false",
}),

除了以上vue中專用的配置,然後加上less,scss的loader,把前面的html外掛加上去。就是一個基本的vue-cli了。完整的配置可以看最前面的倉庫連結。

優化

按需引入第三庫

如 elment plus 按需引入可參照其官網進行配置

https://yk2012.github.io/sgg_webpack5/
https://vue-loader.vuejs.org/zh/
https://webpack.docschina.org/