Webpack+實(shí)現(xiàn)原理

什么是webpack?

1.webpack是一套基于NodeJS的"模塊打包工具",
在webpack剛推出的時(shí)候就是一個(gè)單純的JS模塊打包工具,可以將多個(gè)模塊的JS文件合并打包到一個(gè)文件中
但是隨著時(shí)間的推移、眾多開發(fā)者的追捧和眾多開發(fā)者的貢獻(xiàn)
現(xiàn)在webpack不僅僅能夠打包JS模塊, 還可以打包CSS/LESS/SCSS/圖片等其它文件

2.為什么要分模塊?
如果將所有的JS代碼都寫到一個(gè)文件中, 十分不利于代碼的維護(hù)和復(fù)用
所以我們可以將不同的功能寫到不同的模塊中, 這樣就提升了代碼的維護(hù)性和復(fù)用性
但是當(dāng)將代碼寫到不同模塊時(shí)新的問題又出現(xiàn)了,
例如: 導(dǎo)入資源變多了, 請求次數(shù)變多了, 網(wǎng)頁性能也就差了
例如: 不同功能都放到了不同模塊中了, 那么如何維護(hù)模塊之間的關(guān)系也變成一個(gè)難題了
<script src="./header.js"></script>
<script src="./content.js"></script>
<script src="./index.js"></script>
<script src="./footer.js"></script> // 如果index.js中用到了footer,就會(huì)報(bào)錯(cuò)
例如: ... ...

3.如何解決上述問題
3.1項(xiàng)目上線時(shí)將用到的所有模塊都合并到一個(gè)文件中
3.2在index.html中只導(dǎo)入主文件, 再主文件中再導(dǎo)入依賴模塊

4.如何通過webpack來打包JS模塊
4.1安裝webpack
npm init -y
npm install --save-dev webpack
npm install --save-dev webpack-cli
4.2在終端中輸入打包的指令
npx webpack index.js
注意點(diǎn):
index.js就是需要打包的文件
打包之后的文件會(huì)放到dist目錄中, 名稱叫做main.js
-->



什么是webpack配置文件?

1.我們在打包JS文件的時(shí)候需要輸入: npx webpack index.js
這句指令的含義是: 利用webpack將index.js和它依賴的模塊打包到一個(gè)文件中
其實(shí)在webpack指令中除了可以通過命令行的方式告訴webpack需要打包哪個(gè)文件以外,
還可以通過配置文件的方式告訴webpack需要打包哪個(gè)文件

2.webpack常見配置
entry: 需要打包的文件
output: 打包之后輸出路徑和文件名稱
mode: 打包模式 development/production
development: 不會(huì)壓縮打包后的JS代碼
production: 會(huì)自動(dòng)壓縮打包后的JS代碼
-->

3.webpack配置注意事項(xiàng)
配置文件的名稱必須叫做: webpack.config.js, 否則直接輸入 npx webpack打包會(huì)出錯(cuò)
如果要使用其它名稱, 那么在輸入打包命令時(shí)候必須通過 --config 指定配置文件名稱
npx webpack --config xxx

4.打包命令簡化
每次輸入npx webpack --config xxx來打包文件會(huì)有一點(diǎn)蛋疼, 所以我們可以通過npm script來簡化這個(gè)操作

什么是sourcemap?

1.webpack打包后的文件會(huì)自動(dòng)添加很多代碼, 在開發(fā)過程中非常不利于我們?nèi)フ{(diào)試
因?yàn)槿绻\(yùn)行webpack打包后的代碼,錯(cuò)誤提示的內(nèi)容也是打包后文件的內(nèi)容
所以為了降低調(diào)試的難度, 提高錯(cuò)誤代碼的閱讀性, 我們就需要知道打包后代碼和打包之前代碼的映射關(guān)系
只要有了這個(gè)映射關(guān)系我們就能很好的顯示錯(cuò)誤提示的內(nèi)容, 存儲(chǔ)這個(gè)映射關(guān)系的文件我們就稱之為sourcemap

2.如何開啟sourcemap
https://www.webpackjs.com/configuration/devtool/
2.1在webpack.config.js中添加
devtool: "xxx",
2.2各配置項(xiàng)說明:
eval:
不會(huì)單獨(dú)生成sourcemap文件, 會(huì)將映射關(guān)系存儲(chǔ)到打包的文件中, 并且通過eval存儲(chǔ)
優(yōu)勢: 性能最好
缺點(diǎn): 業(yè)務(wù)邏輯比較復(fù)雜時(shí)候提示信息可能不全面不正確

