你構建的代碼為什么這么大?如何優化~

大家好,我是若川。我持續組織了近一年的源碼共讀活動,感興趣的可以?點此掃碼加我微信?lxchuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信進群。

7f643631344c81a18090eefdc3cefb46.jpeg

掃碼加我微信 lxchuan12、拉你進源碼共讀


本文作者:文西

前言

代碼體積的控制對前端來說至關重要,盡管網絡條件逐漸變好,但是代碼體積的增加不僅僅只影響資源加載速度,還會直接或間接影響瀏覽器各類性能指標。

例如增加用戶內存使用消耗,內存的增加又會更頻繁的觸發 V8 引擎的 GC 機制,進而影響頁面交互性能。

本文從一個典型的 Webpack+Babel 工程出發,找到構建產物體積變大的常見原因和對應的解決思路,減少項目代碼構建后的體積

Babel

babel 最常見的用途就是代碼降級,使構建后的代碼能夠被低版本瀏覽器兼容,按照功能可以劃分兩部分

  1. API 降級

  2. 語法降級

通過 Babel 構建后的代碼為了適配低版本瀏覽器通常會比源代碼大上幾倍,這里面除了源代碼外還包含 API 墊片和語法輔助函數,分別對應上訴的 API 降級和語法降級,我們看下如何減少這部分的代碼體積

core-js

💡 按照目前最新版本的 babel@7,@babel/polyfill 已經廢棄,我們使用 core-js 完成 API 的語法降級

core-js 可以為瀏覽器中可能不兼容的 API 提供墊片,例如 Promise,Map

import?"core-js/modules/es.promise.js";//?使用降級?API
const?promise?=?Promise.resolve();

在需要降級的 API 調用前 require 對應的 core-js 模塊,就可以以污染全局變量或者原型鏈的方式實現 API 降級

手動插入 core-js 即麻煩又不安全,所以我們可以使用@babel/preset-env幫助我們自動插入 core-js 模塊

741e40a99c4ec7dfc91c96016008ae53.png

@babel/preset-env根據項目中 browserlist 定義的用戶環境,選擇性插入墊片代碼,減少墊片代碼體積

在配置@babel/preset-env 時,useBuiltIns 屬性非常重要,有兩個值"entry"|"usage",分別為全量降級和按需降級

entry 全量降級

entry 非常直接,首先我們需要手動在代碼的第一行import 'core-js',在執行編譯時,會按照 browserlist 中定義的環境,把可能需要降級的 API 一次性插入并替換到 core-js 聲明的位置

aa79ba28640b3dbe78ccaf443e25140f.png

開發者不再需要手動插入墊片,但這有個問題,即沒有使用的 API 仍然會被打進 bundle 中,由于 ECMAScript 標準的不斷發展,core-js 在 g-zip 壓縮后也有 50kb 左右的體積,顯然還是太大了

usage 按需降級

當選擇 usage 時,babel 會掃描所有需要編譯的 JS 代碼,根據實際使用到的 API 選擇性插入所需墊片

2de5fcb69af65bc9e40b35cb8f9f75fe.png

看起來是相比 entry 的更優解,但實際過于理想

  1. 通常基于編譯速度的考慮,node_modules 下的模塊不會參與 Babel 編譯,僅參與 Webpack 打包,如果此時恰巧某個依賴包里沒有聲明所需的墊片,那么就可能出現墊片缺失,最終導致線上環境 JS 運行異常。

    實際上這種情況在混亂的 npm 生態中非常普遍,有不少 npm 包直接使用 tsc 打包,除非開發者手動介入,否則構建產物中就會缺少 API 墊片,遇到這種情況往往只能在線上發現異常后手動添加依賴到babel.include中進行編譯

  2. 并不是所有 JS 代碼都會參與編譯,例如通過一些平臺動態下發的腳本,這些平臺動態下發的代碼完全不經過編譯,如果使用了未經降級的 api 也可能會出現 JS 運行異常。

可以看到 entry,usage 都是存在問題的,所以也就有了平臺化的方案,polyfill.io。

如果使用最新的現代化瀏覽器訪問該服務,那么返回的 JS 內容則是空的,反之它會響應瀏覽器所需的降級 API,既控制了包體積,也能確保未經編譯的 JS 獲得降級 API。

2ebf43ce8baf0907b7d9de289a458f63.png
Untitled

出于安全考慮,我們需要自部署服務,目前 polyfill.io 的 node.js 代碼是完全開源的,支持自部署,但是實際落地還需要考慮緩存和異常兜底

@babel/runtime

core-js 是為了解決 API 降級問題存在的,但是我們還有語法降級需要解決,例如 class,async

