webpack分包
- 前言
- 一、多入口打包
- 二、提取公共子產品
- 三、動态導入子產品
- 四、魔法注釋
前言
通過webpack實作前端項目整體子產品化的優勢固然很明顯,但是它同樣存在一些弊端,那就是我們項目中的所有代碼最終都被打包到了一起,如果我們應用非常複雜,子產品非常多的話,我們的打包結果就會特别的大。
而事實情況是,大多數時候,我們在應用開始工作時,并不是我們所有的子產品都需要加載進來的,但是這些子產品又被全部打包到一起,我們需要任何一個子產品,都需要把整體加載過後才能使用,而我們的應用有一般是運作在浏覽器端,那就意味着我們會浪費掉很多的流量和帶寬。
那更合理的方案就是把我們的打包結果按照一定的規則去分離到多個bundle中,然後根據我們應用的運作需要,按需加載這些子產品。這樣我們就能大大提高應用的響應速度與運作效率。
目前webpack實作分包的方式主要有以下兩種:
第一種是根據我們的業務去配置不同的打包入口,也就是我們會有多個打包入口同時打包,輸出多個打包結果。
第二種就是采用ESM的動态導入功能,去實作子產品的按需加載,這個時候webpack會把我們動态加載的子產品單獨輸出到一個bundle中。
一、多入口打包
現有如下檔案結構:
分為album頁面内容、index.html頁面内容、global.css公共樣式、fetch.js是一個公共的提供請求api的子產品,嘗試為這個案例建立多個打包入口。
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
mode:"none",
entry:{ // 将entry配置成一個對象,來設定多個打包入口
index:"./src/index.js",
album:"./src/album.js"
},
output:{// 修改輸出檔案名
filename:"[name].bundle.js" // 通過[name]這種占位符的方式動态輸出檔案名,[name]最終就會替換成打包入口名稱
},
module:{
rules:[
{
test:/\.css$/,
use:[
"style-loader",
"css-loader"
]
}
]
},
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:"Multi Entry",
template:"./src/index.html",
filename:"index.html"
}),
new HtmlWebpackPlugin({
title:"Multi Entry",
template:"./src/album.html",
filename:"album.html"
})
]
}
打包結果:
但是生成的html中會載入兩個打包後的bundle.js,我們隻希望其引入自身的bundle.js
在HtmlWebpackPlugin通過chunks屬性指定載入的bundle.js檔案
new HtmlWebpackPlugin({
title:"Multi Entry",
template:"./src/index.html",
filename:"index.html",
chunks:['index']
}),
new HtmlWebpackPlugin({
title:"Multi Entry",
template:"./src/album.html",
filename:"album.html",
chunks:['album']
})
此時再打包,生成的html就隻會載入自身需要的bundle.js了。
二、提取公共子產品
在index.js和alubm.js中都有對公共子產品的引入
import fetchApi from './fetch'
import './global.css'
import './index.css'
可以将這些公共子產品提取出來,提取的辦法也很簡單,在webpack.config.js中添加如下配置:
optimization: {
splitChunks: {
chunks: 'all'
}
}
然後執行打包就會生成一個包含這些公共子產品的js檔案。但是我這沒有生成,很頭疼。找了很久,結果發現需要加上一個name屬性。
optimization: {
splitChunks: {
chunks: 'all',
name: 'common'
}
}
這樣就能成功将公共子產品的js檔案提取出來了,生成了一個common.bundle.js。
三、動态導入子產品
假設我們有一個單頁面應用,根據需求來載入子產品。
// posts子產品
export default () => {
const posts = document.createElement('div')
posts.className = 'posts'
posts.innerHTML = '<h2>Posts</h2>'
return posts
}
import posts from './posts/posts'
import album from './album/album'//結構與posts一樣
const render = () => {
const hash = window.location.hash || '#posts'
const mainElement = document.querySelector('.main')
mainElement.innerHTML = ''
if (hash === '#posts') {
mainElement.appendChild(posts())
} else if (hash === '#album') {
mainElement.appendChild(album())
}
}
render()
window.addEventListener('hashchange', render)
我們在打包入口中同時導入了ablum子產品和posts子產品,當錨點發生變化時,根據錨點的值去決定要顯示哪個元件,這裡就會存在浪費的可能性,如果使用者打開應用後隻是通路了其中一個頁面,另外一個頁面所對應的子產品的加載就是浪費,是以采用動态導入的方式就不會有浪費了。
動态導入就是采用ES Module的動态導入。
const render = () => {
const hash = window.location.hash || '#posts'
const mainElement = document.querySelector('.main')
mainElement.innerHTML = ''
if (hash === '#posts') {
import('./posts/posts').then(({ default: posts }) => {
mainElement.appendChild(posts())
})
} else if (hash === '#album') {
import('./album/album').then(({ default: album }) => {
mainElement.appendChild(album())
})
}
}
render()
執行打包,打包結果如下:
dist目錄中多出了兩個bundle.js,這兩個bundle.js就是由動态導入自動分包所産生的。
整個過程我們無需配置任何地方,隻需要按照ES Module動态導入的方式去導入子產品就可以了,webpack内部會自動處理分包和按需加載。
四、魔法注釋
預設都過動态導入産生的bundle檔案,它們的名稱就隻是一個序号,如果我們需要給這些bundle命名的話,我們可以使用webpack所特有的魔法注釋來實作。
具體使用如下:
if (hash === '#posts') {
import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {
mainElement.appendChild(posts())
})
} else if (hash === '#album') {
import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) => {
mainElement.appendChild(album())
})
}
執行打包,打包結果:
如果命名一緻,會打包到一個檔案中,否則是多個檔案。