一佛致、明確概念
◆ Code Splitting(代碼分離)
Code Splitting 是 webpack 作為打包工具的核心特征派撕。它通過分離點(diǎn)(邏輯斷點(diǎn))的形式將代碼拆分成Chunk,在分離點(diǎn)中依賴的模塊會被打包到一起,實(shí)現(xiàn)異步加載。一個(gè)分離點(diǎn)會產(chǎn)生一個(gè)打包文件疲扎,從而減小資源體積,縮短加載時(shí)間捷雕。webpack 中文指南#代碼分離 對此概念進(jìn)行了詳細(xì)的介紹椒丧,指出三種最常用的代碼分離方法:
- 入口起點(diǎn):使用 entry 配置手動(dòng)地分離代碼。
- 防止重復(fù):使用 CommonsChunkPlugin 去重和分離 chunk救巷,用于分離第三方庫壶熏。
- 動(dòng)態(tài)導(dǎo)入:通過模塊的內(nèi)聯(lián)函數(shù)調(diào)用來分離代碼。
另外浦译,一些有助于代碼分離的插件和 loaders:
- ExtractTextPlugin: 用于將 CSS 從主應(yīng)用程序中分離棒假。
- bundle-loader: 用于分離代碼和延遲加載生成的 bundle。
- promise-loader: 類似于 bundle-loader精盅,但是使用的是 promises帽哑。
◆ 按需加載(或懶加載)
Code Splitting 僅僅是分離了代碼,仍然會提前加載完所有模塊叹俏。它雖然減小了應(yīng)用的總體體積祝拯,在控制資源加載優(yōu)先級的情況下,可以加快應(yīng)用的加載速度(首頁渲染)她肯,但是也同時(shí)也增加了 http 請求數(shù),并非真正意義上的按需加載鹰贵。不過晴氨,Code Splitting 卻為按需加載提供了很好的途徑。
要實(shí)現(xiàn)真正意義上的按需加載(或懶加載)碉输,就需要結(jié)合用戶的交互:當(dāng)只有在用戶訪問某個(gè)功能的時(shí)候才加載該部分的代碼籽前,沒有用到的代碼塊可能永遠(yuǎn)都不會被加載,這樣就可以更好地提升性能敷钾。
二枝哄、在 React 中實(shí)現(xiàn)按需加載
一般,在使用 webpack 時(shí)阻荒,我們已經(jīng)進(jìn)行了代碼分離:抽離 css挠锥,抽離第三方庫到 vendor.js
中,而將自己寫的源碼打包到 bundle.js
中侨赡,如下圖中給出了對webpack.config.js
的基礎(chǔ)配置:
而對于 bundle.js
蓖租,還能再通過 Code Splitting 進(jìn)行優(yōu)化粱侣。在 React 中,一個(gè)路由就是一個(gè)組件蓖宦,因而從路由層面進(jìn)行代碼分離是一個(gè)很容易的切入點(diǎn)齐婴。這里,我們使用 webpack 的 bundle loader (Code Splitting的一種方式)和 <Bundle> 組件來實(shí)現(xiàn)在 React 中的按需加載稠茂。
1. 首先柠偶,安裝 bundle-loader
npm i --save bundle-loader
2. 實(shí)現(xiàn) <Bundle> 組件
import React, { Component } from 'react'
class Bundle extends Component {
state = {
// short for "module" but that's a keyword in js, so "mod"
mod: null
}
componentWillMount() {
this.load(this.props)
}
componentWillReceiveProps(nextProps) {
if (nextProps.load !== this.props.load) {
this.load(nextProps)
}
}
load(props) {
this.setState({
mod: null
})
props.load((mod) => {
this.setState({
// handle both es imports and cjs
mod: mod.default ? mod.default : mod
})
})
}
render() {
return this.state.mod ? this.props.children(this.state.mod) : null
}
}
export default Bundle
由上述的實(shí)現(xiàn),我們可以看到整個(gè)加載的實(shí)現(xiàn)過程:
- webpack
bundle loader
通過load
參數(shù)的方式傳遞給 Bundle睬关; - 當(dāng) Bundle 加載完或者接收到新的
prop
的時(shí)候诱担,就會調(diào)用load
,然后在回調(diào)函數(shù)中設(shè)置 Bundle 的狀態(tài)值共螺; - 最后该肴,渲染模塊。
3. 結(jié)合 bundle-loader
和 <Bundle>
實(shí)現(xiàn)按需加載
// 通過 `bundle-loader` 懶加載方式引入需要按需加載的模塊
import loadSomething from 'bundle-loader?lazy!./Something'
// 范式
<Bundle load={loadSomething}>
{(mod) => (
// do something with the module
)}
</Bundle>
// 例子:如果 module 是個(gè) component
<Bundle load={loadSomething}>
{(Comp) => (Comp
? <Comp/>
: <Loading/>
)}
</Bundle>
三藐不、使用 bundle loader
相比于使用 import()
的優(yōu)勢
先來看看 import()
方式:
// 同步的 synchronous
import React from 'react';
import { tools } from '../js/util';
// 異步的 asynchronous
const AsyncModulePromise = import ('../js/lib/Module);
import()
已經(jīng)進(jìn)入了 stage 3匀哄,它接收動(dòng)態(tài)模塊名,返回的是一個(gè) Promise雏蛮,需要引入 Babel 插件:syntax-dynamic-import
涎嚼。
// a.js
const a = () => console.log("hello I'm a!");
export default a;
// b.js
const b = () => console.log("Hello I'm b!");
export default b;
// index.js
import a from './a';
a(); // hello I'm a!
const bPromise = import('./b');
bPromise.then(b => {
b.default(); // hello I'm b!
})
.catch(e => console.error(e));
兩者都是動(dòng)態(tài)引入的方式,當(dāng)渲染的時(shí)候才加載代碼挑秉。import
動(dòng)態(tài)加載部分實(shí)踐的時(shí)候有報(bào)錯(cuò)法梯,估計(jì)是配置問題,還待研究犀概。立哑。。具體可參考:ECMAScript 6 入門#import姻灶。
在 React 中铛绰,將 import()
的運(yùn)用結(jié)合到了 <Bundle>
組件中,這樣做产喉,有一個(gè)極大的優(yōu)勢:(附上原文捂掰,求大神解釋:Another HUGE benefit of bundle loader is that the second time it calls back synchronously, which prevents flashing the loading screen every time you visit a code-split screen.)
4. 加載效果展示
在按需加載之前:
在按需加載之后:
可以看到,當(dāng)使用 Code Splitting 之后曾沈,相應(yīng)的模塊只有在需要(如點(diǎn)擊)的時(shí)候才會被加載这嚣。
ps:當(dāng)然這個(gè)例子中,1.bundle.js
和2.bundle.js
的體積相對于bundle.js
幾乎可以忽略不計(jì)(因?yàn)檫@個(gè)原因塞俱,我還特意增加了模塊中的代碼量姐帚,才使得兩次bundle.js
有了小數(shù)點(diǎn)的變化~/笑哭),但是障涯,在大型項(xiàng)目中卧土,按需加載確實(shí)是一個(gè)提升性能很好的優(yōu)化點(diǎn)惫皱。