JAVASCRIPT前端框架WEBPACK5從入門到精通
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
前言 webpack是什么? 摘自官網(wǎng)的一段話:webpack 是一個(gè)用于現(xiàn)代 JavaScript 應(yīng)用程序的 靜態(tài)模塊打包工具。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)在內(nèi)部從一個(gè)或多個(gè)入口點(diǎn)構(gòu)建一個(gè) 依賴圖(dependency graph),然后將你項(xiàng)目中所需的每一個(gè)模塊組合成一個(gè)或多個(gè) bundles,它們均為靜態(tài)資源,用于展示你的內(nèi)容。 官網(wǎng)鏈接:https://webpack.docschina.org/concepts/? 為什么需要打包程序 在日常的開(kāi)發(fā)中,我們會(huì)使用框架(React、Vue),ES6 模塊化語(yǔ)法,Less/Sass 等 css 預(yù)處理器等語(yǔ)法進(jìn)行開(kāi)發(fā)。這樣的代碼要想在瀏覽器運(yùn)行必須經(jīng)過(guò)編譯成瀏覽器能識(shí)別的 JS、Css 等語(yǔ)法,才能運(yùn)行,所以我們需要打包工具幫我們做完這些事。除此之外,打包工具還能壓縮代碼、做兼容性處理、提升代碼性能等。 正文 概念 在這里我將webpack為了幾個(gè)要點(diǎn),也是webpack核心的幾個(gè)點(diǎn) entry(入口) 需要打包的入口文件,可以為一個(gè)入口,也可以為多個(gè)入口(有幾個(gè)入口就有幾個(gè)輸出),Webpack 本身功能比較少,只能處理 js 資源,一旦遇到 css 等其他資源就會(huì)報(bào)錯(cuò)output(輸出) 可以通過(guò)配置 output 選項(xiàng),告知 webpack 如何向硬盤寫入編譯文件。注意,即使可以存在多個(gè) entry 起點(diǎn),但只能指定一個(gè) output 配置。loaders(模塊解析器) loader 用于對(duì)模塊的源代碼進(jìn)行轉(zhuǎn)換。loader 可以使你在 import 或 “l(fā)oad(加載)” 模塊時(shí)預(yù)處理文件。因此,loader 類似于其他構(gòu)建工具中“任務(wù)(task)”,并提供了處理前端構(gòu)建步驟的得力方式。loader 可以將文件從不同的語(yǔ)言(如 TypeScript)轉(zhuǎn)換為 JavaScript 或?qū)?nèi)聯(lián)圖像轉(zhuǎn)換為 data URL。loader 甚至允許你直接在 JavaScript 模塊中 import CSS 文件!Plugins(插件) 插件 是 webpack 的 支柱 功能。Webpack 自身也是構(gòu)建于你在 webpack 配置中用到的 相同的插件系統(tǒng) 之上!插件目的在于解決 loader 無(wú)法實(shí)現(xiàn)的其他事mode(模式) 指示 Webpack 使用相應(yīng)模式的配置。 development 開(kāi)發(fā)模式:會(huì)將 process.env.NODE_ENV 的值設(shè)為 development。啟用 NameChunksPlugin 和 NameModulesPlugin。特點(diǎn)是能讓代碼本地調(diào)試運(yùn)行的環(huán)境。 production 生產(chǎn)模式:會(huì)將 process.env.NODE_ENV 的值設(shè)為 production。啟用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。特點(diǎn)是能讓代碼優(yōu)化上線運(yùn)行的環(huán)境。 起步 新建文件,創(chuàng)建一個(gè)新的項(xiàng)目 下載我們的依賴 構(gòu)建項(xiàng)目目錄文件 webpack_document # 項(xiàng)目根目錄(所有指令必須在這個(gè)目錄運(yùn)行) └── src # 項(xiàng)目源碼目錄 ├── js # js文件目錄 │ ├── count.js │ └── sum.js ├── css # css文件目錄 └── index.js # 項(xiàng)目主文件 創(chuàng)建我們的配置文件webpack.config.js 運(yùn)行指令 觀察生成的dist的文件,就是我們打包后的文件了 Webpack 將來(lái)都通過(guò) webpack.config.js 文件進(jìn)行配置,來(lái)增強(qiáng) Webpack 的功能 開(kāi)發(fā)模式介紹 開(kāi)發(fā)模式顧名思義就是我們開(kāi)發(fā)代碼時(shí)使用的模式。 這個(gè)模式下我們主要做兩件事: 編譯代碼,使瀏覽器能識(shí)別運(yùn)行開(kāi)發(fā)時(shí)我們有樣式資源、字體圖標(biāo)、圖片資源、html 資源等,webpack 默認(rèn)都不能處理這些資源,所以我們要加載配置來(lái)編譯這些資源代碼質(zhì)量檢查,樹(shù)立代碼規(guī)范提前檢查代碼的一些隱患,讓代碼運(yùn)行時(shí)能更加健壯。提前檢查代碼規(guī)范和格式,統(tǒng)一團(tuán)隊(duì)編碼風(fēng)格,讓代碼更優(yōu)雅美觀。 處理樣式資源 由于webpack只能處理js、json文件,并不能處理css文件,所以我們需要借助相應(yīng)loader解析器來(lái)增強(qiáng)我們的功能,在webpack的官網(wǎng)中,為我們提供了常用的loader,如果不能滿足我們的日常需要,也可以到社區(qū)中去尋找想要的loader [webpack官網(wǎng)loader] 處理css資源 下載 說(shuō)明 css-loader:負(fù)責(zé)將 Css 文件編譯成 Webpack 能識(shí)別的模塊style-loader:會(huì)動(dòng)態(tài)創(chuàng)建一個(gè) Style 標(biāo)簽,里面放置 Webpack 中 Css 模塊內(nèi)容 此時(shí)樣式就會(huì)以 Style 標(biāo)簽的形式在頁(yè)面上生效 配置 使用 在src目錄下創(chuàng)建我們的css文件,并在index.js中進(jìn)行引入,然后進(jìn)行打包,觀察我們的dist文件里面的輸出結(jié)果 為了方便我們觀察效果,我們創(chuàng)建我們靜態(tài)頁(yè)面,并引入我們打包后dist文件下面的js文件,文件目錄結(jié)構(gòu)如下 頁(yè)面效果如下: 處理less資源 下載 說(shuō)明 less-loader:負(fù)責(zé)將 Less 文件編譯成 Css 文件 配置 使用 在src目錄下,創(chuàng)建我們的less文件夾,并生成index.less文件,寫入樣式,在index.js,引入我們的less文件,然后在pubilc的index.html文件中,創(chuàng)建對(duì)應(yīng)的box文件,執(zhí)行npx webpack,觀察打包后的結(jié)果 頁(yè)面效果如下: 處理sass/scss資源 下載 說(shuō)明 sass-loader:負(fù)責(zé)將 Sass 文件編譯成 css 文件sass:sass-loader 依賴 sass 進(jìn)行編譯 配置 使用 在src目錄下,創(chuàng)建我們的sass文件夾,并生成index.sass 和 index.scss 文件,寫入樣式,在index.js,引入我們的sass、scss文件,然后在pubilc的index.html文件中,創(chuàng)建對(duì)應(yīng)的box文件,執(zhí)行npx webpack,觀察打包后的結(jié)果 頁(yè)面效果如下: 處理 Styl 資源 說(shuō)明 stylus-loader:負(fù)責(zé)將 Styl 文件編譯成 Css 文件 配置 使用 在src目錄下,創(chuàng)建我們的styl文件夾,并生成index.styl 和 index.styl 文件,寫入樣式,在index.js,引入我們的styl文件,然后在pubilc的index.html文件中,創(chuàng)建對(duì)應(yīng)的box文件,執(zhí)行npx webpack,觀察打包后的結(jié)果 頁(yè)面效果如下: 資源模塊 以下摘自官網(wǎng)的一段話 資源模塊(asset module)是一種模塊類型,它允許使用資源文件(字體,圖標(biāo)等)而無(wú)需配置額外 loader。 在 webpack 5 之前,通常使用: raw-loader 將文件導(dǎo)入為字符串 url-loader 將文件作為 data URI 內(nèi)聯(lián)到 bundle 中 file-loader 將文件發(fā)送到輸出目錄 資源模塊類型(asset module type),通過(guò)添加 4 種新的模塊類型,來(lái)替換所有這些 loader: asset/resource 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL。之前通過(guò)使用 file-loader 實(shí)現(xiàn)。 asset/inline 導(dǎo)出一個(gè)資源的 data URI。之前通過(guò)使用 url-loader 實(shí)現(xiàn)。 asset/source 導(dǎo)出資源的源代碼。之前通過(guò)使用 raw-loader 實(shí)現(xiàn)。 asset 在導(dǎo)出一個(gè) data URI 和發(fā)送一個(gè)單獨(dú)的文件之間自動(dòng)選擇。之前通過(guò)使用 url-loader,并且配置資源體積限制實(shí)現(xiàn)。 當(dāng)在 webpack 5 中使用舊的 assets loader(如 file-loader/url-loader/raw-loader 等)和 asset 模塊時(shí),你可能想停止當(dāng)前 asset 模塊的處理,并再次啟動(dòng)處理,這可能會(huì)導(dǎo)致 asset 重復(fù),你可以通過(guò)將 asset 模塊的類型設(shè)置為 ‘javascript/auto’ 來(lái)解決。 處理圖片資源 配置 使用 在src目錄下,創(chuàng)建我們的images文件夾,添加圖片文件,筆者這里添加了jpe,png,gif三種格式的圖片,分別在不同的樣式中進(jìn)行引入,執(zhí)行npx webpack,觀察打包后的結(jié)果 此時(shí)如果查看 dist 目錄的話,會(huì)發(fā)現(xiàn)多了三張圖片資源 因?yàn)?Webpack 會(huì)將所有打包好的資源輸出到 dist 目錄下 為什么樣式資源沒(méi)有呢? 因?yàn)榻?jīng)過(guò) style-loader 的處理,樣式資源打包到 main.js 里面去了,所以沒(méi)有額外輸出出來(lái) 頁(yè)面效果 圖片資源處理優(yōu)化 將小于某個(gè)大小的圖片轉(zhuǎn)化成 data URI 形式(Base64 格式) 優(yōu)點(diǎn):減少請(qǐng)求數(shù)量 缺點(diǎn):體積變得更大,但是10kb以下的圖片,轉(zhuǎn)換為base64格式的情形下,只有增加1-2kb(所以我們出來(lái)10kb以下的) 配置 修改輸出資源的名稱和路徑 我們發(fā)現(xiàn)打包完成后,圖片直接輸出到dist的根目錄下了,沒(méi)有規(guī)范起來(lái),我們希望輸出到dist的static/images目錄下,同時(shí)js文件輸出到dist的static/js目錄下,這樣就比較規(guī)范了 配置 使用 通過(guò)以上配置后,dist文件輸出的文件被規(guī)范起來(lái)了,這個(gè)時(shí)候需要修改一下我們index.html的引入路徑,打開(kāi)頁(yè)面也是同樣的效果 處理字體圖標(biāo)資源 當(dāng)我們?cè)陧?xiàng)目中使用字體圖標(biāo)的時(shí)候,我們也希望打包的時(shí)候,將這一部分內(nèi)容進(jìn)行打包輸出 配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require("path"); module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: "./src/index.js", }, // 輸出 output: { // 需要一個(gè)絕對(duì)路徑 path: path.resolve(__dirname, "dist"), clean: true, filename: "static/js/index.js", // 將 js 文件輸出到 static/js 目錄中 }, // 解析器 module: { rules: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: ["style-loader", "css-loader"], }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: ["style-loader", "css-loader", "less-loader"], }, { test: /\.s[ac]ss$/, use: ["style-loader", "css-loader", "sass-loader"], }, { test: /\.styl$/, use: ["style-loader", "css-loader", "stylus-loader"], }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的圖片會(huì)被base64處理 }, }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, ], }, plugins: [], mode: "production", }; 使用 在src目錄下,創(chuàng)建我們的fonts文件夾,在阿里巴巴適量庫(kù)中下載字體圖標(biāo)(筆者這里為了演示,使用的是font-class的形式),然后引入到fonts文件夾中,在index.js,然后在pubilc的index.html文件中,創(chuàng)建對(duì)應(yīng)的span標(biāo)簽,添加class類名,執(zhí)行npx webpack,觀察打包后的結(jié)果 頁(yè)面效果 處理其他資源 開(kāi)發(fā)中可能還存在一些其他資源,如音視頻等,我們也一起處理了 就是在處理字體圖標(biāo)資源基礎(chǔ)上增加其他文件類型,統(tǒng)一處理即可 配置 js資源處理 這里有同學(xué)可能會(huì)問(wèn),js 資源 Webpack 不能已經(jīng)處理了嗎,為什么我們還要處理呢? 原因是 Webpack 對(duì) js 處理是有限的,只能編譯 js 中 ES 模塊化語(yǔ)法,不能編譯其他語(yǔ)法,導(dǎo)致 js 不能在 IE 等瀏覽器運(yùn)行,所以我們希望做一些兼容性處理。 其次開(kāi)發(fā)中,團(tuán)隊(duì)對(duì)代碼格式是有嚴(yán)格要求的,我們不能由肉眼去檢測(cè)代碼格式,需要使用專業(yè)的工具來(lái)檢測(cè)。 針對(duì) js 兼容性處理,我們使用 Babel 來(lái)完成 針對(duì)代碼格式,我們使用 Eslint 來(lái)完成 我們先完成 Eslint,檢測(cè)代碼格式無(wú)誤后,在由 Babel 做代碼兼容性處理 ESLint 介紹 簡(jiǎn)介:可組裝的 JavaScript 和 JSX 檢查工具。 這句話意思就是:它是用來(lái)檢測(cè) js 和 jsx 語(yǔ)法的工具,可以配置各項(xiàng)功能 我們使用 Eslint,關(guān)鍵是寫 Eslint 配置文件,里面寫上各種 rules 規(guī)則,將來(lái)運(yùn)行 Eslint 時(shí)就會(huì)以寫的規(guī)則對(duì)代碼進(jìn)行檢查 配置文件 配置文件由很多種寫法: .eslintrc.*:新建文件,位于項(xiàng)目根目錄 .eslintrc .eslintrc.js .eslintrc.json 區(qū)別在于配置格式不一樣 package.json 中 eslintConfig:不需要?jiǎng)?chuàng)建文件,在原有文件基礎(chǔ)上寫 ESLint 會(huì)查找和自動(dòng)讀取它們,所以以上配置文件只需要存在一個(gè)即可 具體配置 我們以.eslintrc.js為例子進(jìn)行配置 具體eslint的配置參考官網(wǎng)和規(guī)則文檔,這里給出相關(guān)鏈接 ESlint規(guī)則 ESlint官網(wǎng) 下載 npm i eslint-webpack-plugin eslint -D 在webpack中使用 vsocde安裝eslint插件 安裝eslint插件后,配置需要忽略檢查的文件 // .eslintignore # 忽略dist目錄下所有文件 dist Babel 介紹 JavaScript 編譯器。 主要用于將 ES6 語(yǔ)法編寫的代碼轉(zhuǎn)換為向后兼容的 JavaScript 語(yǔ)法,以便能夠運(yùn)行在當(dāng)前和舊版本的瀏覽器或其他環(huán)境中 配置文件 配置文件由很多種寫法: babel.config.*:新建文件,位于項(xiàng)目根目錄 babel.config.js babel.config.json .babelrc.*:新建文件,位于項(xiàng)目根目錄 .babelrc .babelrc.js .babelrc.json package.json 中 babel:不需要?jiǎng)?chuàng)建文件,在原有文件基礎(chǔ)上寫 Babel 會(huì)查找和自動(dòng)讀取它們,所以以上配置文件只需要存在一個(gè)即可 具體配置 我們以babel.config.js為例子 // babel.config.js module.exports = { // 預(yù)設(shè) presets: [], }; presets 預(yù)設(shè) 簡(jiǎn)單理解:就是一組 Babel 插件, 擴(kuò)展 Babel 功能 @babel/preset-env: 一個(gè)智能預(yù)設(shè),允許您使用最新的 JavaScript。 @babel/preset-react:一個(gè)用來(lái)編譯 React jsx 語(yǔ)法的預(yù)設(shè) @babel/preset-typescript:一個(gè)用來(lái)編譯 TypeScript 語(yǔ)法的預(yù)設(shè) 下載 npm i babel-loader @babel/core @babel/preset-env -D 配置 HTML資源處理 在實(shí)際的開(kāi)發(fā)工作中,我們希望使用一個(gè)html模塊,然后將我們打包后的js文件自動(dòng)引入到html模版中,這樣我們開(kāi)發(fā)的時(shí)候就不用手動(dòng)引入或者修改打包后的文件了 插件 下載 npm i html-webpack-plugin -D 配置 css資源處理 Css 文件目前被打包到 js 文件中,當(dāng) js 文件加載時(shí),會(huì)創(chuàng)建一個(gè) style 標(biāo)簽來(lái)生成樣式 這樣對(duì)于網(wǎng)站來(lái)說(shuō),會(huì)出現(xiàn)閃屏現(xiàn)象,用戶體驗(yàn)不好 我們應(yīng)該是單獨(dú)的 Css 文件,通過(guò) link 標(biāo)簽加載性能才好 提取css為單獨(dú)文件 下載插件 npm i mini-css-extract-plugin -D 配置 css兼容性處理 下載 npm i postcss-loader postcss postcss-preset-env -D 配置 配置需要兼容的瀏覽器列表 // package.json "browserslist": [ "last 2 version", "> 1%", "not dead" ] } css壓縮 下載 npm i css-minimizer-webpack-plugin -D 配置 合并樣式處理配置 開(kāi)發(fā)服務(wù)器&自動(dòng)化 在我們的日常開(kāi)發(fā)中我們希望我改了代碼后,會(huì)自動(dòng)更新我們更改后的內(nèi)容,而不用每次打包后才能看到效果,所以webpack提供了一個(gè)devServer的配置 下載 npm i webpack-dev-server -D 配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { // 需要一個(gè)絕對(duì)路徑 path: path.resolve(__dirname, 'dist'), clean: true, filename: "static/js/index.js", // 將 js 文件輸出到 static/js 目錄中 }, // 解析器 module: { rules: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: ["style-loader", "css-loader"], }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: ["style-loader", "css-loader", "less-loader"], }, { test: /\.s[ac]ss$/, use: ["style-loader", "css-loader", "sass-loader"], }, { test: /\.styl$/, use: ["style-loader", "css-loader", "stylus-loader"], }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, { test: /\.js$/, exclude: /node_modules/, // 排除node_modules代碼不編譯 loader: "babel-loader", }, ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "src"), }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "public/index.html"), }) ], // 開(kāi)發(fā)服務(wù)器 devServer: { host: "localhost", // 啟動(dòng)服務(wù)器域名 port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) open: true, // 是否自動(dòng)打開(kāi)瀏覽器 }, mode: 'production' } 運(yùn)行 npx webpack serve 并且當(dāng)你使用開(kāi)發(fā)服務(wù)器時(shí),所有代碼都會(huì)在內(nèi)存中編譯打包,并不會(huì)輸出到 dist 目錄下。 開(kāi)發(fā)時(shí)我們只關(guān)心代碼能運(yùn)行,有效果即可,至于代碼被編譯成什么樣子,我們并不需要知道。 開(kāi)發(fā)模式與生產(chǎn)模式 在我們?nèi)粘i_(kāi)發(fā)中,我們總是希望我們開(kāi)發(fā)完成后,今天一系列打包優(yōu)化,性能提升到最好,所以webpack這里也給我們提供了使用不同的配置文件進(jìn)行配置 開(kāi)發(fā)模式 適用于dev環(huán)境,配合devServe配置,進(jìn)行開(kāi)發(fā),并自動(dòng)實(shí)現(xiàn)代碼更新,所有代碼都會(huì)在內(nèi)存中編譯打包,并不會(huì)輸出到 dist 目錄下。 生產(chǎn)模式 生產(chǎn)模式是開(kāi)發(fā)完成代碼后,我們需要得到代碼將來(lái)部署上線。 這個(gè)模式下我們主要對(duì)代碼進(jìn)行優(yōu)化,讓其運(yùn)行性能更好。 優(yōu)化主要從兩個(gè)角度出發(fā): 優(yōu)化代碼運(yùn)行性能優(yōu)化代碼打包速度 使用 我們分別準(zhǔn)備兩個(gè)配置文件來(lái)放不同的配置,分別對(duì)應(yīng)開(kāi)發(fā)模式和生產(chǎn)模式 目錄配置 ├── webpack-document (項(xiàng)目根目錄) ├── config (Webpack配置文件目錄) │ ├── webpack.dev.js(開(kāi)發(fā)模式配置文件) │ └── webpack.prod.js(生產(chǎn)模式配置文件) ├── node_modules (下載包存放目錄) ├── src (項(xiàng)目源碼目錄,除了html其他都在src里面) │ └── 略 ├── public (項(xiàng)目html文件) │ └── index.html ├── .eslintrc.js(Eslint配置文件) ├── babel.config.js(Babel配置文件) └── package.json (包的依賴管理配置文件) 生產(chǎn)不同的webpack配置,并做里面的絕對(duì)路徑進(jìn)行相應(yīng)的修改 webpack.dev.js配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄 filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中 // clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果 }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, // 解析器 module: { rules: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, { test: /\.js$/, exclude: /node_modules/, // 排除node_modules代碼不編譯 loader: "babel-loader", }, ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/index.css", }), new CssMinimizerPlugin() ], // 開(kāi)發(fā)服務(wù)器 devServer: { host: "localhost", // 啟動(dòng)服務(wù)器域名 port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) open: true, // 是否自動(dòng)打開(kāi)瀏覽器 }, mode: 'development' } webpack.prod.js配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { // 需要一個(gè)絕對(duì)路徑 path: path.resolve(__dirname, '../dist'), clean: true, filename: "static/js/index.js", // 將 js 文件輸出到 static/js 目錄中 }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, // 解析器 module: { rules: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, { test: /\.js$/, exclude: /node_modules/, // 排除node_modules代碼不編譯 loader: "babel-loader", }, ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/index.css", }), new CssMinimizerPlugin() ], // 開(kāi)發(fā)服務(wù)器 // devServer: { // host: "localhost", // 啟動(dòng)服務(wù)器域名 // port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) // open: true, // 是否自動(dòng)打開(kāi)瀏覽器 // }, mode: 'production' } package.json配置 { "name": "webpack_document", "version": "1.0.0", "description": "", "main": "index.js", // 為了方便運(yùn)行不同模式的指令,我們將指令定義在 package.json 中 scripts 里面 "scripts": { "start": "npx webpack serve --config ./config/webpack.dev.js", "dev": "npx webpack serve --config ./config/webpack.dev.js", "build": "npx webpack --config ./config/webpack.prod.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.20.12", "@babel/preset-env": "^7.20.2", "babel-loader": "^9.1.2", "css-loader": "^6.7.3", "eslint": "^8.33.0", "eslint-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^5.5.0", "less-loader": "^11.1.0", "sass": "^1.58.0", "sass-loader": "^13.2.0", "style-loader": "^3.3.1", "stylus-loader": "^7.1.0", "webpack": "^5.75.0", "webpack-cli": "^5.0.1", "webpack-dev-server": "^4.11.1" } } 開(kāi)發(fā)模式:npm start 或 npm run dev 生產(chǎn)模式:npm run build 基礎(chǔ)總結(jié) 如果你學(xué)習(xí)到這里,恭喜你現(xiàn)在已經(jīng)是一個(gè)webpack小能手了 兩種開(kāi)發(fā)模式 開(kāi)發(fā)模式:代碼能編譯自動(dòng)化運(yùn)行 生產(chǎn)模式:代碼編譯優(yōu)化輸出 Webpack 基本功能 開(kāi)發(fā)模式:可以編譯 ES Module 語(yǔ)法 生產(chǎn)模式:可以編譯 ES Module 語(yǔ)法,壓縮 js 代碼 Webpack 配置文件 5 個(gè)核心概念 entry output loader plugins mode devServer 配置 Webpack 腳本指令用法 webpack 直接打包輸出 webpack serve 啟動(dòng)開(kāi)發(fā)服務(wù)器,內(nèi)存編譯打包沒(méi)有輸出 高級(jí) 提升開(kāi)發(fā)體驗(yàn)之sourceMap 在開(kāi)發(fā)過(guò)程中,我們運(yùn)行的代碼是經(jīng)過(guò)webpack編譯后的代碼,如果代碼出錯(cuò)了,我們想看到代碼的映射為我們自己的代碼,這個(gè)時(shí)候就需要sourceMap 了 介紹 SourceMap(源代碼映射)是一個(gè)用來(lái)生成源代碼與構(gòu)建后代碼一一映射的文件的方案。 它會(huì)生成一個(gè) xxx.map 文件,里面包含源代碼和構(gòu)建后代碼每一行、每一列的映射關(guān)系。當(dāng)構(gòu)建后代碼出錯(cuò)了,會(huì)通過(guò) xxx.map 文件,從構(gòu)建后代碼出錯(cuò)位置找到映射后源代碼出錯(cuò)位置,從而讓瀏覽器提示源代碼文件出錯(cuò)位置,幫助我們更快的找到錯(cuò)誤根源。 使用 通過(guò)查看Webpack DevTool 文檔文檔可知,SourceMap 的值有很多種情況. 但實(shí)際開(kāi)發(fā)時(shí)我們只需要關(guān)注兩種情況即可: 開(kāi)發(fā)模式:cheap-module-source-map 優(yōu)點(diǎn):打包編譯速度快,只包含行映射 缺點(diǎn):沒(méi)有列映射 module.exports = { // 其他省略 mode: "development", devtool: "cheap-module-source-map", }; 生產(chǎn)模式:source-map 優(yōu)點(diǎn):包含行/列映射 缺點(diǎn):打包編譯速度更慢 module.exports = { // 其他省略 mode: "production", devtool: "source-map", }; 提升打包構(gòu)建速度 介紹 開(kāi)發(fā)時(shí)我們修改了其中一個(gè)模塊代碼,Webpack 默認(rèn)會(huì)將所有模塊全部重新打包編譯,速度很慢。 所以我們需要做到修改某個(gè)模塊代碼,就只有這個(gè)模塊代碼需要重新打包編譯,其他模塊不變,這樣打包速度就能很快。 HotModuleReplacement(HMR/熱模塊替換):在程序運(yùn)行中,替換、添加或刪除模塊,而無(wú)需重新加載整個(gè)頁(yè)面。 使用 module.exports = { // 其他省略 devServer: { host: "localhost", // 啟動(dòng)服務(wù)器域名 port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) open: true, // 是否自動(dòng)打開(kāi)瀏覽器 hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了) }, }; 此時(shí) css 樣式經(jīng)過(guò) style-loader 處理,已經(jīng)具備 HMR 功能了。 但是 js 還不行。 JS 配置需要手動(dòng)進(jìn)行屬性需要進(jìn)行熱模塊使用的功能 // 判斷是否支持HMR功能 if (module.hot) { module.hot.accept("./js/sum"); } 這樣如果我們自己搭建腳手架,每個(gè)引入的文件都需要進(jìn)行這樣更新一次,這里我們推薦使用社區(qū)的loader進(jìn)行解決,比如:vue-loader, react-hot-loader。 使用oneof對(duì)loader進(jìn)行處理 介紹 打包時(shí)每個(gè)文件都會(huì)經(jīng)過(guò)所有 loader 處理,雖然因?yàn)?test 正則原因?qū)嶋H沒(méi)有處理上,但是都要過(guò)一遍。比較慢。 規(guī)則數(shù)組,當(dāng)規(guī)則匹配時(shí),只使用第一個(gè)匹配規(guī)則。 使用 我們的生產(chǎn)模式和開(kāi)發(fā)模式都需要配置,這里筆者展示開(kāi)發(fā)模式的配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄 filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中 // clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果 }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "cheap-module-source-map", // 解析器 module: { rules: [ { // 每個(gè)文件只能被其中的一個(gè)loader處理 oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, { test: /\.js$/, exclude: /node_modules/, // 排除node_modules代碼不編譯 loader: "babel-loader", }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/index.css", }), new CssMinimizerPlugin() ], // 開(kāi)發(fā)服務(wù)器 devServer: { host: "localhost", // 啟動(dòng)服務(wù)器域名 port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) open: true, // 是否自動(dòng)打開(kāi)瀏覽器 hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了) }, mode: 'development' } Include/Exclude 介紹 開(kāi)發(fā)時(shí)我們需要使用第三方的庫(kù)或插件,所有文件都下載到 node_modules 中了。而這些文件是不需要編譯可以直接使用的。 所以我們?cè)趯?duì) js 文件處理時(shí),要排除 node_modules 下面的文件。(只針對(duì)js文件進(jìn)行處理,只有babel和eslint在進(jìn)行工作) 注意:以下兩個(gè)配置只能寫一種 include 包含,只處理 xxx 文件 exclude 排除,除了 xxx 文件以外其他文件都處理 配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { // 需要一個(gè)絕對(duì)路徑 path: path.resolve(__dirname, '../dist'), clean: true, filename: "static/js/index.js", // 將 js 文件輸出到 static/js 目錄中 }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "source-map", // 解析器 module: { rules: [ { oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, { test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代碼不編譯 include: path.resolve(__dirname, "../src"), // 也可以用包含 loader: "babel-loader", }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默認(rèn)值 }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/index.css", }), new CssMinimizerPlugin() ], // 開(kāi)發(fā)服務(wù)器 // devServer: { // host: "localhost", // 啟動(dòng)服務(wù)器域名 // port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) // open: true, // 是否自動(dòng)打開(kāi)瀏覽器 // }, mode: 'production' } cache緩存 介紹 每次打包時(shí) js 文件都要經(jīng)過(guò) Eslint 檢查 和 Babel 編譯,速度比較慢。 我們可以緩存之前的 Eslint 檢查 和 Babel 編譯結(jié)果,這樣第二次打包時(shí)速度就會(huì)更快了。 對(duì) Eslint 檢查 和 Babel 編譯結(jié)果進(jìn)行緩存。 配置 我們的生產(chǎn)模式和開(kāi)發(fā)模式都需要配置,這里筆者展示開(kāi)發(fā)模式的配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄 filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中 // clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果 }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "cheap-module-source-map", // 解析器 module: { rules: [ { // 每個(gè)文件只能被其中的一個(gè)loader處理 oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, { test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代碼不編譯 include: path.resolve(__dirname, "../src"), // 也可以用包含 loader: "babel-loader", options: { cacheDirectory: true, // 開(kāi)啟babel編譯緩存 cacheCompression: false, // 緩存文件不要壓縮 }, }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默認(rèn)值 cache: true, // 開(kāi)啟緩存 // 緩存目錄 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" ), }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/index.css", }), new CssMinimizerPlugin() ], // 開(kāi)發(fā)服務(wù)器 devServer: { host: "localhost", // 啟動(dòng)服務(wù)器域名 port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) open: true, // 是否自動(dòng)打開(kāi)瀏覽器 hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了) }, mode: 'development' } Thead多進(jìn)程打包 介紹 當(dāng)項(xiàng)目越來(lái)越龐大時(shí),打包速度越來(lái)越慢,甚至于需要一個(gè)下午才能打包出來(lái)代碼。這個(gè)速度是比較慢的。 我們想要繼續(xù)提升打包速度,其實(shí)就是要提升 js 的打包速度,因?yàn)槠渌募急容^少。 而對(duì) js 文件處理主要就是 eslint 、babel、Terser 三個(gè)工具,所以我們要提升它們的運(yùn)行速度。 我們可以開(kāi)啟多進(jìn)程同時(shí)處理 js 文件,這樣速度就比之前的單進(jìn)程打包更快了。 多進(jìn)程打包:開(kāi)啟電腦的多個(gè)進(jìn)程同時(shí)干一件事,速度更快。 需要注意:請(qǐng)僅在特別耗時(shí)的操作中使用,因?yàn)槊總€(gè)進(jìn)程啟動(dòng)就有大約為 600ms 左右開(kāi)銷。 使用 下載 npm i thread-loader -D 獲取cpu核數(shù) // nodejs核心模塊,直接使用 const os = require("os"); // cpu核數(shù) const threads = os.cpus().length; 配置 我們的生產(chǎn)模式和開(kāi)發(fā)模式都需要配置,這里筆者展示開(kāi)發(fā)模式的配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const os = require("os"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); // cpu核數(shù) const threads = os.cpus().length; // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄 filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中 // clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果 }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "cheap-module-source-map", // 解析器 module: { rules: [ { // 每個(gè)文件只能被其中的一個(gè)loader處理 oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, { test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代碼不編譯 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ { loader: "thread-loader", // 開(kāi)啟多進(jìn)程 options: { workers: threads, // 數(shù)量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 開(kāi)啟babel編譯緩存 }, }, ], options: { cacheDirectory: true, // 開(kāi)啟babel編譯緩存 cacheCompression: false, // 緩存文件不要壓縮 }, }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默認(rèn)值 cache: true, // 開(kāi)啟緩存 // 緩存目錄 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" ), threads, // 開(kāi)啟多進(jìn)程 }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/index.css", }), // new CssMinimizerPlugin() ], // 推薦將壓縮操作放在這里 optimization: { minimize: true, minimizer: [ // css壓縮也可以寫到optimization.minimizer里面,效果一樣的 new CssMinimizerPlugin(), // 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了 new TerserPlugin({ parallel: threads // 開(kāi)啟多進(jìn)程 }) ], }, // 開(kāi)發(fā)服務(wù)器 devServer: { host: "localhost", // 啟動(dòng)服務(wù)器域名 port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) open: true, // 是否自動(dòng)打開(kāi)瀏覽器 hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了) }, mode: 'development' } 減少代碼體積 Tree Shaking 介紹 開(kāi)發(fā)時(shí)我們定義了一些工具函數(shù)庫(kù),或者引用第三方工具函數(shù)庫(kù)或組件庫(kù)。 如果沒(méi)有特殊處理的話我們打包時(shí)會(huì)引入整個(gè)庫(kù),但是實(shí)際上可能我們可能只用上極小部分的功能。 這樣將整個(gè)庫(kù)都打包進(jìn)來(lái),體積就太大了。 Tree Shaking 是一個(gè)術(shù)語(yǔ),通常用于描述移除 JavaScript 中的沒(méi)有使用上的代碼。 注意:它依賴 ES Module 使用 Webpack 已經(jīng)默認(rèn)開(kāi)啟了這個(gè)功能,無(wú)需其他配置。 減少babel文件生成的體積 介紹 Babel 為編譯的每個(gè)文件都插入了輔助代碼,使代碼體積過(guò)大! Babel 對(duì)一些公共方法使用了非常小的輔助代碼,比如 _extend。默認(rèn)情況下會(huì)被添加到每一個(gè)需要它的文件中。 你可以將這些輔助代碼作為一個(gè)獨(dú)立模塊,來(lái)避免重復(fù)引入。 @babel/plugin-transform-runtime: 禁用了 Babel 自動(dòng)對(duì)每個(gè)文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有輔助代碼從這里引用。 使用 下載 npm i @babel/plugin-transform-runtime -D 配置 我們的生產(chǎn)模式和開(kāi)發(fā)模式都需要配置,這里筆者展示開(kāi)發(fā)模式的配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const os = require("os"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); // cpu核數(shù) const threads = os.cpus().length; // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄 filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中 // clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果 }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "cheap-module-source-map", // 解析器 module: { rules: [ { // 每個(gè)文件只能被其中的一個(gè)loader處理 oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, { test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代碼不編譯 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ { loader: "thread-loader", // 開(kāi)啟多進(jìn)程 options: { workers: threads, // 數(shù)量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 開(kāi)啟babel編譯緩存 cacheCompression: false, // 緩存文件不要壓縮 plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積 }, }, ], }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默認(rèn)值 cache: true, // 開(kāi)啟緩存 // 緩存目錄 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" ), threads, // 開(kāi)啟多進(jìn)程 }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/index.css", }), // new CssMinimizerPlugin() ], // 推薦將壓縮操作放在這里 optimization: { minimize: true, minimizer: [ // css壓縮也可以寫到optimization.minimizer里面,效果一樣的 new CssMinimizerPlugin(), // 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了 new TerserPlugin({ parallel: threads // 開(kāi)啟多進(jìn)程 }) ], }, // 開(kāi)發(fā)服務(wù)器 devServer: { host: "localhost", // 啟動(dòng)服務(wù)器域名 port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) open: true, // 是否自動(dòng)打開(kāi)瀏覽器 hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了) }, mode: 'development' } Image Minimizer 圖片壓縮 介紹 開(kāi)發(fā)如果項(xiàng)目中引用了較多圖片,那么圖片體積會(huì)比較大,將來(lái)請(qǐng)求速度比較慢。 我們可以對(duì)圖片進(jìn)行壓縮,減少圖片體積。 注意:如果項(xiàng)目中圖片都是在線鏈接,那么就不需要了。本地項(xiàng)目靜態(tài)圖片才需要進(jìn)行壓縮。 image-minimizer-webpack-plugin: 用來(lái)壓縮圖片的插件 使用 下載 npm i image-minimizer-webpack-plugin imagemin -D 還有剩下包需要下載,有兩種模式:(如果npm下載不下來(lái),使用cnpm下載) 無(wú)損壓縮 npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D 有損壓縮 npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D 配置 以無(wú)損壓縮為例子,(生產(chǎn)模式與開(kāi)發(fā)模式都需要配置) // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const os = require("os"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // cpu核數(shù) const threads = os.cpus().length; // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄 filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中 // clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果 }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "cheap-module-source-map", // 解析器 module: { rules: [ { // 每個(gè)文件只能被其中的一個(gè)loader處理 oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) generator: { // 將圖片文件輸出到 static/imgs 目錄中 // 將圖片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件擴(kuò)展名 // [query]: 添加之前的query參數(shù) filename: "static/imgs/[hash:8][ext][query]", }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, { test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代碼不編譯 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ { loader: "thread-loader", // 開(kāi)啟多進(jìn)程 options: { workers: threads, // 數(shù)量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 開(kāi)啟babel編譯緩存 cacheCompression: false, // 緩存文件不要壓縮 plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積 }, }, ], }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默認(rèn)值 cache: true, // 開(kāi)啟緩存 // 緩存目錄 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" ), threads, // 開(kāi)啟多進(jìn)程 }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/index.css", }), // new CssMinimizerPlugin() ], // 推薦將壓縮操作放在這里 optimization: { minimize: true, minimizer: [ // css壓縮也可以寫到optimization.minimizer里面,效果一樣的 new CssMinimizerPlugin(), // 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了 new TerserPlugin({ parallel: threads // 開(kāi)啟多進(jìn)程 }), // 壓縮圖片 new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], [ "svgo", { plugins: [ "preset-default", "prefixIds", { name: "sortAttrs", params: { xmlnsOrder: "alphabetical", }, }, ], }, ], ], }, }, }), ], }, // 開(kāi)發(fā)服務(wù)器 devServer: { host: "localhost", // 啟動(dòng)服務(wù)器域名 port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) open: true, // 是否自動(dòng)打開(kāi)瀏覽器 hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了) }, mode: 'development' } 優(yōu)化代碼運(yùn)行性能 Code Split 介紹 打包代碼時(shí)會(huì)將所有 js 文件打包到一個(gè)文件中,體積太大了。我們?nèi)绻灰秩臼醉?yè),就應(yīng)該只加載首頁(yè)的 js 文件,其他文件不應(yīng)該加載。 所以我們需要將打包生成的文件進(jìn)行代碼分割,生成多個(gè) js 文件,渲染哪個(gè)頁(yè)面就只加載某個(gè) js 文件,這樣加載的資源就少,速度就更快。 代碼分割(Code Split)主要做了兩件事: 分割文件:將打包生成的文件進(jìn)行分割,生成多個(gè) js 文件。 按需加載:需要哪個(gè)文件就加載哪個(gè)文件。 使用 單頁(yè)面配置 如果我們使用了動(dòng)態(tài)導(dǎo)入的語(yǔ)法,那么在eslint里面需要支持一下 下載 npm i eslint-plugin-import -D 配置 // .eslintrc.js module.exports = { // 繼承 Eslint 規(guī)則 extends: ["eslint:recommended"], env: { node: true, // 啟用node中全局變量 browser: true, // 啟用瀏覽器中全局變量 }, plugins: ["import"], // 解決動(dòng)態(tài)導(dǎo)入import語(yǔ)法報(bào)錯(cuò)問(wèn)題 --> 實(shí)際使用eslint-plugin-import的規(guī)則解決的 parserOptions: { ecmaVersion: 6, sourceType: "module", }, rules: { "no-var": 2, // 不能使用 var 定義變量 }, }; Code Split統(tǒng)一命名 我們的配置中對(duì)于輸出文件的配置有很多,比如入口文件,圖片文件,字體圖標(biāo)文件、css文件、動(dòng)態(tài)引入文件等等,我們希望統(tǒng)一寫在一個(gè)地方 配置 這里以生產(chǎn)模式為例子 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const os = require("os"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // cpu核數(shù) const threads = os.cpus().length; // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { // 需要一個(gè)絕對(duì)路徑 path: path.resolve(__dirname, '../dist'), clean: true, // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名無(wú)關(guān)。 // 為什么需要這樣命名呢?如果還是之前寫法main.js,那么打包生成兩個(gè)js文件都會(huì)叫做main.js會(huì)發(fā)生覆蓋。(實(shí)際上會(huì)直接報(bào)錯(cuò)的) filename: "static/js/[name].js", // 入口文件打包輸出資源命名方式 chunkFilename: "static/js/[name].chunk.js", // 動(dòng)態(tài)導(dǎo)入輸出資源命名方式 assetModuleFilename: "static/media/[name].[hash][ext]", // 圖片、字體等資源命名方式(注意用hash) }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "source-map", // 解析器 module: { rules: [ { oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) // generator: { // // 將圖片文件輸出到 static/imgs 目錄中 // // 將圖片文件命名 [hash:8][ext][query] // // [hash:8]: hash值取8位 // // [ext]: 使用之前的文件擴(kuò)展名 // // [query]: 添加之前的query參數(shù) // filename: "static/imgs/[hash:8][ext][query]", // }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", // generator: { // filename: "static/media/[hash:8][ext][query]", // }, }, { test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代碼不編譯 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ { loader: "thread-loader", // 開(kāi)啟多進(jìn)程 options: { workers: threads, // 數(shù)量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 開(kāi)啟babel編譯緩存 cacheCompression: false, // 緩存文件不要壓縮 plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積 }, }, ], }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默認(rèn)值 cache: true, // 開(kāi)啟緩存 // 緩存目錄 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" ), threads, // 開(kāi)啟多進(jìn)程 }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/[name].css", chunkFilename: "static/css/[name].chunk.css", }), // new CssMinimizerPlugin() ], // 推薦將壓縮操作放在這里 optimization: { minimize: true, minimizer: [ // css壓縮也可以寫到optimization.minimizer里面,效果一樣的 new CssMinimizerPlugin(), // 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了 new TerserPlugin({ parallel: threads // 開(kāi)啟多進(jìn)程 }), // 壓縮圖片 new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], [ "svgo", { plugins: [ "preset-default", "prefixIds", { name: "sortAttrs", params: { xmlnsOrder: "alphabetical", }, }, ], }, ], ], }, }, }), ], splitChunks: { chunks: "all" } }, // 開(kāi)發(fā)服務(wù)器 // devServer: { // host: "localhost", // 啟動(dòng)服務(wù)器域名 // port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) // open: true, // 是否自動(dòng)打開(kāi)瀏覽器 // }, mode: 'production' } Preload / Prefetch 預(yù)處理 介紹 我們前面已經(jīng)做了代碼分割,同時(shí)會(huì)使用 import 動(dòng)態(tài)導(dǎo)入語(yǔ)法來(lái)進(jìn)行代碼按需加載(我們也叫懶加載,比如路由懶加載就是這樣實(shí)現(xiàn)的)。 但是加載速度還不夠好,比如:是用戶點(diǎn)擊按鈕時(shí)才加載這個(gè)資源的,如果資源體積很大,那么用戶會(huì)感覺(jué)到明顯卡頓效果。 我們想在瀏覽器空閑時(shí)間,加載后續(xù)需要使用的資源。我們就需要用上 Preload 或 Prefetch 技術(shù) Preload:告訴瀏覽器立即加載資源。 Prefetch:告訴瀏覽器在空閑時(shí)才開(kāi)始加載資源。(開(kāi)發(fā)中這個(gè)用的最多) 它們共同點(diǎn): 都只會(huì)加載資源,并不執(zhí)行。 都有緩存。 它們區(qū)別: Preload加載優(yōu)先級(jí)高,Prefetch加載優(yōu)先級(jí)低。 Preload只能加載當(dāng)前頁(yè)面需要使用的資源,Prefetch可以加載當(dāng)前頁(yè)面資源,也可以加載下一個(gè)頁(yè)面需要使用的資源。 總結(jié): 當(dāng)前頁(yè)面優(yōu)先級(jí)高的資源用 Preload 加載。 下一個(gè)頁(yè)面需要使用的資源用 Prefetch 加載。 它們的問(wèn)題:兼容性較差。 我們可以去 Can I Use 網(wǎng)站查詢 API 的兼容性問(wèn)題。 Preload 相對(duì)于 Prefetch 兼容性好一點(diǎn)。 使用 下載 npm i @vue/preload-webpack-plugin -D 配置 以生產(chǎn)模式為例子 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const os = require("os"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin"); // cpu核數(shù) const threads = os.cpus().length; // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { // 需要一個(gè)絕對(duì)路徑 path: path.resolve(__dirname, '../dist'), clean: true, // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名無(wú)關(guān)。 // 為什么需要這樣命名呢?如果還是之前寫法main.js,那么打包生成兩個(gè)js文件都會(huì)叫做main.js會(huì)發(fā)生覆蓋。(實(shí)際上會(huì)直接報(bào)錯(cuò)的) filename: "static/js/[name].js", // 入口文件打包輸出資源命名方式 chunkFilename: "static/js/[name].chunk.js", // 動(dòng)態(tài)導(dǎo)入輸出資源命名方式 assetModuleFilename: "static/media/[name].[hash][ext]", // 圖片、字體等資源命名方式(注意用hash) }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "source-map", // 解析器 module: { rules: [ { oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) // generator: { // // 將圖片文件輸出到 static/imgs 目錄中 // // 將圖片文件命名 [hash:8][ext][query] // // [hash:8]: hash值取8位 // // [ext]: 使用之前的文件擴(kuò)展名 // // [query]: 添加之前的query參數(shù) // filename: "static/imgs/[hash:8][ext][query]", // }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", // generator: { // filename: "static/media/[hash:8][ext][query]", // }, }, { test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代碼不編譯 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ { loader: "thread-loader", // 開(kāi)啟多進(jìn)程 options: { workers: threads, // 數(shù)量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 開(kāi)啟babel編譯緩存 cacheCompression: false, // 緩存文件不要壓縮 plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積 }, }, ], }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默認(rèn)值 cache: true, // 開(kāi)啟緩存 // 緩存目錄 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" ), threads, // 開(kāi)啟多進(jìn)程 }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/[name].css", chunkFilename: "static/css/[name].chunk.css", }), // new CssMinimizerPlugin() new PreloadWebpackPlugin({ rel: "preload", // preload兼容性更好 as: "script", // rel: 'prefetch' // prefetch兼容性更差 }), ], // 推薦將壓縮操作放在這里 optimization: { minimize: true, minimizer: [ // css壓縮也可以寫到optimization.minimizer里面,效果一樣的 new CssMinimizerPlugin(), // 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了 new TerserPlugin({ parallel: threads // 開(kāi)啟多進(jìn)程 }), // 壓縮圖片 new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], [ "svgo", { plugins: [ "preset-default", "prefixIds", { name: "sortAttrs", params: { xmlnsOrder: "alphabetical", }, }, ], }, ], ], }, }, }), ], splitChunks: { chunks: "all" } }, // 開(kāi)發(fā)服務(wù)器 // devServer: { // host: "localhost", // 啟動(dòng)服務(wù)器域名 // port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) // open: true, // 是否自動(dòng)打開(kāi)瀏覽器 // }, mode: 'production' } Network Cache 介紹 將來(lái)開(kāi)發(fā)時(shí)我們對(duì)靜態(tài)資源會(huì)使用緩存來(lái)優(yōu)化,這樣瀏覽器第二次請(qǐng)求資源就能讀取緩存了,速度很快。 但是這樣的話就會(huì)有一個(gè)問(wèn)題, 因?yàn)榍昂筝敵龅奈募且粯拥?,都?main.js,一旦將來(lái)發(fā)布新版本,因?yàn)槲募麤](méi)有變化導(dǎo)致瀏覽器會(huì)直接讀取緩存,不會(huì)加載新資源,項(xiàng)目也就沒(méi)法更新了。 所以我們從文件名入手,確保更新前后文件名不一樣,這樣就可以做緩存了。 它們都會(huì)生成一個(gè)唯一的 hash 值。 fullhash(webpack4 是 hash) 每次修改任何一個(gè)文件,所有文件名的 hash 至都將改變。所以一旦修改了任何一個(gè)文件,整個(gè)項(xiàng)目的文件緩存都將失效。 chunkhash 根據(jù)不同的入口文件(Entry)進(jìn)行依賴文件解析、構(gòu)建對(duì)應(yīng)的 chunk,生成對(duì)應(yīng)的哈希值。我們 js 和 css 是同一個(gè)引入,會(huì)共享一個(gè) hash 值。 contenthash 根據(jù)文件內(nèi)容生成 hash 值,只有文件內(nèi)容變化了,hash 值才會(huì)變化。所有文件 hash 值是獨(dú)享且不同的。 存在問(wèn)題與解決方案 問(wèn)題: 當(dāng)我們修改 math.js 文件再重新打包的時(shí)候,因?yàn)?contenthash 原因,math.js 文件 hash 值發(fā)生了變化(這是正常的)。 但是 main.js 文件的 hash 值也發(fā)生了變化,這會(huì)導(dǎo)致 main.js 的緩存失效。明明我們只修改 math.js, 為什么 main.js 也會(huì)變身變化呢? 原因: 更新前:math.xxx.js, main.js 引用的 math.xxx.js 更新后:math.yyy.js, main.js 引用的 math.yyy.js, 文件名發(fā)生了變化,間接導(dǎo)致 main.js 也發(fā)生了變化 解決: 將 hash 值單獨(dú)保管在一個(gè) runtime 文件中。 我們最終輸出三個(gè)文件:main、math、runtime。當(dāng) math 文件發(fā)送變化,變化的是 math 和 runtime 文件,main 不變。 runtime 文件只保存文件的 hash 值和它們與文件關(guān)系,整個(gè)文件體積就比較小,所以變化重新請(qǐng)求的代價(jià)也小。 使用 // 提取runtime文件 runtimeChunk: { name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名規(guī)則 }, 以生產(chǎn)模式為例子 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const os = require("os"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin"); // cpu核數(shù) const threads = os.cpus().length; // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { // 需要一個(gè)絕對(duì)路徑 path: path.resolve(__dirname, '../dist'), clean: true, // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名無(wú)關(guān)。 // 為什么需要這樣命名呢?如果還是之前寫法main.js,那么打包生成兩個(gè)js文件都會(huì)叫做main.js會(huì)發(fā)生覆蓋。(實(shí)際上會(huì)直接報(bào)錯(cuò)的) // [contenthash:8]使用contenthash,取8位長(zhǎng)度 filename: "static/js/[name].[contenthash:8].js", // 入口文件打包輸出資源命名方式 chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 動(dòng)態(tài)導(dǎo)入輸出資源命名方式 assetModuleFilename: "static/media/[name].[hash][ext]", // 圖片、字體等資源命名方式(注意用hash) }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "source-map", // 解析器 module: { rules: [ { oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) // generator: { // // 將圖片文件輸出到 static/imgs 目錄中 // // 將圖片文件命名 [hash:8][ext][query] // // [hash:8]: hash值取8位 // // [ext]: 使用之前的文件擴(kuò)展名 // // [query]: 添加之前的query參數(shù) // filename: "static/imgs/[hash:8][ext][query]", // }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", // generator: { // filename: "static/media/[hash:8][ext][query]", // }, }, { test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代碼不編譯 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ { loader: "thread-loader", // 開(kāi)啟多進(jìn)程 options: { workers: threads, // 數(shù)量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 開(kāi)啟babel編譯緩存 cacheCompression: false, // 緩存文件不要壓縮 plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積 }, }, ], }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默認(rèn)值 cache: true, // 開(kāi)啟緩存 // 緩存目錄 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" ), threads, // 開(kāi)啟多進(jìn)程 }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/[name].[contenthash:8].css", chunkFilename: "static/css/[name].[contenthash:8].chunk.css", }), // new CssMinimizerPlugin() new PreloadWebpackPlugin({ rel: "preload", // preload兼容性更好 as: "script", // rel: 'prefetch' // prefetch兼容性更差 }), ], // 推薦將壓縮操作放在這里 optimization: { minimize: true, minimizer: [ // css壓縮也可以寫到optimization.minimizer里面,效果一樣的 new CssMinimizerPlugin(), // 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了 new TerserPlugin({ parallel: threads // 開(kāi)啟多進(jìn)程 }), // 壓縮圖片 new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], [ "svgo", { plugins: [ "preset-default", "prefixIds", { name: "sortAttrs", params: { xmlnsOrder: "alphabetical", }, }, ], }, ], ], }, }, }), ], splitChunks: { chunks: "all" }, // 提取runtime文件 runtimeChunk: { name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名規(guī)則 }, }, // 開(kāi)發(fā)服務(wù)器 // devServer: { // host: "localhost", // 啟動(dòng)服務(wù)器域名 // port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) // open: true, // 是否自動(dòng)打開(kāi)瀏覽器 // }, mode: 'production' } Core-js徹底解決js的兼容問(wèn)題 介紹 過(guò)去我們使用 babel 對(duì) js 代碼進(jìn)行了兼容性處理,其中使用@babel/preset-env 智能預(yù)設(shè)來(lái)處理兼容性問(wèn)題。 它能將 ES6 的一些語(yǔ)法進(jìn)行編譯轉(zhuǎn)換,比如箭頭函數(shù)、點(diǎn)點(diǎn)點(diǎn)運(yùn)算符等。但是如果是 async 函數(shù)、promise 對(duì)象、數(shù)組的一些方法(includes)等,它沒(méi)辦法處理。 所以此時(shí)我們 js 代碼仍然存在兼容性問(wèn)題,一旦遇到低版本瀏覽器會(huì)直接報(bào)錯(cuò)。所以我們想要將 js 兼容性問(wèn)題徹底解決 core-js 是專門用來(lái)做 ES6 以及以上 API 的 polyfill。 polyfill翻譯過(guò)來(lái)叫做墊片/補(bǔ)丁。就是用社區(qū)上提供的一段代碼,讓我們?cè)诓患嫒菽承┬绿匦缘臑g覽器上,使用該新特性。 使用 下載 npm i core-js 三種使用方式 手動(dòng)全部引入 import "core-js"; 這樣引入會(huì)將所有兼容性代碼全部引入,體積太大了。我們只想引入 promise 的 polyfill。 手動(dòng)按需引入 import "core-js/es/promise"; 只引入打包 promise 的 polyfill,打包體積更小。但是將來(lái)如果還想使用其他語(yǔ)法,我需要手動(dòng)引入庫(kù)很麻煩。 自動(dòng)按需引入 修改babel.config.js的智能預(yù)設(shè),幫助我們進(jìn)行core.js的按需加載 // babel.config.js module.exports = { // 智能預(yù)設(shè):能夠編譯ES6語(yǔ)法 presets: [ [ "@babel/preset-env", // 按需加載core-js的polyfill { useBuiltIns: "usage", corejs: { version: "3", proposals: true } }, ], ], }; 此時(shí)就會(huì)自動(dòng)根據(jù)我們代碼中使用的語(yǔ)法,來(lái)按需加載相應(yīng)的 polyfill 了。 PWA 介紹 開(kāi)發(fā) Web App 項(xiàng)目,項(xiàng)目一旦處于網(wǎng)絡(luò)離線情況,就沒(méi)法訪問(wèn)了。 我們希望給項(xiàng)目提供離線體驗(yàn)。 漸進(jìn)式網(wǎng)絡(luò)應(yīng)用程序(progressive web application - PWA): 是一種可以提供類似于 native app(原生應(yīng)用程序) 體驗(yàn)的 Web App 的技術(shù)。 其中最重要的是,在 離線(offline) 時(shí)應(yīng)用程序能夠繼續(xù)運(yùn)行功能。 內(nèi)部通過(guò) Service Workers 技術(shù)實(shí)現(xiàn)的。 使用 下載 npm i workbox-webpack-plugin -D 配置 // path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑 const path = require('path') const os = require("os"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin"); const WorkboxPlugin = require("workbox-webpack-plugin"); // cpu核數(shù) const threads = os.cpus().length; // 獲取處理樣式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題 ], }, }, }, preProcessor, ].filter(Boolean); }; module.exports = { // 入口 entry: { // 需要一個(gè)相對(duì)路徑 index: './src/index.js' }, // 輸出 output: { // 需要一個(gè)絕對(duì)路徑 path: path.resolve(__dirname, '../dist'), clean: true, // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名無(wú)關(guān)。 // 為什么需要這樣命名呢?如果還是之前寫法main.js,那么打包生成兩個(gè)js文件都會(huì)叫做main.js會(huì)發(fā)生覆蓋。(實(shí)際上會(huì)直接報(bào)錯(cuò)的) // [contenthash:8]使用contenthash,取8位長(zhǎng)度 filename: "static/js/[name].[contenthash:8].js", // 入口文件打包輸出資源命名方式 chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 動(dòng)態(tài)導(dǎo)入輸出資源命名方式 assetModuleFilename: "static/media/[name].[hash][ext]", // 圖片、字體等資源命名方式(注意用hash) }, // Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。 performance: { hints: false }, devtool: "source-map", // 解析器 module: { rules: [ { oneOf: [ { test: /\.css$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders(), }, { // 正則匹配所有已.less文件結(jié)尾的文件 test: /\.less$/, // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下 // loader:"style-loader" use: getStyleLoaders("less-loader"), }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader"), }, { test: /\.(png|jpe?g|gif|webp)$/, type: "asset", // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。 parser: { // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。 dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理 } }, // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng) // generator: { // // 將圖片文件輸出到 static/imgs 目錄中 // // 將圖片文件命名 [hash:8][ext][query] // // [hash:8]: hash值取8位 // // [ext]: 使用之前的文件擴(kuò)展名 // // [query]: 添加之前的query參數(shù) // filename: "static/imgs/[hash:8][ext][query]", // }, }, { test: /\.(ttf|woff2?|svg)$/, // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL type: "asset/resource", // generator: { // filename: "static/media/[hash:8][ext][query]", // }, }, { test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代碼不編譯 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ { loader: "thread-loader", // 開(kāi)啟多進(jìn)程 options: { workers: threads, // 數(shù)量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 開(kāi)啟babel編譯緩存 cacheCompression: false, // 緩存文件不要壓縮 plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積 }, }, ], }, ] } ] }, plugins: [ new ESLintWebpackPlugin({ // 指定檢查文件的根目錄 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默認(rèn)值 cache: true, // 開(kāi)啟緩存 // 緩存目錄 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" ), threads, // 開(kāi)啟多進(jìn)程 }), new HtmlWebpackPlugin({ // 以 public/index.html 為模板創(chuàng)建文件 // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源 template: path.resolve(__dirname, "../public/index.html"), }), // 提取css成單獨(dú)文件 new MiniCssExtractPlugin({ // 定義輸出文件名和目錄 filename: "static/css/[name].[contenthash:8].css", chunkFilename: "static/css/[name].[contenthash:8].chunk.css", }), // new CssMinimizerPlugin() new PreloadWebpackPlugin({ rel: "preload", // preload兼容性更好 as: "script", // rel: 'prefetch' // prefetch兼容性更差 }), new WorkboxPlugin.GenerateSW({ // 這些選項(xiàng)幫助快速啟用 ServiceWorkers // 不允許遺留任何“舊的” ServiceWorkers clientsClaim: true, skipWaiting: true, }), ], // 推薦將壓縮操作放在這里 optimization: { minimize: true, minimizer: [ // css壓縮也可以寫到optimization.minimizer里面,效果一樣的 new CssMinimizerPlugin(), // 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了 new TerserPlugin({ parallel: threads // 開(kāi)啟多進(jìn)程 }), // 壓縮圖片 new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], [ "svgo", { plugins: [ "preset-default", "prefixIds", { name: "sortAttrs", params: { xmlnsOrder: "alphabetical", }, }, ], }, ], ], }, }, }), ], splitChunks: { chunks: "all" }, // 提取runtime文件 runtimeChunk: { name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名規(guī)則 }, }, // 開(kāi)發(fā)服務(wù)器 // devServer: { // host: "localhost", // 啟動(dòng)服務(wù)器域名 // port: "3000", // 啟動(dòng)服務(wù)器端口號(hào) // open: true, // 是否自動(dòng)打開(kāi)瀏覽器 // }, mode: 'production' } 問(wèn)題與解決 此時(shí)如果直接通過(guò) VSCode 訪問(wèn)打包后頁(yè)面,在瀏覽器控制臺(tái)會(huì)發(fā)現(xiàn) SW registration failed。 因?yàn)槲覀兇蜷_(kāi)的訪問(wèn)路徑是:http://127.0.0.1:5500/dist/index.html。此時(shí)頁(yè)面會(huì)去請(qǐng)求 service-worker.js 文件,請(qǐng)求路徑是:http://127.0.0.1:5500/service-worker.js,這樣找不到會(huì) 404。 實(shí)際 service-worker.js 文件路徑是:http://127.0.0.1:5500/dist/service-worker.js。 解決 下載包 npm i serve -g serve 也是用來(lái)啟動(dòng)開(kāi)發(fā)服務(wù)器來(lái)部署代碼查看效果的。 運(yùn)行指令 serve dist 此時(shí)通過(guò) serve 啟動(dòng)的服務(wù)器我們 service-worker 就能注冊(cè)成功了。 高級(jí)總結(jié) 我們從 4 個(gè)角度對(duì) webpack 和代碼進(jìn)行了優(yōu)化: 提升開(kāi)發(fā)體驗(yàn) 使用 Source Map 讓開(kāi)發(fā)或上線時(shí)代碼報(bào)錯(cuò)能有更加準(zhǔn)確的錯(cuò)誤提示。提升 webpack 提升打包構(gòu)建速度使用 HotModuleReplacement 讓開(kāi)發(fā)時(shí)只重新編譯打包更新變化了的代碼,不變的代碼使用緩存,從而使更新速度更快。使用 OneOf 讓資源文件一旦被某個(gè) loader 處理了,就不會(huì)繼續(xù)遍歷了,打包速度更快。使用 Include/Exclude 排除或只檢測(cè)某些文件,處理的文件更少,速度更快。使用 Cache 對(duì) eslint 和 babel 處理的結(jié)果進(jìn)行緩存,讓第二次打包速度更快。使用 Thead 多進(jìn)程處理 eslint 和 babel 任務(wù),速度更快。(需要注意的是,進(jìn)程啟動(dòng)通信都有開(kāi)銷的,要在比較多代碼處理時(shí)使用才有效果) 減少代碼體積 使用 Tree Shaking 剔除了沒(méi)有使用的多余代碼,讓代碼體積更小。使用 @babel/plugin-transform-runtime 插件對(duì) babel 進(jìn)行處理,讓輔助代碼從中引入,而不是每個(gè)文件都生成輔助代碼,從而體積更小。使用 Image Minimizer 對(duì)項(xiàng)目中圖片進(jìn)行壓縮,體積更小,請(qǐng)求速度更快。(需要注意的是,如果項(xiàng)目中圖片都是在線鏈接,那么就不需要了。本地項(xiàng)目靜態(tài)圖片才需要進(jìn)行壓縮。) 優(yōu)化代碼運(yùn)行性能 使用 Code Split 對(duì)代碼進(jìn)行分割成多個(gè) js 文件,從而使單個(gè)文件體積更小,并行加載 js 速度更快。并通過(guò) import 動(dòng)態(tài)導(dǎo)入語(yǔ)法進(jìn)行按需加載,從而達(dá)到需要使用時(shí)才加載該資源,不用時(shí)不加載資源。使用 Preload / Prefetch 對(duì)代碼進(jìn)行提前加載,等未來(lái)需要使用時(shí)就能直接使用,從而用戶體驗(yàn)更好。使用 Network Cache 能對(duì)輸出資源文件進(jìn)行更好的命名,將來(lái)好做緩存,從而用戶體驗(yàn)更好。使用 Core-js 對(duì) js 進(jìn)行兼容性處理,讓我們代碼能運(yùn)行在低版本瀏覽器。使用 PWA 能讓代碼離線也能訪問(wèn),從而提升用戶體驗(yàn)。 如果你學(xué)習(xí)到這了,那么恭喜你,你已經(jīng)可以搞定面試官了?。?! 該文章在 2025/4/30 15:36:46 編輯過(guò) |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |