国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

分享一款超好用的JavaScript 打包壓縮工具

瀏覽:3日期:2023-06-21 14:52:12

背景

平時(shí)大家在開(kāi)發(fā) Js 項(xiàng)目的時(shí)候,可能已經(jīng)離不開(kāi) webpack 等打包工具了。而 webpack 打包速度大概就是“能用“的水平。大概去年開(kāi)始,我就開(kāi)始在構(gòu)想,如果能寫(xiě)一個(gè)極速的打包工具,功能未必需要很強(qiáng),可能對(duì)小項(xiàng)目非常有用。去年我用 C++ 寫(xiě)完 parser 之后,便沒(méi)什么動(dòng)力寫(xiě)下去了。但是最近發(fā)現(xiàn)有這個(gè)想法的不止我一個(gè),F(xiàn)igma 的 CTO 業(yè)余之際寫(xiě)了一個(gè)打包器 https://github.com/evanw/esbuild ,可以說(shuō)完完全全實(shí)現(xiàn)了我想象中的需求,不過(guò)他是用 Go 語(yǔ)言實(shí)現(xiàn)的。我看到這個(gè)項(xiàng)目時(shí)心里一想,這不是我去年就想做的事嗎,這 push 我趕緊把打包壓縮部分完成。

代碼

Github 地址: https://github.com/vincentdchan/jetpack.js

優(yōu)化思路

并行 Parsing

毫無(wú)疑問(wèn),每一個(gè) js 文件的 parsing 可以在不同線(xiàn)程完成,這就需要支持并行的語(yǔ)言。由于 parsing 的結(jié)果是 AST,所以需要可以共享內(nèi)存的語(yǔ)言(排除通過(guò) messeage parsing 實(shí)現(xiàn)多線(xiàn)程的語(yǔ)言)。滿(mǎn)足以上兩個(gè)要求的語(yǔ)言不多。 Evan 選擇了 Go,我選擇了 C++。

減少遍歷次數(shù)

要想速度快,就要減少 AST 的遍歷次數(shù)。最好就是只遍歷一次來(lái)生成代碼,在 Parsing 構(gòu)建 AST 的時(shí)候就收集足夠的信息。但是這也意味著只能做比較淺層次的優(yōu)化,不能做深層次的壓縮(死代碼消除,tree shaking 都做不了)。

架構(gòu)

由上述思路我總結(jié)出了以下打包的架構(gòu):

并行 parse 文件 作用域提升、生成框架代碼、重命名變量 并行生成代碼 合并輸出文件

流程圖如下:

分享一款超好用的JavaScript 打包壓縮工具

打包壓縮原理

本章節(jié)主要講如何“最簡(jiǎn)單“地壓縮 Js 代碼。本章節(jié)假設(shè)讀者對(duì)編譯原理有一定了解,知道什么是 AST。如果不懂請(qǐng)直接跳到下文「性能」章節(jié)。

字面量替換

字面替換最簡(jiǎn)單。規(guī)則有一下幾個(gè):

undefined 替換為 void 0 true 替換為 !0 , false 替換為 !1

:warning: 注意:在 ES 中,undefined 是標(biāo)識(shí)符(Identifier),而不是關(guān)鍵字,也就是說(shuō)你可以定義一個(gè)叫 undefined 的變量,所以這個(gè)時(shí)候不能簡(jiǎn)單地替換為 void 0

常量折疊

計(jì)算簡(jiǎn)單的運(yùn)算:

var two = 1 + 1;var foobar = ’foo’ + ’bar’;

轉(zhuǎn)換成

var two = 2;var foobar = ’foobar’;

:warning: 注意:這里要注意實(shí)現(xiàn)的平臺(tái)和 js 的差異,比如在 C++ 里面大整數(shù)相加可能會(huì)溢出,而在 Js 會(huì)自動(dòng)轉(zhuǎn)換成 bigint. 加法問(wèn)題就如此,其他運(yùn)算符問(wèn)題更多。如果要完整實(shí)現(xiàn)常量折疊,可能要部分實(shí)現(xiàn) js 引擎。

變量別名