默認情況下 babel 為了實現 class 功能會生成一些內聯輔助函數,例如下圖的 createClass。這會產生一個問題,就是當多個模塊都使用 class 語法時則會生成多個相同的輔助函數,輔助函數不能復用

68a04c2e158aa7a3635491fecc03d29a.png
Untitled

我們可以通過注冊 babel 插件@babel/plugin-transform-runtime,將硬編碼輔助函數的方式改為從@babel/runtime引入輔助函數,實現不同模塊間輔助函數的復用

4405b33529682ba91b6440ef32b9e69c.png
Untitled

從下圖可以看到 createClass 函數從硬編碼改為require("@babel/runtime/helpers/createClass"),代碼大幅縮小

1701386ba42c3a2fde9690c1aab386aa.png
Untitled

但是@babel/plugin-transform-runtime的方案也不是毫無問題,和 api 降級一樣,同樣面臨各種依賴包構建不標準帶來的困擾

最大的問題就是沒有辦法保證依賴包的產物一定使用了@babel/plugin-transform-runtime進行構建,語法降級使用了內聯的輔助函數,又或者使用了老版本的babel-runtime·,導致項目最終的構建產物對輔助函數進行了多次打包

以相對常見的依賴包構建工具 father-build 和 tsc 為例,他們都沒有將語法輔助函數通過@babel/runtime依賴包進行提取,而是都以硬編碼的形式存在每個 JS 模塊當中。

這類由社區維護的 npm 包我們不好處理,但是可以通過收斂公司內部構建工具的方式,統一處理公司內部維護的依賴包,使它們構建的產物符合應用打包的需求,我們在文章結尾處再說

Tree-shaking

tree-shaking 是減少構建產物體積最有效的方式,以常用 lodash 為例,g-zip 后的體積 24kb,但是項目中使用到的函數并不多,如果能夠為它啟用 tree-shaking,代碼體積能控制在 1kb 以內

如何為依賴代碼啟用 tree-shaking?

  1. package.json 聲明 module 字段,地址指向 ESM 規范的構建產物

  2. package.json 聲明sideEffects:false,告訴 Webpack 整個依賴包沒有存在副作用,或者指明存在副作用模塊的地址

ESM

ESM 相比 commonjs 具備靜態分析能力, 這是 tree-shaking 的前置依賴條件,所以我們需要 babel 構建我們的源代碼時保留 import 語法,不要編譯成 commonjs

