
前言
最近有在使用 highlight.js 做代碼的高亮展示,主要是展示對(duì) SQL 語言的處理。看了看 highlight.js 的提供的相關(guān)代碼

因?yàn)橹恍枰虞d對(duì)應(yīng)語言的種類,以及一種樣式,所以我們希望 webpack 能夠按需加載
按需加載的實(shí)踐
完全加載
為了對(duì)比出按需加載究竟能幫助我們節(jié)約多少資源,我們先貼出沒有按需加載的代碼
// 忽略一些無關(guān)的代碼
import * as hljs from 'highlight.js/lib/highlight'
import 'highlight.js/styles/atom-one-light.css'
export class Highlight extends React.Component {
public componentDidMount() {
hljs.highlightBlock((this.code as any))
}
public render() {
return (
<pre ref={ref => this.code = ref} style={{marginTop: 20}}>
<code>{this.props.content}</code>
</pre>
)
}
}
這是一份完整的加載,我們看看最后的數(shù)據(jù)有多大(包含完整引用的 antd 文件,我在項(xiàng)目中使用了 antd )

按需加載
接著我們按照官方的 demo 實(shí)現(xiàn)按需加載
import * as hljs from 'highlight.js/lib/highlight'
import * as javascript from 'highlight.js/lib/languages/javascript'
hljs.registerLanguage('javascript', javascript)
其他的部分和上文相同,區(qū)別在于,沒有從整個(gè) highlight 中加載,而是引用了部分文件以及需要注冊(cè)的 javascript 語言部分,默認(rèn)是加載包含所有語言版本的 hljs ,看看這下的打包大小

我們可以看到,使用按需加載將近節(jié)省了600KB的空間,而使用按需加載的引入方式是
import * as XXX from 'module/lib/xxx'。并且使用 import { xx } from 'moduls' 并不能觸發(fā) webpack 的 treeshake,webpack仍然會(huì)打包完整庫,哪怕引用的僅僅是從庫里導(dǎo)出的接口(在ECharts下是如此表現(xiàn)的)。我們看看按需引用 antd 里的組件會(huì)是什么情況
部分按需引用
上面1.78MB的打包體積是 import { Card } from 'antd'(如gif效果圖,我用Card包裹了高亮組件),接著我們看看
import Card from 'antd/lib/card'
這種方式最后的打包體積

媽耶,居然這么小。
小結(jié)
- 如果要實(shí)現(xiàn)按需加載得使用babel-plugin-import,這個(gè)在TS下的情況還沒有檢查過
- 使用TS時(shí),因?yàn)槟承斓?d.ts 文件 指向的路徑是模塊,因此要導(dǎo)入該庫的接口只能完整的導(dǎo)入該模塊,比如ECharts,這個(gè)問題目前暫時(shí)還未解決
動(dòng)態(tài)加載的實(shí)踐
上面只是按需加載部分的JS,并且通過字符串寫死的方式指定了路徑,還有一部分,如同CSS的部分需要在組件生成時(shí)動(dòng)態(tài)加載,或者通過變量的形式加載。如下所示
constructor(props) {
super(props)
require('highlight.js/styles/' + this.props.css)
}
static async getDerivedStateFromProps(nextProps) {
// const css = await import('highlight.js/styles/' + nextProps.css)
const css = require('highlight.js/styles/' + nextProps.css)
console.log(css)
return null
}
我們?cè)跇?gòu)造階段通過props傳過來的變量加載對(duì)應(yīng)的CSS文件,之前是使用import 'highlight.js/styles/atom-one-light.css'的方式,我們看看兩者打包體積的區(qū)別


通過指定加載的CSS體積大小是427KB,而動(dòng)態(tài)加載的體積大小是484KB。動(dòng)態(tài)加載的體積要比靜態(tài)加載的體積大很多。分析一下webpack打包的行為
webpack始終結(jié)合關(guān)鍵字并按照靜態(tài)地址信息進(jìn)行打包。比如require('highlight.js/styles/' + nextProps.css)
require是關(guān)鍵字,接下來 webpack 會(huì)對(duì) require 這個(gè)函數(shù)中的入?yún)⑦M(jìn)行分析,它會(huì)發(fā)現(xiàn)入?yún)⒂袃蓚€(gè)部分構(gòu)成, 一部分是硬編碼的 'highlight.js/styles/' 另一部分是不可知的變量。webpack將會(huì)以硬編碼部分為打包入口,將'highlight.js/styles/*'下所有文件打包,在運(yùn)行時(shí)根據(jù)完整的路徑記載資源。
所以我們沒辦法使用完全的變量 require(variable),因?yàn)檫@樣webpack找不到打包的路徑。
缺陷
效果圖雖然能看到我們通過 Select 的選擇按需加載 CSS 樣式,但其實(shí)是有缺陷的,表現(xiàn)為右側(cè)可以看到,動(dòng)態(tài)加載的CSS是通過一個(gè)個(gè)style標(biāo)簽加載上去的,這樣后面的樣式效果會(huì)覆蓋前面的。表現(xiàn)為 當(dāng) Select 又選到已經(jīng)加載的樣式時(shí), 瀏覽器并不會(huì)重新加載那段代碼,導(dǎo)致樣式無效。這個(gè)問題在另一個(gè)組件中得到了解決
react-syntax-highlighter
還沒來得及看具體的實(shí)現(xiàn),不過我估計(jì)應(yīng)該是使用了 CSS-MODULES,明天再看看
沒來得及驗(yàn)證的部分
有注意到 我在使用 const css = await import('xxx'),const css = require('xxx'),這兩者的表現(xiàn)上是有區(qū)別的,前者是一個(gè)Promise對(duì)象,后者直接返回了值,這就涉及到了一個(gè)同步和異步的問題,雖然最后打印出來都是 {}, 不過這是因?yàn)闆]有使用CSS modules的原因。以后再研究研究 import require 動(dòng)態(tài)加載時(shí)的區(qū)別
總結(jié)
-
import { Card } from 'antd'并不會(huì)觸發(fā)按需加載,仍然會(huì)加載全部antd文件,應(yīng)該使用import Card from 'antd/lib/Card' - 使用變量加載
require('highlight.js/styles/' + this.props.style)webpack會(huì)打包'highlight.js/styles/*'下所有文件 - 猜想 在TS下即使只從某個(gè)庫里引用接口,
import { IXxx } from 'xxx',webpack仍然會(huì)打包所有的 'xxx' 文件(在ECharts的表現(xiàn)下如此)
以上都是我瞎編的