source-map:
會(huì)單獨(dú)生成sourcemap文件, 通過單獨(dú)文件來存儲(chǔ)映射關(guān)系
優(yōu)勢: 提示信息全面,可以直接定位到錯(cuò)誤代碼的行和列
缺點(diǎn): 打包速度慢

inline:
不會(huì)單獨(dú)生成sourcemap文件, 會(huì)將映射關(guān)系存儲(chǔ)到打包的文件中, 并且通過base64字符串形式存儲(chǔ)

cheap:
生成的映射信息只能定位到錯(cuò)誤行不能定位到錯(cuò)誤列

module:
不僅希望存儲(chǔ)我們代碼的映射關(guān)系, 還希望存儲(chǔ)第三方模塊映射關(guān)系, 以便于第三方模塊出錯(cuò)時(shí)也能更好的排錯(cuò)

2.3企業(yè)開發(fā)配置:
development: cheap-module-eval-source-map
只需要行錯(cuò)誤信息, 并且包含第三方模塊錯(cuò)誤信息, 并且不會(huì)生成單獨(dú)sourcemap文件

production: cheap-module-source-map
只需要行錯(cuò)誤信息, 并且包含第三方模塊錯(cuò)誤信息, 并且會(huì)生成單獨(dú)sourcemap文件

什么是loader?

1.webapck的本質(zhì)是一個(gè)模塊打包工具, 所以webpack默認(rèn)只能處理JS文件,不能處理其他文件,
因?yàn)槠渌募袥]有模塊的概念, 但是在企業(yè)開發(fā)中我們除了需要對JS進(jìn)行打包以外,
還有可能需要對圖片/CSS等進(jìn)行打包, 所以為了能夠讓webpack能夠?qū)ζ渌奈募愋瓦M(jìn)行打包,
在打包之前就必須將其它類型文件轉(zhuǎn)換為webpack能夠識別處理的模塊,
用于將其它類型文件轉(zhuǎn)換為webpack能夠識別處理模塊的工具我們就稱之為loader

2.如何使用loader
webpack中的loader都是用NodeJS編寫的, 但是在企業(yè)開發(fā)中我們完全沒有必要自己編寫,
因?yàn)橐呀?jīng)有眾多大神幫我們編寫好了企業(yè)中常用的loader, 我們只需要安裝、配置、使用即可

2.1通過npm安裝對應(yīng)的loader
2.2按照loader作者的要求在webpack進(jìn)行相關(guān)配置
2.3使用配置好的loader

3.fileloader使用
https://www.webpackjs.com/loaders/file-loader/
3.1安裝file-loader
npm install --save-dev file-loader
3.2在webpack.config.js中配置file-loader
module: {
rules: [
{
test: /.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {}
}
]
}
]
}

注意點(diǎn):
默認(rèn)情況下fileloader生成的圖片名就是文件內(nèi)容的 MD5 哈希值
如何想打包后不修改圖片的名稱, 那么可以新增配置 name: "[name].[ext]"
其它命名規(guī)則詳見: placeholders

注意點(diǎn):
默認(rèn)情況下fileloader會(huì)將生成的圖片放到dist根目錄下面
如果想打包之后放到指定目錄下面, 那么可以新增配置 outputPath: "images/"

注意點(diǎn):
如果需要將圖片托管到其它服務(wù)器, 那么只需在打包之前配置 publicPath: "托管服務(wù)器地址"即可

urlloader

1.url-loader 功能類似于 file-loader,
但是在文件大小(單位 byte)低于指定的限制時(shí),可以返回一個(gè) DataURL

2.urlloader使用
https://www.webpackjs.com/loaders/url-loader/
2.1安裝urlloader
npm install --save-dev url-loader
2.2配置urlloader
{
test: /.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
name: "[name].[ext]",
outputPath: "/images",
limit: 1024
}
}
]
}

優(yōu)勢:
圖片比較小的時(shí)候直接轉(zhuǎn)換成base64字符串圖片, 減少請求次數(shù)
圖片比較大的時(shí)候由于生成的base64字符串圖片也比較大, 就保持原有的圖片

css-loader

1.和圖片一樣webpack默認(rèn)能不能處理CSS文件, 所以也需要借助loader將CSS文件轉(zhuǎn)換為webpack能夠處理的類型

2.css-loader使用:
2.1安裝cssloader
npm install --save-dev css-loader
2.2安裝styleloader
npm install style-loader --save-dev
2.3配置css-loader
{
test: /.css$/,
use: [ 'style-loader', 'css-loader' ]
}