{"presets":?[["@babel/preset-env",{"modules":?false?//?保留ESM語法}]]
}

sideEffects

為什么依賴包的 package.json 需要聲明 sideEffects?

這里需要引申出自函數式編程中的純函數副作用函數概念,如果我們的代碼沒有存在任何副作用,tree-shaking 確實可以不需要類似 sideEffects 的副作用聲明,但實際上副作用普遍存在我們的代碼中,如果只依據函數是否被引用過作為 DCE(Dead Code Elimination) 的條件,很容易影響程序運行的正確性

通過 css-loader 引入 css 文件是很典型的例子

import?"./button.css";

對于 webpack 來說 button.css 同樣是一個模塊,這里沒有引用任何的具名函數,但是引入 css 模塊是會為我們帶來一個副作用,它會為 html 插入一個 style 標簽。如果 webpack 認為他是沒有副作用的,那么在 minify 階段 webpack 會刪除這行代碼,最終導致樣式錯亂

為了告訴 webpack 這個 css 文件是存在副作用的,不能刪除,sideEffects 就可以怎么寫

{"sideEffects":?["*.css",?"*.less"]
}

公司內部維護的依賴相比開源社區,很容易忽略sideEffects的聲明,如果存在公司內部的依賴構建工具,可以將sideEffects添加到相關的模板代碼中,默認為依賴包開啟 tree-shaking

回到社區現狀我們再來看 tree-shaking,lodash 推出了支持 tree-shaking 的lodash-es,antd@4 也不再需要安裝babel-plugin-import插件,可以通過 tree-shaking 的方式原生支持代碼按需加載,從而大幅縮小構建體積

Duplicate dependencies 重復依賴

依賴重復打包是前端開發中的常見問題,容易出現在公司內部長期無人維護的依賴包中

當我們的項目中存在 Root→C→D@2.0.0,Root→B→D@3.0.0類似的依賴關系時,node_module 結構如下

node_modules--?C?<--?depends?on?D@2.0.0--?D@2.0.0--?B?<--?depends?on?D@3.0.0--?node_modules--?D@3.0.0

可以看到在 node_modules 下嵌套安裝了 2 個版本的依賴 D,即D@2.0.0D@3.0.0。這可能導致在構建的產物中也同樣存在兩份相同依賴不同版本的代碼,除了會影響代碼體積,還可能導致代碼運行異常

解決方式是升級 B 的依賴D@2.0.0→D@3.0.0,此時重新安裝后node_modules的嵌套結構會恢復扁平

node_modules--?C?<--?depends?on?D@3.0.0--?D@3.0.0--?B?<--?depends?on?D@3.0.0

我們可以使用find-duplicate-dependencieswebpack-bundle-analyzer這些工具輔助我們排查依賴重復打包的問題

最佳實踐

回顧文章我們對一個典型前端應用可能影響 Bundle 體積的因素進行了分析,同時提出對應的解決方案。在文章的結尾我們可以更進一步通過工程化和平臺化的手段,以相對一勞永逸的方式解決上訴問題

如下圖,@company/app-builder負責構建應用,@company/module-builder負責構建依賴包,然后通過使用封裝的 babel 配置@company/babel-base,統一處理 JS 編譯

47208aed438e268593ef116b5e809b11.png
Untitled

babel-base關閉 core-js 的 api 降級,由 app-builder 開啟平臺 polyfill.io 方案,同時babel-base開啟@babel/plugin-transform-runtime,為應用和依賴包啟用語法輔助函數抽離

module-builder關閉 ESM 語法的轉換,為app-builder做 tree-shaking 時提供必要前置條件

通過這種方式,我們就可以實現在構建過程中減少代碼體積的最佳實踐

至于重復依賴的問題,由于必定需要開發者介入做版本選擇,所以我們可以考慮在部署平臺構建時自動上報 Dependency graph 數據,然后由性能分析等平臺將重復依賴的問題郵件抄送給相關開發者進行優化

總結

本文從構建工具的角度,闡述了如何減少構建產物的體積。可以看到僅僅處理應用的構建是不夠的,為了實現最佳效果,我們還需要介入公司內部依賴包的構建,使依賴包的構建產物符合應用構建的需求。只有具備全場景的構建能力才能最大程度降低代碼的構建體積。

參考資料

  • https://docs.npmjs.com/cli/v8/commands/npm-dedupe

  • https://babeljs.io/docs/en/babel-plugin-transform-runtime

  • https://babeljs.io/docs/en/babel-preset-env

  • https://babeljs.io/docs/en/babel-polyfill

  • https://webpack.js.org/guides/tree-shaking/#root

  • https://cdn.polyfill.io/v3/


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274425.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274425.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274425.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

用戶體驗需求層次_需求和用戶體驗

用戶體驗需求層次Shortly after the start of 2020 I led the development of a new website, and it went live in August. A week before the deployment, I paused development and took a step back in order to write about the project. Taking that pause, that step ba…

VMwareWorkstation設置U盤啟動(或U盤使用)

最近在工作中&#xff0c;經常要和LINUX部署打交道&#xff0c;一般在生產環境部署之前需要在自己的機器上進行測試。比如使用U盤安裝操作系統等。 在機器上安裝了VMware Workstation9.0&#xff0c;運行多個測試虛擬機。理由所當然的要使用此做一些操作系統部署&#xff0c;…

類從未使用_如果您從未依賴在線銷售,如何優化您的網站

類從未使用初學者指南 (A beginner’s guide) If you own a small business with a store front, you might have never had to rely on online sales. Maybe you’re a small clothing store or a coffee shop. You just made that website so people could find you online, …

狼書三卷終大成,狼叔親傳Node神功【留言送書】

大家好&#xff0c;我是若川。之前送過N次書&#xff0c;可以點此查看回饋粉絲&#xff0c;現在又和博文視點合作再次爭取了幾本書&#xff0c;具體送書規則看文末。眾所周知&#xff0c;我在參加掘金人氣作者打榜活動&#xff08;可點擊跳轉&#xff09;&#xff0c;需要大家投…

entity framework5 sqlserver2005 事務(TransactionScope)報未啟用MSDTC錯誤解決辦法

詳情請看&#xff1a;http://stackoverflow.com/questions/12809958/ef-how-do-i-call-savechanges-twice-inside-a-transaction using (var transaction new TransactionScope()) {// Do somethingdb.SaveChanges();// Do something elsedb.SaveChanges();tramsaction.Comple…

程序詳細設計之代碼編寫規范_我在不編寫任何代碼的情況下建立了一個設計策劃網站

程序詳細設計之代碼編寫規范It’s been just over a month since MakeStuffUp.Info — my first solo project as an independent Creator; was released to the world. It was not a big project or complicated in any way, it’s not even unique, but I’m thrilled where …

偷偷告訴你們一個 git 神器 tig,一般人我不告訴TA~

大家好&#xff0c;我是若川。眾所周知&#xff0c;我參加了掘金創作者人氣作者投票活動&#xff0c;最后3天投票。今天可投28票&#xff0c;明天32票&#xff0c;后天36票&#xff08;結束&#xff09;。投票操作流程看這里&#xff1a;一個普通小前端&#xff0c;將如何再戰掘…

DAO層使用泛型的兩種方式

package sanitation.dao;import java.util.List;/** * * param <T>*/public interface GenericDAO <T>{/** * 通過ID獲得實體對象 * * param id實體對象的標識符 * return 該主鍵值對應的實體對象*/ T findById(int id);/** * 將實體對象持…

將是驚心動魄的決戰~

大家好&#xff0c;我是若川。一個和大家一起學源碼的普通小前端。眾所周知&#xff0c;我參加了掘金人氣創作者評選活動&#xff08;投票&#xff09;&#xff0c;具體操作見此文&#xff1a;一個普通小前端&#xff0c;將如何再戰掘金年度創作者人氣榜單~。最后再簡單拉拉票吧…

圖書漂流系統的設計和研究_研究在設計系統中的作用

圖書漂流系統的設計和研究Having spent the past 8 months of my academic career working co-ops and internships in marketing & communication roles, my roots actually stem from arts & design. Although I would best describe myself as an early 2000s child…

黑馬-程序員C#泛型簡介

---------------------- Windows Phone 7手機開發、.Net培訓、期待與您交流&#xff01; ---------------------- 泛型&#xff1a;通過參數化類型來實現在同一份代碼上操作多種數據類型。利用“參數化類型”將類型抽象化&#xff0c;從而實現靈活的復用。 例子代碼&#xff1a…

西里爾字符_如何設計西里爾字母?(Nje),?(Lje),?(Tshe)和?(Dje)

西里爾字符This article is about how to design Cyrillic characters ?, ?, ?, and ? (upright caps and lowercase; italics are not covered here). They are often problematic since they are Cyrillic, but not found in the Russian alphabet, so there is no much …

學習 vuex 源碼整體架構,打造屬于自己的狀態管理庫

前言這是學習源碼整體架構第五篇。整體架構這詞語好像有點大&#xff0c;姑且就算是源碼整體結構吧&#xff0c;主要就是學習是代碼整體結構&#xff0c;不深究其他不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。其余四篇分別是&#xff1a;學習 jQuery 源碼整體…

VMware workstation 8.0上安裝VMware ESXI5.0

首先&#xff0c;在VMware的官網上注冊&#xff0c;下載VMware ESXI的安裝包vmware&#xff0d;vmvisor&#xff0d;installer&#xff0d;5.0.0&#xff0d;469512.x86_64.iso&#xff0c;它是iso文件&#xff0c;刻盤進行安裝&#xff0c;安裝過程中&#xff0c;會將硬盤全部…

最新ui設計趨勢_10個最新且有希望的UI設計趨勢

最新ui設計趨勢重點 (Top highlight)Recently, I’ve spent some time observing the directions in which UI design is heading. I’ve stumbled across a few very creative, promising and inspiring trends that, in my opinion, will shape the UI design in the nearest…

Lists

動態數組&#xff0c;可以存儲不同數據類型 >>> a [spam, eggs, 100, 1234] >>> a [spam, eggs, 100, 1234] 和string一樣&#xff0c;支持索引&#xff0c;&#xff0c;* >>> a[0] spam >>> a[3] 1234 >>> a[-2] 100 >>&…

學習 axios 源碼整體架構,打造屬于自己的請求庫

前言這是學習源碼整體架構系列第六篇。整體架構這詞語好像有點大&#xff0c;姑且就算是源碼整體結構吧&#xff0c;主要就是學習是代碼整體結構&#xff0c;不深究其他不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。學習源碼整體架構系列文章如下&#xff1a;1.…

404 錯誤頁面_如何設計404錯誤頁面,以使用戶留在您的網站上

404 錯誤頁面重點 (Top highlight)網站設計 (Website Design) There is a thin line between engaging and enraging when it comes to a site’s 404 error page. They are the most neglected of any website page. The main reason being, visitors are not supposed to end…

宏定義學習

【1】宏定義怎么理解&#xff1f; 關于宏定義&#xff0c;把握住本質&#xff1a;僅僅是一種字符替換&#xff0c;而且是在預處理之前就進行。 【2】宏定義可以包括分號嗎&#xff1f; 可以&#xff0c;示例代碼如下&#xff1a; 1 #include<iostream>2 using namespace…

學習 koa 源碼的整體架構,淺析koa洋蔥模型原理和co原理

前言這是學習源碼整體架構系列第七篇。整體架構這詞語好像有點大&#xff0c;姑且就算是源碼整體結構吧&#xff0c;主要就是學習是代碼整體結構&#xff0c;不深究其他不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。學習源碼整體架構系列文章如下&#xff1a;1.…