別名就是要給變量重新賦予比較短的變量名。從字母一直排上去,abcd,一個(gè)字母用完了用兩個(gè)字母。實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,用一個(gè)計(jì)數(shù)器,一直加上去就可。最后每個(gè)變量分配一個(gè)數(shù)字,把這個(gè)數(shù)字映射到相應(yīng)的英文字母上,有點(diǎn)像 36 進(jìn)制轉(zhuǎn)換成字母的面試題。不過(guò)這里有一點(diǎn)值得注意的是,變量名第一個(gè)字母不能是數(shù)字,第二個(gè)字母開(kāi)始可以是數(shù)字,要考慮到這一點(diǎn),才能盡可能“壓榨”變量名。

為了盡可能地“壓榨”變量名,同一級(jí)的作用域里面的變量名是可以使用相同的變量名。到下一級(jí)的時(shí)候,對(duì)子作用域進(jìn)行合并。

舉個(gè)例子:

function Mother() {var e = ’capture’; // d 不能使用跟子作用域同樣的變量名,不然子作用域無(wú)法捕獲這個(gè)變量function A(a, b, c, d) { console.log(e);}function B(a, b, c) { // B 跟 A 函數(shù)同級(jí),分配同樣的變量名 // ...}}

上述例子中,A 和 B 都沒(méi)有子作用域了,變量名從 0 開(kāi)始分配。到給 Mother 下 e 分配變量名時(shí),找到子作用域最大的計(jì)數(shù)器。分配最多的子作用域 A 分配了 4 個(gè),所以 B 計(jì)數(shù)器從 5 開(kāi)始分配,所以給 e 分配了5,所以 e 就得到了這個(gè)名字。

所以變量別名就是從 AST 的葉子開(kāi)始向上構(gòu)造,一直分配到根結(jié)點(diǎn)把所有作用域都分配完為止。

小技巧

這里 esbuild 采用了比較聰明的技巧。它統(tǒng)計(jì)了所有變量的引用次數(shù),然后進(jìn)行排序,引用次數(shù)最多的變量分配到的名字就是盡量短的,這樣也可以減少編譯出來(lái) js 的體積。我在寫(xiě) jetpack 打包的時(shí)候,也借鑒了這種做法。

模塊合并

模塊合并的辦法有很多。webpack 采用的是用 function 把每個(gè)函數(shù)包起來(lái),放到了一個(gè)長(zhǎng)長(zhǎng)的數(shù)組里面,然后實(shí)現(xiàn)了自己的 require,esbuild 也采用了類(lèi)似的方法。

Rollup.js 實(shí)現(xiàn)的方法則是作用域提升(Scope hoisting),把模塊都放到根作用域。這里我采用的方法也是作用域提升。

假設(shè)有 a.js 文件:

export function A() { console.log(’a’);}

然后有 main.js 文件:

import { A as ExternalA } from ’./a’;function A() { console.log(’local A’);}export function main() { A() + ExternalA();}

使用 jetpack 打包完的結(jié)果:

// a.jsfunction A() { console.log(’a’);}// main.jsfunction A_0() { console.log(’local A’);}function main() { A_0() + A();}export { main };

難點(diǎn)在于作用域合并。實(shí)際上在 ES modules 里面不同 modules 之間引用是一個(gè)圖結(jié)構(gòu)。

C++ 的優(yōu)化

除了策略上的優(yōu)化,C++ 還提供了諸多基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)/內(nèi)存方面的優(yōu)化。

shared_ptr

AST 的結(jié)點(diǎn)全部使用 shared_ptr,有人可能認(rèn)為這是一個(gè)很大的開(kāi)銷(xiāo)。但是早期的時(shí)候我實(shí)現(xiàn)過(guò)一個(gè)裸指針版本(不釋放內(nèi)存),并沒(méi)有測(cè)出有明顯差距。

使用 shared ptr 很重要一個(gè)原因是,一個(gè)子樹(shù)可能被其他類(lèi)擁有(打包模塊,Scope,ES Module 管理器)。這個(gè)時(shí)候如果用 unique ptr 的話(huà)就會(huì) gg。只能說(shuō) GC 大法好。

對(duì)于 C++ 這種沒(méi)有 GC 的語(yǔ)言有一個(gè)毛病就是:析構(gòu) AST 非常耗時(shí)。AST 夠大的話(huà)能耗上十幾 ms(這個(gè)時(shí)間跟 gc 比有何優(yōu)勢(shì)?),所以因此我也能想出了一個(gè)辦法: 不釋放內(nèi)存 ……。