3.css-loader和style-loader作用
css-loader: 解析css文件中的@import依賴關(guān)系
style-loader: 將webpack處理之后的內(nèi)容插入到HTML的HEAD代碼中

4.loader特點(diǎn):
4.1單一原則, 一個(gè)loader只做一件事情
4.2多個(gè)loader會(huì)按照從右至左, 從下至上的順序執(zhí)行
例如: 從右至左
[ 'style-loader', 'css-loader' ]
先執(zhí)行css-loader解析css文件關(guān)系拿到所有內(nèi)容,
再執(zhí)行style-loader將內(nèi)容插入到HTML的HEAD代碼中
例如: 從下至上
[{
loader: "style-loader"
},{
loader: "css-loader"
}]
先執(zhí)行css-loader解析css文件關(guān)系拿到所有內(nèi)容,
再執(zhí)行style-loader將內(nèi)容插入到HTML的HEAD代碼中

ES6模塊化

1.在ES6出現(xiàn)之前,JS不像其他語言擁有“模塊化”這一概念,于是為了支持JS模塊化
我們使用類、立即執(zhí)行函數(shù)或者第三方插件(RequireJS、seaJS)來實(shí)現(xiàn)模塊化
但是在ES6出現(xiàn)之后, 上述解決方案都已經(jīng)被廢棄, 因?yàn)镋S6中正式引入了模塊化的概念

ES6模塊化模塊和NodeJS中一樣, 一個(gè)文件就是一個(gè)模塊, 模塊中的數(shù)據(jù)都是私有的
ES6模塊化模塊和NodeJS中一樣, 可以通過對應(yīng)的關(guān)鍵字暴露模塊中的數(shù)據(jù),
可以通過對應(yīng)的關(guān)鍵字導(dǎo)入模塊, 使用模塊中暴露的數(shù)據(jù)

2.ES6模塊化使用
2.1常規(guī)導(dǎo)出
2.1.1分開導(dǎo)入導(dǎo)出
export xxx;
import {xxx} from "path";

2.1.2一次性導(dǎo)入導(dǎo)出
export {xxx, yyy, zzz};
import {xxx, yyy, zzz} from "path";

注意點(diǎn):
接收導(dǎo)入變量名必須和導(dǎo)出變量名一致
如果想修改接收變量名可以通過 xxx as newName方式
變量名被修改后原有變量名自動(dòng)失效

2.2默認(rèn)導(dǎo)入導(dǎo)出
export default xxx;
import xxx from "path";

注意點(diǎn):
一個(gè)模塊只能使用一次默認(rèn)導(dǎo)出, 多次無效
默認(rèn)導(dǎo)出時(shí), 導(dǎo)入的名稱可以和導(dǎo)出的名稱不一致

什么是Tree-Shaking?

1.過濾掉無用的JS代碼和CSS代碼, 我們稱之為Tree-Shaking
例如: 在a.js中引入了b模塊, b模塊中有2個(gè)方法, 但是我只用到了1個(gè)方法
默認(rèn)情況下會(huì)將b模塊中所有代碼都打包到a.js中,
為了提升網(wǎng)頁性能降低打包體積, 我們可以只將用到的方法打包到a.js中

2.webpack中如何開啟Tree-Shaking?
https://www.webpackjs.com/guides/tree-shaking/
2.1開發(fā)環(huán)境
webpack.config.js配置, 告訴webpack只打包導(dǎo)入模塊中用到的內(nèi)容
optimization: {
usedExports: true
},
package.json配置, 告訴webpack哪些文件不做Tree-Shaking
"sideEffects": [".css", ".less", "*.scss"],

2.2生產(chǎn)環(huán)境
無需進(jìn)行任何配置, webpack默認(rèn)已經(jīng)實(shí)現(xiàn)了Tree-Shaking

注意點(diǎn):

  • 只有ES Modle導(dǎo)入才支持Tree-Shaking
  • 任何導(dǎo)入的文件都會(huì)受到 tree shaking 的影響。
    這意味著,如果在項(xiàng)目中使用類似 css-loader 并導(dǎo)入 CSS 文件,
    則需要將其添加到 side effect 列表中,以免在生產(chǎn)模式中無意中將它刪除:
CSS模塊Tree-Shaking

1.不光JS模塊可以進(jìn)行Tree-Shaking, CSS模塊也可以進(jìn)行Tree-Shaking

2.如何開啟CSS模塊Tree-Shaking
https://github.com/webpack-contrib/purifycss-webpack
2.1安裝相關(guān)插件
npm i -D purifycss-webpack purify-css glob-all
2.2配置插件
const PurifyCSS = require("purifycss-webpack");
const glob = require("glob-all");

