前言
最近有在使用 highlight.js 做代碼的高亮展示,主要是展示對 SQL 語言的處理粗悯。看了看 highlight.js 的提供的相關代碼
因為只需要加載對應語言的種類,以及一種樣式萧豆,所以我們希望 webpack 能夠按需加載
按需加載的實踐
完全加載
為了對比出按需加載究竟能幫助我們節(jié)約多少資源,我們先貼出沒有按需加載的代碼
// 忽略一些無關的代碼
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 文件祝拯,我在項目中使用了 antd )
按需加載
接著我們按照官方的 demo 實現(xiàn)按需加載
import * as hljs from 'highlight.js/lib/highlight'
import * as javascript from 'highlight.js/lib/languages/javascript'
hljs.registerLanguage('javascript', javascript)
其他的部分和上文相同,區(qū)別在于温算,沒有從整個 highlight 中加載怜校,而是引用了部分文件以及需要注冊的 javascript 語言部分,默認是加載包含所有語言版本的 hljs 注竿,看看這下的打包大小
我們可以看到茄茁,使用按需加載將近節(jié)省了600KB的空間,而使用按需加載的引入方式是
import * as XXX from 'module/lib/xxx'
巩割。并且使用 import { xx } from 'moduls'
并不能觸發(fā) webpack 的 treeshake裙顽,webpack仍然會打包完整庫,哪怕引用的僅僅是從庫里導出的接口(在ECharts下是如此表現(xiàn)的)宣谈。我們看看按需引用 antd 里的組件會是什么情況
部分按需引用
上面1.78MB的打包體積是 import { Card } from 'antd'
(如gif效果圖愈犹,我用Card包裹了高亮組件),接著我們看看
import Card from 'antd/lib/card'
這種方式最后的打包體積
媽耶,居然這么小闻丑。
小結(jié)
- 如果要實現(xiàn)按需加載得使用babel-plugin-import,這個在TS下的情況還沒有檢查過
- 使用TS時漩怎,因為某些庫的 d.ts 文件 指向的路徑是模塊勋颖,因此要導入該庫的接口只能完整的導入該模塊,比如ECharts勋锤,這個問題目前暫時還未解決
動態(tài)加載的實踐
上面只是按需加載部分的JS饭玲,并且通過字符串寫死的方式指定了路徑,還有一部分怪得,如同CSS的部分需要在組件生成時動態(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
}
我們在構(gòu)造階段通過props傳過來的變量加載對應的CSS文件徒恋,之前是使用import 'highlight.js/styles/atom-one-light.css'
的方式蚕断,我們看看兩者打包體積的區(qū)別
通過指定加載的CSS體積大小是427KB,而動態(tài)加載的體積大小是484KB入挣。動態(tài)加載的體積要比靜態(tài)加載的體積大很多亿乳。分析一下webpack打包的行為
webpack始終結(jié)合關鍵字并按照靜態(tài)地址信息進行打包。比如require('highlight.js/styles/' + nextProps.css)
require是關鍵字径筏,接下來 webpack 會對 require 這個函數(shù)中的入?yún)⑦M行分析葛假,它會發(fā)現(xiàn)入?yún)⒂袃蓚€部分構(gòu)成, 一部分是硬編碼的 'highlight.js/styles/' 另一部分是不可知的變量滋恬。webpack將會以硬編碼部分為打包入口聊训,將'highlight.js/styles/*'下所有文件打包,在運行時根據(jù)完整的路徑記載資源恢氯。
所以我們沒辦法使用完全的變量 require(variable)带斑,因為這樣webpack找不到打包的路徑。
缺陷
效果圖雖然能看到我們通過 Select 的選擇按需加載 CSS 樣式勋拟,但其實是有缺陷的勋磕,表現(xiàn)為右側(cè)可以看到,動態(tài)加載的CSS是通過一個個style標簽加載上去的敢靡,這樣后面的樣式效果會覆蓋前面的挂滓。表現(xiàn)為 當 Select 又選到已經(jīng)加載的樣式時, 瀏覽器并不會重新加載那段代碼啸胧,導致樣式無效赶站。這個問題在另一個組件中得到了解決
react-syntax-highlighter
還沒來得及看具體的實現(xiàn),不過我估計應該是使用了 CSS-MODULES吓揪,明天再看看
沒來得及驗證的部分
有注意到 我在使用 const css = await import('xxx')
,const css = require('xxx')
,這兩者的表現(xiàn)上是有區(qū)別的亲怠,前者是一個Promise對象,后者直接返回了值柠辞,這就涉及到了一個同步和異步的問題,雖然最后打印出來都是 {}主胧, 不過這是因為沒有使用CSS modules的原因叭首。以后再研究研究 import require 動態(tài)加載時的區(qū)別
總結(jié)
-
import { Card } from 'antd'
并不會觸發(fā)按需加載,仍然會加載全部antd文件,應該使用import Card from 'antd/lib/Card'
- 使用變量加載
require('highlight.js/styles/' + this.props.style)
webpack會打包'highlight.js/styles/*'
下所有文件 - 猜想 在TS下即使只從某個庫里引用接口,
import { IXxx } from 'xxx'
,webpack仍然會打包所有的 'xxx' 文件(在ECharts的表現(xiàn)下如此)
以上都是我瞎編的