webpack 非同步載入原理
文章來自前端大全公眾號,寫的不錯
webpack 非同步載入原理
webpack ensure
有人稱它為非同步載入,也有人稱為程式碼切割,他其實就是將 js 模組給獨立匯出一個.js 檔案,然後使用這個模組的時候,再建立一個 script
物件,加入到 document.head
物件中,瀏覽器會自動幫我們發起請求,去請求這個 js 檔案,然後寫個回撥函式,讓請求到的 js 檔案做一些業務操作。
舉個例子
需求:main.js
依賴兩個 js 檔案:A.js
是點選 aBtn 按鈕後,才執行的邏輯,B.js
是點選 bBtn 按鈕後,才執行的邏輯。
webpack.config.js
,我們先來寫一下 webpack
打包的配置的程式碼
const path = require('path') // 路徑處理模組
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 引入CleanWebpackPlugin外掛
module.exports = {
entry: {
index: path.join(__dirname, '/src/main.js'),
},
output: {
path: path.join(__dirname, '/dist'),
filename: 'index.js',
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, '/index.html'),
}),
new CleanWebpackPlugin(), // 所要清理的資料夾名稱
],
}
index.html
程式碼如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>webpack</title>
</head>
<body>
<div id="app">
<button id="aBtn">按鈕A</button>
<button id="bBtn">按鈕B</button>
</div>
</body>
</html>
入口檔案 main.js
如下
import A from'./A'
import B from'./B'
document.getElementById('aBtn').onclick = function () {
alert(A)
}
document.getElementById('bBtn').onclick = function () {
alert(B)
}
A.js
和 B.js
的程式碼分別如下
// A.js
const A = 'hello A'
module.exports = A
// B.js
const B = 'hello B'
module.exports = B
此時,我們對專案進行 npm run build
, 打包出來的只有兩個檔案
-
index.html
-
index.js
由此可見,此時 webpack
把 main.js
依賴的兩個檔案都同時打包到同一個 js 檔案,並在 index.html 中引入。但是 A.js
和 B.js
都是點選相應按鈕才會執行的邏輯,如果使用者並沒有點選相應按鈕,而且這兩個檔案又是比較大的話,這樣是不是就導致首頁預設載入的 js 檔案太大,從而導致首頁渲染較慢呢?那麼有能否實現當使用者點選按鈕的時候再載入相應的依賴檔案呢?
webpack.ensure
就解決了這個問題。
require.ensure 非同步載入
下面我們將 main.js
改成非同步載入的方式
document.getElementById('aBtn').onclick = function () {
//非同步載入A
require.ensure([], function () {
let A = require('./A.js')
alert(A)
})
}
document.getElementById('bBtn').onclick = function () {
//非同步載入b
require.ensure([], function () {
let B = require('./B.js')
alert(B)
})
}
此時,我們再進行一下打包,發現多了 1.index.js
和 2.index.js
兩個檔案。而我們開啟頁面時只引入了 index.js
一個檔案,當點選按鈕 A 的時候才引入 1.index.js
檔案,點選按鈕 B 的時候才引入 2.index.js
檔案。這樣就滿足了我們按需載入的需求。
require.ensure
這個函式是一個程式碼分離的分割線,表示回撥裡面的 require
是我們想要進行分割出去的,即 require('./A.js')
,把 A.js 分割出去,形成一個 webpack
打包的單獨 js 檔案。它的語法如下
require.ensure(dependencies: String[], callback: function(require), chunkName: String)
我們開啟 1.index.js
檔案,發現它的程式碼如下
(window.webpackJsonp = window.webpackJsonp || []).push([
[1],
[
,
function (o, n) {
o.exports = 'hello A'
},
],
])
由上面的程式碼可以看出:
-
非同步載入的程式碼,會儲存在一個全域性的
webpackJsonp
中。 -
webpackJsonp.push
的的值,兩個引數分別為非同步載入的檔案中存放的需要安裝的模組對應的 id 和非同步載入的檔案中存放的需要安裝的模組列表。 -
在滿足某種情況下,會執行具體模組中的程式碼。
import() 按需載入
webpack4 官方文件提供了模組按需切割載入,配合 es6 的按需載入 import()
方法,可以做到減少首頁包體積,加快首頁的請求速度,只有其他模組,只有當需要的時候才會載入對應 js。
import()
的語法十分簡單。該函式只接受一個引數,就是引用包的地址,並且使用了 promise
式的回撥,獲取載入的包。在程式碼中所有被 import()
的模組,都將打成一個單獨的包,放在 chunk
儲存的目錄下。在瀏覽器執行到這一行程式碼時,就會自動請求這個資源,實現非同步載入。
下面我們將上述程式碼改成 import()
方式。
document.getElementById('aBtn').onclick = function () {
//非同步載入A
import('./A').then((data) => {
alert(data.A)
})
}
document.getElementById('bBtn').onclick = function () {
//非同步載入b
import('./B').then((data) => {
alert(data.B)
})
}
此時打包出來的檔案和 webpack.ensure
方法是一樣的。
路由懶載入
為什麼需要懶載入?
像 vue 這種單頁面應用,如果沒有路由懶載入,運用 webpack 打包後的檔案將會很大,造成進入首頁時,需要載入的內容過多,出現較長時間的白屏,運用路由懶載入則可以將頁面進行劃分,需要的時候才載入頁面,可以有效的分擔首頁所承擔的載入壓力,減少首頁載入用時。
vue 路由懶載入有以下三種方式
-
vue 非同步元件
-
ES6 的
import()
-
webpack 的
require.ensure()
vue 非同步元件
這種方法主要是使用了 resolve
的非同步機制,用 require
代替了 import
實現按需載入
exportdefaultnew Router({
routes: [
{
path: '/home',',
component: (resolve) => require(['@/components/home'], resolve),
},
{
path: '/about',',
component: (resolve) =>require(['@/components/about'], resolve),
},
],
})
require.ensure
這種模式可以通過引數中的 webpackChunkName
將 js 分開打包。
exportdefaultnew Router({
routes: [
{
path: '/home',
component: (resolve) =>require.ensure([], () => resolve(require('@/components/home')), 'home'),
},
{
path: '/about',
component: (resolve) =>require.ensure([], () => resolve(require('@/components/about')), 'about'),
},
],
})
ES6 的 import()
vue-router
在官網提供了一種方法,可以理解也是為通過 Promise
的 resolve
機制。因為 Promise
函式返回的 Promise
為 resolve
元件本身,而我們又可以使用 import
來匯入元件。
exportdefaultnew Router({
routes: [
{
path: '/home',
component: () =>import('@/components/home'),
},
{
path: '/about',
component: () =>import('@/components/home'),
},
],
})
webpack 分包策略
在 webpack 打包過程中,經常出現 vendor.js
, app.js
單個檔案較大的情況,這偏偏又是網頁最先載入的檔案,這就會使得載入時間過長,從而使得白屏時間過長,影響使用者體驗。所以我們需要有合理的分包策略。
CommonsChunkPlugin
在 Webapck4.x 版本之前,我們都是使用 CommonsChunkPlugin
去做分離
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
return (
module.resource &&
/.js$/.test(module.resource) &&
module.resource.indexOf(path.join(__dirname, './node_modules')) === 0
)
},
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
chunks: 'initial',
minChunks: 2,
}),
]
我們把以下檔案單獨抽離出來打包
-
node_modules
資料夾下的,模組 -
被 3 個 入口
chunk
共享的模組
optimization.splitChunks
webpack 4 最大的改動就是廢除了 CommonsChunkPlugin
引入了 optimization.splitChunks
。如果你的 mode
是 production
,那麼 webpack4 就會自動開啟 Code Splitting
。
它內建的程式碼分割策略是這樣的:
-
新的 chunk 是否被共享或者是來自
node_modules
的模組 -
新的 chunk 體積在壓縮之前是否大於 30kb
-
按需載入 chunk 的併發請求數量小於等於 5 個
-
頁面初始載入時的併發請求數量小於等於 3 個
雖然在 webpack4 會自動開啟 Code Splitting
,但是隨著專案工程的最大,這往往不能滿足我們的需求,我們需要再進行個性化的優化。
應用例項
我們先找到一個優化空間較大的專案來進行操作。這是一個後臺管理系統專案,大部分內容由 3-4 個前端開發,平時開發週期較短,且大部分人沒有優化意識,只是寫好業務程式碼完成需求,日子一長,造成打包出來的檔案較大,大大影響效能。
我們先用 webpack-bundle-analyzer
分析打包後的模組依賴及檔案大小,確定優化的方向在哪。
然後我們再看下打包出來的 js 檔案
看到這兩張圖的時候,我內心是崩潰的,槽點如下
-
打包後生成多個將近 1M 的 js 檔案,其中不乏
vendor.js
首頁必須載入的大檔案 -
xlsx.js
這樣的外掛沒必要使用,匯出 excel 更好的方法應該是後端返回檔案流格式給前端處理 -
echart
和iview
檔案太大,應該使用 cdn 引入的方法
吐槽完之後我們就要開始做正事了。正是因為有這麼多槽點,我們才更好用來驗證我們優化方法的可行性。
抽離 echart 和 iview
由上面分析可知,echart
和 iview
檔案太大,此時我們就用到 webpack4 的 optimization.splitChunks
進行程式碼分割了,把他們單獨抽離打包成檔案。(為了更好地呈現優化效果,我們先把 xlsx.js 去掉)
vue.config.js
修改如下:
chainWebpack: config => {
config.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[/]node_modules[/]/,
priority: 10,
chunks: 'initial'
},
iview: {
name: 'chunk-iview',
priority: 20,
test: /[/]node_modules[/]_?iview(.*)/
},
echarts: {
name: 'chunk-echarts',
priority: 20,
test: /[/]node_modules[/]_?echarts(.*)/
},
commons: {
name: 'chunk-commons',
minChunks: 2,
priority: 5,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
},
此時我們再用 webpack-bundle-analyzer
分析一下
打包出來的 js 檔案
從這裡可以看出我們已經成功把 echart
和 iview
單獨抽離出來了,同時 vendor.js
也相應地減小了體積。此外,我們還可以繼續抽離其他更多的第三方模組。
CDN 方式
雖然第三方模組是單獨抽離出來了,但是在首頁或者相應路由載入時還是要載入這樣一個幾百 kb 的檔案,還是不利於效能優化的。這時,我們可以用 CDN 的方式引入這樣外掛或者 UI 元件庫。
-
在
index.html
引入相應 cdn 連結
<head>
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/iview/3.5.4/styles/iview.css" />
</head>
<body>
<div id="app"></div>
<script src="https://cdn.bootcss.com/vue/2.6.8/vue.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/iview/3.5.4/iview.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.16.8/xlsx.mini.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.16.8/cpexcel.min.js"></script>
</body>
-
vue.config.js
配置externals
configureWebpack: (config) => {
config.externals = {
vue: 'Vue',
xlsx: 'XLSX',
iview: 'iView',
iView: 'ViewUI',
}
}
-
刪除之前的引入方式並解除安裝相應 npm 依賴包
npm uninstall vue iview echarts xlsx --save
此時我們在來看一下打包後的情況
打包出來的 js 檔案
well done ! 這時基本沒有打包出大檔案了,首頁載入需要的 vendor.js
也只有幾十 kb,而且我們還可以進一步優化,就是把 vue 全家桶的一些模組再通過 cdn 的方法引入,比如 vue-router
,vuex
,axios
等。這時頁面特別是首頁載入的效能就得到大大地優化了。
相關文章
- webpack模組非同步載入原理解析Web非同步
- ???由淺至深瞭解webpack非同步載入背後的原理Web非同步
- webpack懶載入程式碼原理深究Web
- webpack,非同步載入,程式碼分割,require.ensureWeb非同步UI
- Webpack模組載入器Web
- Webpack實戰-載入SVGWebSVG
- 非阻塞載入指令碼指令碼
- echarts非同步載入Echarts非同步
- dhtmlXTree非同步載入HTML非同步
- webpack原理Web
- Webpack按需載入秒開應用Web
- 腦闊疼的webpack按需載入Web
- webpack配置常用loader載入器Web
- 同步非同步,阻塞非阻塞非同步
- 非同步、同步、阻塞、非阻塞非同步
- 同步、非同步、阻塞、非阻塞非同步
- 按需載入原理分析
- tornado原理介紹及非同步非阻塞實現方式非同步
- AssetBoundle載入非預設資源
- 同步非同步 與 阻塞非阻塞非同步
- 理解阻塞、非阻塞、同步、非同步非同步
- 同步、非同步,阻塞、非阻塞理解非同步
- 同步、非同步、阻塞與非阻塞非同步
- 同步、非同步、阻塞和非阻塞非同步
- Javascript非同步載入詳解JavaScript非同步
- [iOS]非同步載入UIImageView—-AsyImageViewiOS非同步UIView
- express製作小型熱載入打包webpackExpressWeb
- webpack系列--淺析webpack的原理Web
- 單步除錯理解webpack裡通過require載入nodejs原生模組實現原理除錯WebUINodeJS
- 關於懶載入原理
- 圖片懶載入原理
- 前端效能優化——延遲載入和非同步載入前端優化非同步
- 滾動載入圖片(懶載入)實現原理
- [轉]阻塞/非阻塞與同步/非同步非同步
- 同步與非同步 阻塞與非阻塞非同步
- Webpack 原理淺析Web
- Webpack 模組打包原理Web
- webpack原理解析Web