new PurifyCSS({
paths: glob.sync([
// 要做CSS Tree Shaking的路徑文件
path.resolve(__dirname, "./.html"),
path.resolve(__dirname, "./src/js/
.js"),
])
}),

什么是Code-Splitting(代碼分割)?

1.默認(rèn)情況下webpack會(huì)將所有引入的模塊都打包到一個(gè)文件中,
這樣就導(dǎo)致了打包后的文件比較大, 以及修改文件后用戶需要重新下載所有打包內(nèi)容問題
例如: 在a.js中引入了b.js, 那么a.js和b.js都會(huì)被打包到bundle.js中
如果a.js有1MB, b.js也有1MB, 那么打包之后的文件就有2MB
那么用戶第一次打開網(wǎng)頁的時(shí)候就需要下載2MB的文件
問題的關(guān)鍵在于, 如果我們修改了a.js, 但沒有修改b.js
重新打包后用戶需要重新下載新打包的文件(因?yàn)橛脩舯镜鼐彺娴氖莂和b的合體)
這樣就導(dǎo)致了每次修改了其中一個(gè)文件用戶都要重新下載所有內(nèi)容
解決方案: 將不經(jīng)常修改的內(nèi)容打包到另一個(gè)文件中, 這樣每次修改后用戶就只用下載修改后的文件
沒有被修改的文件由于用戶上一次打開已經(jīng)緩存在了本地就不用下載了, 這樣性能也提升了
Code-Splitting就是將不經(jīng)常修改的模塊打包到單獨(dú)的文件中, 避免每次修改用戶都需要重新下載所有內(nèi)容

2.如何開啟Code-Splitting
2.1手動(dòng)分割(了解)

  • 在單獨(dú)文件中引入模塊, 將模塊中的內(nèi)容添加到window上
  • 修改配置文件同時(shí)打包多個(gè)文件
    entry: {
    calculate: "./src/js/calculate.js", // 先打包會(huì)被先引入
    main: "./src/js/index.js",
    },
    output: {
    filename: "js/[name].js",
    path: path.resolve(__dirname, "bundle")
    },

2.2自動(dòng)分割
webpack會(huì)自動(dòng)判斷是否需要分割, 如果需要會(huì)自動(dòng)幫助我們風(fēng)格
optimization: {
splitChunks: {
chunks: "all"
}
},

瀏覽器緩存問題

1.瀏覽器會(huì)自動(dòng)緩存網(wǎng)頁上的資源, 以便于提升下次訪問的速度
但正式因?yàn)闉g覽器的緩存機(jī)制, 導(dǎo)致文件內(nèi)容被修改之后只要文件名稱沒有發(fā)生變化
就不會(huì)重新去加載修改之后的資源, 所以刷新網(wǎng)頁后顯示的還是修改之前的內(nèi)容
為了解決這個(gè)問題, 我們就需要在打包文件的時(shí)候給"文件名稱加上內(nèi)容的hash值"
一旦內(nèi)容發(fā)生了變化, 內(nèi)容的hash值就會(huì)發(fā)生變化, 文件的名稱也會(huì)發(fā)生變化
一旦文件的名稱發(fā)生了變化, 瀏覽器就會(huì)自動(dòng)去加載新打包的文件

2.hash/chunkhash/contenthash
hash:
根據(jù)每次編譯打包的內(nèi)容生成的哈希值, 每次打包都不一樣, 不能很好利用緩存, 不推薦
chunkhash:
根據(jù)不同的入口文件(Entry)進(jìn)行依賴文件解析、構(gòu)建對應(yīng)的chunk,生成對應(yīng)的哈希值。
在生產(chǎn)環(huán)境里把一些公共庫和程序入口文件區(qū)分開,單獨(dú)打包構(gòu)建,
接著我們采用chunkhash的方式生成哈希值,那么只要我們不改動(dòng)公共庫的代碼,就可以保證其哈希值不會(huì)受影響。
注意點(diǎn): 只支持css和js, 不支持img等其它資源
contenthash(推薦):
根據(jù)某個(gè)文件內(nèi)容生成的哈希值, 只要某個(gè)文件內(nèi)容發(fā)生改變,該文件的contenthash就會(huì)發(fā)生變化

注意點(diǎn): 在webpack4中contenthash和熱更新有沖突, 所以在開發(fā)模式想使用contenthash需要關(guān)閉熱更新
但是一般情況下我們需要通過hash解決的是線上代碼的內(nèi)容更新問題, 所以開發(fā)模式無關(guān)緊要