最后說(shuō)一句: GC 大法好

robin hood hashing

由于打包器中大量使用哈希表,所以提高哈希表速度尤其重要,這里我使用了 robin hood hashing

參見(jiàn): https://martin.ankerl.com/2019/04/01/hashmap-benchmarks-01-overview/

在 hash 方面我有一個(gè)設(shè)想,就是像 Lua 一樣,對(duì)于短字符,在字符串創(chuàng)建的時(shí)候把 hash 記下來(lái),這樣在多次使用哈希表的時(shí)候可以節(jié)省 hash 的時(shí)間(但是要求字符串是 immutable 的)。為此我專(zhuān)門(mén)寫(xiě)了個(gè) String 類(lèi),最后的結(jié)果是總體速度慢了 2-3x,測(cè)出來(lái)是 immutable 字符串拼接耗時(shí)太多,最后放棄了這個(gè)方案。

jemalloc

Parsing 過(guò)程中需要大量分配 node,大家都知道很明顯 C++ 的 new 并不夠快。經(jīng)過(guò)測(cè)試在 macOS 下使用 jemalloc 會(huì)讓 parsing 速度提升 1 倍。使 用系統(tǒng) malloc 會(huì)導(dǎo)致 parsing 速度比 Go 慢 1x 左右,慢在 new 。

當(dāng)然了,內(nèi)存池我也試過(guò)的,測(cè)出來(lái)速度基本和 jemalloc 一樣,所以就直接用 jemalloc 了。

性能

分享一款超好用的JavaScript 打包壓縮工具

總結(jié)

寫(xiě)編譯器需要快速大量產(chǎn)生 node 結(jié)點(diǎn),大量樹(shù)和圖的結(jié)構(gòu),這一方面的運(yùn)算 C++ 并沒(méi)有什么優(yōu)勢(shì)可言。

不得不承認(rèn),使用 C++ 你要思考很多東西,做很多很多額外的工作,才能獲得比 Go 還快的速度(什么都不想做出來(lái)只會(huì)比 Go 還慢)。另一方面使用 C++ 會(huì)讓你額外考慮很多和業(yè)務(wù)無(wú)關(guān)的東西,大大降低開(kāi)發(fā)速度,而對(duì)于打包器這個(gè)場(chǎng)景 C++ 在這一塊本身不能提供很大優(yōu)勢(shì)。

到此這篇關(guān)于寫(xiě)一個(gè)飛快的 JavaScript 打包壓縮工具的文章就介紹到這了,更多相關(guān)JavaScript 打包壓縮工具內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: JavaScript
相關(guān)文章:
主站蜘蛛池模板: 欧美级毛片 | 免费播放特黄特色毛片 | 免费v片在线看 | 日韩在线 中文字幕 | 亚洲精品成人网 | 国产精品99精品久久免费 | 九九全国免费视频 | 操操网站 | 国产亚洲福利 | 亚洲成aⅴ人在线观看 | 久久久久网站 | 亚洲天堂国产精品 | 亚洲精品国产成人一区二区 | 成人区精品一区二区毛片不卡 | 国产永久精品 | 暖暖在线精品日本中文 | 精品在线观看视频 | 国产毛片一区二区三区精品 | 久久99国产亚洲精品 | 亚洲精品综合一区二区三区 | 国产一区在线观看免费 | 91国偷自产一区二区三区 | 国产精品不卡 | 欧美性色一级在线观看 | 成人久久18免费网 | 中文字幕一区二区在线视频 | 国产精品手机在线亚洲 | 日韩不卡在线 | 波多野结衣在线观看高清免费资源 | 欧美经典成人在观看线视频 | 亚洲爱爱爱 | 欧美一区二区三区久久久人妖 | 成人网18免费网站在线 | 亚洲天堂免费在线视频 | 女人张开腿等男人桶免费视频 | 中文字幕一区二区三区免费视频 | 日韩经典中文字幕 | 在线久草 | 亚洲精品国产拍拍拍拍拍 | 国产精品九九免费视频 | 国产成人久久综合热 |