🤖 作者簡介:水煮白菜王,一個web開發工程師 👻
👀 文章專欄: 前端專欄 ,記錄一下平時在博客寫作中,總結出的一些開發技巧和知識歸納總結?。
感謝支持💕💕💕
目錄
- Webpack 5 相較于 Webpack 4 的主要改進
- 安裝
- 生命周期
- Compiler Hooks
- use hooks
- webpack中的loader(轉換器)
- 工作原理
- 常用loader
- 自定義loader
- webpack中的plugins(插件)
- 工作原理
- 自定義plugins
- 打包過程
- 加速webpack打包速度和減小打包體積的優化
- webpack配置
- webpack配置拆分
- webpack.config.common.js文件公共環境配置
- webpack.config.dev.js文件開發環境配置 npx webpack -c ./webpack.config.dev.js
- webpack.config.prod.js文件生產環境配置 npx webpack -c ./webpack.config.prod.js
- webpack.config.js 運行:webpack -c ./webpack.config.js --env development
- 封裝webpack自定義插件
- 如果你覺得這篇文章對你有幫助,請點贊 👍、收藏 👏 并關注我!👀
Webpack 5 相較于 Webpack 4 的主要改進
- 性能改進:webpack5在構建速度和打包體積方面進行了一些優化。它引入了持久緩存,可以減少構建時間。
此外,webpack5還引入了更好的樹搖(tree shaking)算法,可以更好地優化打包體積。 - 模塊聯邦(Module Federation)新增特性:這是webpack5中最重要的新功能之一。模塊聯邦允許不同的應用程序共享模塊,從而實現更好的代碼復用和拆分。這對于構建大型的微服務架構非常有用。
new ModuleFederationPlugin({name: 'myApp',filename: 'remoteEntry.js',remotes: {},exposes: {'./Button': './src/Button',},shared: { react: { singleton: true } },
});
- 支持WebAssembly:webpack5對WebAssembly提供了更好的支持。它可以直接導入和導出WebAssembly模塊,并且可以通過配置進行優化。
- 緩存策略增強:webpack5引入了更好的緩存策略,可以更好地利用瀏覽器緩存。這可以減少用戶在更新應用程序時需要下載的文件數量。
- Tree Shaking 改進:webpack5引入了更好的Tree Shaking算法,可以更好地識別和刪除未使用的代碼。這可以進一步減少打包體積。
- 改進的持久緩存:webpack5引入了更好的持久緩存策略,可以更好地利用緩存。這可以減少構建時間。
特性 | Webpack 5 改進 |
---|---|
性能改進 | Webpack 5 在構建速度和打包體積方面進行了優化。引入了持久緩存機制,顯著減少重復構建時間。 |
模塊聯邦(Module Federation) | 引入模塊聯邦功能,允許不同應用之間共享模塊,提升代碼復用能力,適用于微服務架構和微前端項目。 |
WebAssembly 支持增強 | 原生支持 WebAssembly 模塊的導入與導出,無需額外 loader,支持異步加載、Tree Shaking 和 Code Splitting。 |
緩存策略增強 | 引入更智能的瀏覽器緩存策略(如 deterministic ID 算法),提升長期緩存利用率,減少用戶更新時的資源下載量。 |
Tree Shaking 改進 | 使用更高級的 Tree Shaking 算法,支持導出級(export-level)和嵌套級的未使用代碼識別與刪除,進一步減小打包體積。 |
持久緩存改進 | 引入基于文件系統的持久化緩存(cache: { type: 'filesystem' } ),大幅提升開發階段的增量構建速度。 |
其中最顯著的變化是 webpack5 引入了持久化緩存機制,使得構建速度大幅提升。此外,webpack5 改進了長期緩存策略,支持更好的 Tree Shaking 和代碼分割功能
安裝
npm init -y // 初始化package.json
npm install webpack webpack-cli --save-devnpx webpack --watch // 監聽文件修改
npx webpack-dev-server // 以server的方式啟動項目,不會打包物理文件,而是輸出到內存
生命周期
Compiler Hooks
beforeRun:在webpack開始運行之前調用,執行全局初始化邏輯,例如異步加載配置、插件依賴項等。
run:在webpack開始運行時調用,可以在此做一些全局處理 ,一些初始化操作。
beforeCompile:在webpack開始編譯之前調用,修改編譯參數、注入額外上下文。
compile:在webpack開始編譯時調用,初始化與本次編譯相關的狀態。
make:在webpack開始構建編譯器時調用,啟動模塊解析、構建等操作,可在此階段添加自定義入口點或資源。
afterCompile:在webpack完成編譯之后調用,獲取完整的模塊依賴圖,進行最終分析或優化。
emit:在webpack生成最終的資源之前調用,添加、修改或刪除最終輸出的資源(如生成額外文件)。
afterEmit:在webpack生成最終的資源之后調用,做一些清理工作或觸發后續流程。
done:在webpack完成構建之后調用,清理臨時資源、輸出構建信息、發送通知。
Hook 名稱 | 觸發時機 | 使用場景 |
---|---|---|
beforeRun | 在 Compiler 開始運行前觸發(如執行 webpack() ) | 可用于初始化插件或設置運行時配置 |
run | 在 Compiler 開始運行時觸發(異步) | 類似入口點,可以在此做一些全局處理 ,一些初始化操作 |
beforeCompile | 在編譯開始前觸發 | 準備編譯所需的數據或資源 |
compile | 在編譯開始時觸發 | 初始化編譯過程,執行一些初始化操作,例如創建 Compilation 實例 |
thisCompilation | 在 Compilation 創建之前觸發 | 用于監聽后續的 Compilation 生命周期 |
compilation | 在 Compilation 被創建后觸發 | 插件可在此接入 Compilation 生命周期 |
make | 在模塊解析開始時觸發 | 可以添加/修改模塊依賴關系 |
afterCompile | 在編譯完成后觸發 | 對編譯結果做最后調整或校驗 |
emit | 在生成最終輸出資源之前觸發 | 可以修改輸出內容,如添加額外文件 |
afterEmit | 在輸出資源之后觸發 | 清理 emit 階段使用的臨時數據 |
done | 整個構建流程完成之后觸發 | 執行清理工作或輸出構建耗時信息 |
failed | 構建失敗時觸發 | 處理異常、記錄錯誤日志 |
use hooks
module.exports = {// ...plugins: [{apply: (compiler) => {compiler.hooks.beforeRun.tap('MyPlugin', () => {console.log('Before run');});compiler.hooks.done.tap('MyPlugin', () => {console.log('Build done');});}}]
};//在這個示例中,我們定義了一個自定義插件,并利用beforeRun和done兩個生命周期鉤子函數。在這些鉤子函數中,我們可以實現自定義行為,如輸出日志信息。
webpack中的loader(轉換器)
工作原理
webpack loader 在 webpack 構建過程中的生命周期中的工作主要分為以下幾個階段:
-
解析階段:webpack 會根據配置文件中的入口文件,遞歸解析所有的依賴模塊。在這個階段,webpack 會根據文件的后綴名來確定使用哪個 loader 來處理該文件。
-
編譯階段:在這個階段,webpack 會將解析后的模塊轉換成 AST(抽象語法樹),并且根據配置文件中的規則,將模塊中的代碼進行轉換和處理。
這個階段是 loader 的主要工作階段,loader 可以對模塊進行各種處理,例如轉換代碼、添加額外的功能等。 -
生成階段:在這個階段,webpack 會根據處理后的模塊生成最終的輸出文件。輸出文件的格式和路徑可以通過配置文件進行配置。
在這些階段中,loader 主要在編譯階段發揮作用。loader 可以通過導出一個函數來定義自己的處理邏輯,這個函數接收一個參數,即待處理的模塊的源代碼,然后返回處理后的代碼。
常用loader
以下是一些常用的webpack loader:
Loader | 描述 |
---|---|
babel-loader | 用于將ES6+的JavaScript代碼轉換為ES5代碼,以便在舊版本瀏覽器中運行。 |
css-loader | 用于解析CSS文件,并處理其中的import和url()等語法。 |
style-loader | 將解析后的CSS代碼以<style> 標簽的形式插入到HTML文件中。 |
file-loader | 用于處理文件資源(如圖片、字體等),并將其復制到輸出目錄中。 |
url-loader | 類似于file-loader,但可以根據文件大小將文件轉換為DataURL,以減少HTTP請求。 |
sass-loader | 用于將Sass/SCSS代碼轉換為CSS代碼。 |
less-loader | 用于將Less代碼轉換為CSS代碼。 |
postcss-loader | 用于對CSS代碼進行后處理,如自動添加瀏覽器前綴等。 |
vue-loader | 用于解析和轉換Vue單文件組件。 |
ts-loader | 用于將TypeScript代碼轉換為JavaScript代碼。 |
自定義loader
// 核心代碼:function clearConsoleLoader(source) {// 使用正則表達式匹配并替換console語句const modifiedSource = source.replace(/console\.[a-z]+\(.+\);?/g, '');return modifiedSource;
}module.exports = clearConsoleLoader;//使用
module.exports = {// ...module: {rules: [{test: /\.js$/,exclude: /node_modules/,use: ['babel-loader','./path/to/clearConsoleLoader.js']}]}
};
webpack中的plugins(插件)
工作原理
webpack 插件是用來擴展 webpack 功能的工具,它可以在 webpack 構建過程中的不同階段執行一些額外的操作。
插件的工作原理是通過在 webpack 的構建過程中的不同生命周期中注冊一些鉤子函數,然后在對應的階段執行這些鉤子函數中的邏輯。
webpack 的構建過程中有以下幾個生命周期:
-
初始化階段:在這個階段,webpack 會初始化配置參數,加載插件,并準備開始編譯。
-
編譯階段:在這個階段,webpack 會從入口文件開始遞歸解析所有的依賴模塊,并將模塊轉換成 AST(抽象語法樹),然后根據配置文件中的規則進行轉換和處理。
-
完成編譯階段:在這個階段,webpack 已經完成了所有的模塊的轉換和處理,并且生成了最終的輸出文件。
-
輸出階段:在這個階段,webpack 會將生成的輸出文件寫入到磁盤上。
插件可以在這些生命周期中的任意階段注冊對應的鉤子函數,并在鉤子函數中執行一些額外的操作。
自定義plugins
class MyPlugin {apply(compiler) {// 注冊初始化階段的鉤子函數compiler.hooks.initialize.tap('MyPlugin', () => {console.log('MyPlugin initialized');});// 注冊編譯階段的鉤子函數compiler.hooks.compile.tap('MyPlugin', () => {console.log('MyPlugin compiling');});}
}module.exports = MyPlugin;// 使用const MyPlugin = require('./my-plugin');module.exports = {// ...plugins: [new MyPlugin(),],
};
打包過程
-
讀取配置文件:Webpack會首先讀取配置文件,根據配置文件中的入口、出口等信息進行打包。
-
解析模塊依賴:Webpack會從指定的入口文件開始遞歸解析所有的模塊依賴,直到找到所有的模塊。
-
加載器處理:對于不同類型的模塊,Webpack會使用相應的加載器對其進行處理。例如,對于JavaScript模塊,Webpack會使用Babel加載器將ES6語法轉換為ES5語法;對于CSS模塊,Webpack會使用CSS加載器將CSS代碼打包進JS文件中。
-
插件處理:在模塊加載完成之后,Webpack會執行一系列插件,用于完成一些額外的任務,例如生成HTML文件、提取CSS文件等。
-
編譯打包:Webpack將經過處理的模塊和插件生成最終的打包文件。通常情況下,Webpack會生成一個或多個JavaScript文件,同時也可以生成其他類型的文件,例如CSS、圖片等。
-
輸出打包文件:Webpack將生成的打包文件輸出到指定的目錄中。通常情況下,Webpack會將打包文件輸出到dist目錄下。
加速webpack打包速度和減小打包體積的優化
以下是一些加速Webpack打包和減小打包體積的技巧:
-
優化Webpack配置:使用Tree shaking來減小打包體積,設置Webpack的mode為production以啟用UglifyJsPlugin等插件進行代碼壓縮和優化。
-
使用Webpack的code splitting功能:將代碼分割成較小的塊,以便在需要時動態加載。
-
壓縮圖片和字體文件:使用ImageMinWebpackPlugin和FontminWebpackPlugin等插件來壓縮圖片和字體文件,減小打包體積。
-
緩存:啟用Webpack的緩存功能,以便在修改代碼時只重新打包修改的文件,而不是重新打包所有文件。
-
使用DLLPlugin和DllReferencePlugin:將一些第三方庫打包成單獨的文件,以便在每次打包應用程序時不必重新打包這些庫。
-
使用HappyPack插件:使用多線程來加速Webpack打包,以便同時處理多個任務。
-
使用externals選項:將一些不需要打包的庫從打包中排除,以便減小打包體積。
-
使用Webpack-bundle-analyzer插件:分析打包后的文件,以便找出冗余的代碼和依賴關系,進行優化。
這些技巧可以幫助優化Webpack的打包速度和打包體積。
優化方法 | 具體實現 |
---|---|
優化Webpack配置 | 使用Tree shaking,設置mode: 'production' 啟用UglifyJsPlugin等優化插件 |
使用code splitting | 動態加載分割后的代碼塊(如import() 或SplitChunksPlugin ) |
壓縮圖片和字體文件 | 通過ImageMinWebpackPlugin 和FontminWebpackPlugin 插件壓縮資源文件 |
啟用緩存 | 配置cache: true 或使用HardSourceWebpackPlugin 加速重復構建 |
使用DLLPlugin | 預編譯第三方庫為獨立文件(DLL),通過DllReferencePlugin 引用 |
多線程打包(HappyPack) | 使用HappyPack 或thread-loader 并行處理任務 |
排除外部庫(externals) | 配置externals 將庫(如jQuery)排除,通過CDN引入 |
分析打包體積 | 使用webpack-bundle-analyzer 可視化分析依賴,優化冗余代碼 |
webpack配置
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 自動引入資源插件 npm install --save-dev html-webpack-plugin
const MiniCssExtracPlugin = require("mini-css-extrac-plugin"); // css抽離
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //css壓縮 npm install css-minimizer-webpack-plugin --save-dev
const TerserPlugin = require("terser-webpack-plugin"); // js壓縮 npm install --save-dev terser-webpack-plugin
//加載toml、yarm、json5數據資源 npm install toml yarm json5 -D
const toml = require("toml");
const yarm = require("yarm");
const json5 = require("json5");module.exports = (env) => {return {// 手動分離公共文件,通過配置成對象的方式實現多入口代碼分割// entry: {// index:{// import:"./src/index.js",// dependOn: "shared" // 抽離公共文件// },// shared: "lodash" // 公共的js文件// },// 多入口// entry: {// pageOne: './src/pageOne/index.js',// pageTwo: './src/pageTwo/index.js',// pageThree: './src/pageThree/index.js',// },// 單入口entry: {index: "./src/index.js",},output: {filename: "scripts/[name].[contenthash].js", // 將所有的js放入同一個文件夾,并且根據文件名自動命名path: path.resolve(__dirname, "./dist"),clean: true, // 清除上一次的垃圾文件assetModuleFilename: "images/[contenthash][ext]", // 在images目錄下,根據文件內容自動生成hash文件名publicPath: "https://*****.com/", // 公共路徑(cdn域名或者本地localhost)},mode: env.prodection ? "prodection" : "development", // 生產環境或者開發環境 package.json 啟動命令:npx webpack --env prodectiondevtool: "cheap-module-source-map", // 真實報錯文件指向,生產環境一般不開啟sourcemap// 插件(非必要的,缺少也不影響項目打包)plugins: [new HtmlWebpackPlugin({template: "./index.html", // 模板filename: "app.html",inject: "body", // script 存在的位置hash: true, // 解決緩存minify: {removeAttributeQuotes: true, // 壓縮,去掉引號},}),new MiniCssExtracPlugin({filename: "style/[contenthash].css",}),],devServer: {static: "./dist", // 監聽根目錄文件變化,自動刷新頁面插件 npm install --save-dev webpack-dev-server//反向代理proxy: {"/ajax": {target: "https:**********",ws: true,changeOrigin: true,},},},// 模塊(必要的,缺少影響項目打包)module: {rules: [//資源模塊類型我們稱之為Asset Modules Type,總共有四種,來代替loader,分別是:// asset/resource:發送一個單獨的文件并導出URL,替代file-loader// asset/inline:導出一個資源的data URI(base64),替代url-loader// asset/source:導出資源的源代碼,之前通過使用raw-loader實現// asset:介于asset/resource和asset/inline之間, 之前通過url-loader+limit屬性實現。{test: /\.(png|gif|jp?g|svg|webp|ico)$/, // 正則圖片文件type: "asset",generator: {filename: "images/[contenthash][ext]", // 優先級高于 assetModuleFilename},},{// 支持less// npm install style-loader css-loader less-loader less --save-dev// 抽離 npm install mini-css-extrac-plugin --save-dev webpack5環境下構建的插件test: /\.(le|c)ss$/, // .less and .cssuse: [MiniCssExtracPlugin.loader,/* "style-loader", */ "css-loader","less-loader"],},{test: /\.(woff|woff2|eot|ttf|oft)$/, // 正則字體文件type: "asset/resource",},//加載csv、xml數據資源 npm install csv-loader xml-loader -D{test: /\.(csv|tsv)$/,use: "csv-loader",},{test: /\.xml$/,use: "xml-loader",},//加載toml、yarm、json5數據資源{test: /\.toml$/,type: "json",parser: {parse: toml.parse,},},{test: /\.yarm$/,type: "json",parser: {parse: yarm.parse,},},{test: /\.json5$/,type: "json",parser: {parse: json5.parse,},},// loader工具 支持數組方式鏈式調用,數組靠后的元素先執行{// 壓縮圖片//圖片小于一定大小使用base64 否則使用file-loader產生真實圖片 npm install url-loader --save-devtest: /\.(png|gif|jp?g|svg|webp|ico)$/, // 正則use: [{loader: "url-loader",options: {limit: 5000, //小于限定使用base64name: "home/images/[name].[hash:8].[ext]",publicPath: `../../`,esModule: false,},},],},// 使用babel-loader npm install -D babel-loader @babel/core @babel/preset-env// regeneratorRuntime是webpack打包生成的全局輔助函數,由babel生成,用于兼容 async/await 的語法// npm install --save @babel/runtime// npm install --save-dev @babel/plugin-transform-runtime{test: /\.js$/,exclude: /node_modules/, // *業務代碼里面可能會引入node_modules外部js,這些js不需要babel-loader編譯,因此需要排除掉use: {loader: "babel-loader", // *引入babel-loaderoptions: {presets: ["@babel/preset-env"], // *引入預設plugins: [["@babel/plugin-transform-runtime", // *配置插件信息],],},},},],},optimization: {minimizer: [new CssMinimizerPlugin(),new TerserPlugin()], //代碼壓縮 mode改為 productionsplitChunks: {// 緩存cacheGroups: {vendor: {test: /[\\/]node_modules[\\/]/,name: "vendors",chunks: "all", // 自動重復代碼抽離},},},},};
};
webpack配置拆分
webpack.config.common.js文件公共環境配置
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 自動引入資源插件 npm install --save-dev html-webpack-plugin
const MiniCssExtracPlugin = require("mini-css-extrac-plugin"); // css抽離
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //css壓縮 npm install css-minimizer-webpack-plugin --save-dev
const TerserPlugin = require("terser-webpack-plugin"); // js壓縮 npm install --save-dev terser-webpack-plugin
//加載toml、yarm、json5數據資源 npm install toml yarm json5 -D
const toml = require("toml");
const yarm = require("yarm");
const json5 = require("json5");module.exports = {entry: {index: "./src/index.js",},output: {path: path.resolve(__dirname, "./dist"),clean: true, // 清除上一次的垃圾文件assetModuleFilename: "images/[contenthash][ext]", // 在images目錄下,根據文件內容自動生成hash文件名},// 插件(非必要的,缺少也不影響項目打包)plugins: [new HtmlWebpackPlugin({template: "./index.html", // 模板filename: "app.html",inject: "body", // script 存在的位置hash: true, // 解決緩存minify: {removeAttributeQuotes: true, // 壓縮,去掉引號},}),new MiniCssExtracPlugin({filename: "style/[contenthash].css",}),],// 模塊(必要的,缺少影響項目打包)module: {…},optimization: {splitChunks: {// 緩存cacheGroups: {vendor: {test: /[\\/]node_modules[\\/]/,name: "vendors",chunks: "all", // 自動重復代碼抽離},},},},
};
webpack.config.dev.js文件開發環境配置 npx webpack -c ./webpack.config.dev.js
module.exports = {output: {filename: "scripts/[name].js", // 將所有的js放入同一個文件夾,并且根據文件名自動命名},mode: "development", // 生產環境或者開發環境 package.json 啟動命令:npx webpack --env prodectiondevtool: "cheap-module-source-map", // 真實報錯文件指向,生產devServer: {static: "./dist", // 監聽根目錄文件變化,自動刷新頁面插件 npm install --save-dev webpack-dev-server//反向代理proxy: {"/ajax": {target: "https:**********",ws: true,changeOrigin: true,},},},
};
webpack.config.prod.js文件生產環境配置 npx webpack -c ./webpack.config.prod.js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //css壓縮 npm install css-minimizer-webpack-plugin --save-dev
const TerserPlugin = require("terser-webpack-plugin"); // js壓縮 npm install --save-dev terser-webpack-pluginmodule.exports = {output: {filename: "scripts/[name].[contenthash].js", // 將所有的js放入同一個文件夾,并且根據文件名自動命名publicPath: "https://*****.com/", // 公共路徑(cdn域名或者本地localhost)},mode: "prodection", // 生產環境或者開發環境 package.json 啟動命令:npx webpack --env prodectionoptimization: {minimizer: [new CssMinimizerPlugin(), new TerserPlugin()], //代碼壓縮 mode改為 production},performance: {hints: false, // 關閉性能提示},
};
webpack.config.js 運行:webpack -c ./webpack.config.js --env development
const { merge } = require ('webpack-merge') // npm install webpack-merge -D
const commonConfig = require ('./webpack.config.common')
const productionConfig = require ('./webpack.config.prod')
const developmentConfig = require ('./webpack.config.dev')module.exports = (env) => {switch (true) {case env.development :return merge(commonConfig,developmentConfig)case env.production :return merge(commonConfig,productionConfig)defult:return new Error()}
}
封裝webpack自定義插件
-
創建一個 JavaScript 文件,并導出一個函數。這個函數將作為你的插件的構造函數。
-
在函數中定義一個 apply 方法,該方法接收一個 compiler 參數。這個 compiler 對象是 Webpack 的核心,它包含了 Webpack 的所有配置和工作流程。
function MyPlugin() {} // 構造函數// 核心 apply 方法
MyPlugin.prototype.apply = function(compiler) {// 插件邏輯實現
};
- 在 apply 方法中,可以通過 compiler.hooks 對象訪問 Webpack 的生命周期鉤子。通過這些鉤子,你可以在 Webpack 運行的不同階段執行自定義代碼。
compiler.hooks.done.tap('PluginName', (stats) => {console.log('編譯完成!');
});
-
實現你的插件邏輯,例如在特定的 Webpack 鉤子上注冊回調函數,向編譯器添加自定義插件等。
-
將你的插件打包成一個 npm 模塊,并在項目中引入和使用它。
Webpack 插件本質上是一個具有 apply 方法的類或函數對象。通過掛接到 Webpack 編譯器的生命周期鉤子,我們可以實現各種構建時的定制化功能,如資源處理、日志輸出、文件生成等。
const MyPlugin = function() {};MyPlugin.prototype.apply = function(compiler) {compiler.hooks.done.tap('MyPlugin', (stats) => {console.log('Webpack 構建已完成!');if (stats.hasErrors()) {console.error('構建過程中出現錯誤');}});
};
module.exports = MyPlugin;上面示例我們定義了一個 MyPlugin 插件,它在 Webpack 編譯完成后輸出一條信息。
在 apply 方法中,我們使用 compiler.hooks.done 鉤子注冊了一個回調函數,在編譯完成后輸出一條消息。要使用這個插件,你需要將它打包成一個 npm 模塊,并在 Webpack 配置文件中引入和使用它:const MyPlugin = require('my-plugin');module.exports = {// ...plugins: [new MyPlugin()]
};
在這個示例中,我們在 Webpack 配置文件中引入了 MyPlugin 插件,并通過 plugins 選項將其添加到插件數組中。這樣一來,當 Webpack 進行編譯時,MyPlugin 就會被自動啟用并執行相應的邏輯。