3.manifest
webpack在打包時(shí),會(huì)把庫和業(yè)務(wù)代碼之間的關(guān)系做manifest,
它既存在于業(yè)務(wù)代碼(main.js),也存在于庫中(vendor.js),
在舊版webpack中(webpack4之前),mainfest在每次打包的時(shí)候的時(shí)候可能會(huì)變化,
所以contenthash值也會(huì)跟著變化。配置runtimeChunk后,會(huì)把manifest提取到runtime中,
這樣打包就不會(huì)影響到其他js了。
optimization: {
runtimeChunk: "single",
splitChunks: {
chunks: "all",
},
}

什么是Split-Chunks-Plugin?

1.webpack在代碼分割的時(shí)候底層使用的其實(shí)是Split-Chunks-Plugin來實(shí)現(xiàn)代碼分割的
所以這個(gè)插件的作用就是進(jìn)行代碼分割

2.Split-Chunks-Plugin相關(guān)配置
https://www.webpackjs.com/plugins/split-chunks-plugin/
{
chunks: "async", // 對那些代碼進(jìn)行分割 async(只分割異步加載模塊)、all(所有導(dǎo)入模塊)
minSize: 30000, // 表示被分割的代碼體積至少有多大才分割(單位是字節(jié))
minChunks: 1, // 表示至少被引用多少次數(shù)才分割,默認(rèn)為1
maxAsyncRequests: 5, // 異步加載并發(fā)最大請求數(shù)(保持默認(rèn)即可)
maxInitialRequests: 3, // 最大的初始請求數(shù)(保持默認(rèn)即可)
automaticNameDelimiter: '~', // 命名連接符
name: true, // 拆分出來塊的名字使用0/1/2... 還是指定名稱
cacheGroups: { // 緩存組, 將當(dāng)前文件中導(dǎo)入的所有模塊緩存起來統(tǒng)一處理
vendors: { // 分割從node_modules目錄中導(dǎo)入的模塊
test: /[\/]node_modules[\/]/,
priority: -10 // 優(yōu)先級, 值越小越優(yōu)先
},
default: { // 分割從其它地方導(dǎo)入的模塊
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 如果當(dāng)前代碼塊包含的模塊已經(jīng)有了,就不在產(chǎn)生一個(gè)新的代碼塊
}
}
}

模塊各種引入方式

1.1在HTML中全局引入
<script src="https://code.jquery.com/jquery-3.4.1.js"></script>
特點(diǎn): 什么地方都可以使用
1.2通過npm安裝通過import局部引入
特點(diǎn): 只能在import導(dǎo)入的模塊中使用

2.什么是Provide-Plugin?
https://www.webpackjs.com/plugins/provide-plugin/
自動(dòng)加載模塊,而不必到處 import 或 require
默認(rèn)情況下模塊中的數(shù)據(jù)都是私有的, 所以想要使用模塊必須先導(dǎo)入模塊
如果說在a.js中想要使用jQuery, 那么就必須在a.js中導(dǎo)入jQuery模塊
如果說在b.js中想要使用jQuery, 那么就必須在b.js中導(dǎo)入jQuery模塊
new Webpack.ProvidePlugin({
$: "jquery"
})

什么是imports-loader?

1.https://www.webpackjs.com/loaders/imports-loader/
https://www.npmjs.com/package/imports-loader
imports-loader和Provide-Plugin功能一樣可以實(shí)現(xiàn)全局導(dǎo)入,
但是imports-loader的功能比Provide-Plugin更強(qiáng)大,
imports-loader除了可以實(shí)現(xiàn)全局導(dǎo)入以外, 還可以修改全局this指向
默認(rèn)情況下模塊中的this指向一個(gè)空對象, 我們可以通過imports-loader實(shí)現(xiàn)讓this指向window

2.imports-loader注意點(diǎn):
2.1在企業(yè)開發(fā)中如何需要實(shí)現(xiàn)全局導(dǎo)入, 更推薦使用ProvidePlugin來實(shí)現(xiàn)
因?yàn)镻rovidePlugin是webpack內(nèi)置的官方插件更靠譜
2.2使用imports-loader修改this指向, 系統(tǒng)會(huì)自動(dòng)將我們的代碼放到一個(gè)立即執(zhí)行函數(shù)中
這就導(dǎo)致了在打包時(shí)候import不在第一行, 會(huì)報(bào)錯(cuò)
2.3如何解決?
無需修改this指向, 直接在模塊中使用window

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容