自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

CommonJS 是如何導(dǎo)致打包后體積增大的?

開(kāi)發(fā) 后端
CommonJS 是 2009 年發(fā)布的 JavaScript模塊化的一項(xiàng)標(biāo)準(zhǔn),最初它只打算在瀏覽器之外的場(chǎng)景使用,主要用于服務(wù)器端的應(yīng)用程序。

 [[352796]]

原文鏈接:https://web.dev/commonjs-larger-bundles

今天的文章,將介紹什么是 CommonJS,以及它為什么會(huì)導(dǎo)致我們打包后的文件體積增大。

什么是 CommonJS?

CommonJS 是 2009 年發(fā)布的 JavaScript模塊化的一項(xiàng)標(biāo)準(zhǔn),最初它只打算在瀏覽器之外的場(chǎng)景使用,主要用于服務(wù)器端的應(yīng)用程序。

你可以使用 CommonJS 來(lái)定義模塊,并從中導(dǎo)出部分模塊。例如,下面的代碼定義了一個(gè)模塊,該模塊導(dǎo)出了五個(gè)函數(shù):add、 subtract、 multiply、 divide、max:

 

  1. // utils.js 
  2. const { maxBy } = require('lodash-es'); 
  3. const fns = { 
  4.   add: (a, b) => a + b, 
  5.   subtract: (a, b) => a - b, 
  6.   multiply: (a, b) => a * b, 
  7.   divide: (a, b) => a / b, 
  8.   max: arr => maxBy(arr) 
  9. }; 
  10.  
  11. Object.keys(fns).forEach(fnName => module.exports[fnName] = fns[fnName]); 

 

其他模塊可以導(dǎo)入這個(gè)模塊的部分函數(shù)。

 

  1. // index.js 
  2. const { add } = require(‘./utils'); 
  3. console.log(add(1, 2)); 

 

通過(guò) node 運(yùn)行 index.js ,會(huì)在控制臺(tái)輸出數(shù)字 3。

在 2010 年,由于瀏覽器缺乏標(biāo)準(zhǔn)化的模塊化能力,CommonJS 成了當(dāng)時(shí) JavaScript 客戶端較為流行的模塊化標(biāo)準(zhǔn)。

CommonJS 如何影響包體?

服務(wù)端的 JavaScript 程序?qū)Υa體積并不像瀏覽器中那么敏感,這就是為什么在設(shè)計(jì) CommonJS 的時(shí)候,并沒(méi)有考慮減少生產(chǎn)包大小的原因。同時(shí),研表究明 JavaScript 代碼的體積依然是影響頁(yè)面加載速度的一個(gè)重要因素。

JavaScript 的打包工具(webpack、terser)會(huì)進(jìn)行許多優(yōu)化以減小最后生成的包體大小。他們?cè)跇?gòu)建時(shí),會(huì)分析你的代碼,盡可能的刪除不會(huì)使用的部分。例如,上面的代碼中,最終生成的包應(yīng)該只包含 add 函數(shù),因?yàn)檫@是 index.js 唯一從 utils.js 中導(dǎo)入的部分。

下面我們使用如下 webpack 配置對(duì)應(yīng)用進(jìn)行打包:

 

  1. const path = require('path'); 
  2. module.exports = { 
  3.   entry: 'index.js'
  4.   output: { 
  5.     filename: 'out.js'
  6.     path: path.resolve(__dirname, 'dist'), 
  7.   }, 
  8.   mode: 'production'
  9. }; 

 

我們需要將 webpack 的 mode 指定為 production,并且將 index.js 作為入口。運(yùn)行 webpack 后,會(huì)輸出一個(gè)文件:dist/out.js,可以通過(guò)如下方式統(tǒng)計(jì)它的大小:

 

  1. $ cd dist && ls -lah 
  2. 625K Apr 13 13:04 out.js 

 

打包后的文件高達(dá) 625 KB。如果看下 out.js 文件,會(huì)發(fā)現(xiàn) utils.js 導(dǎo)入 lodash 的所有模塊都打包到了輸出的文件中,盡管我們?cè)? index.js 并沒(méi)有使用到 lodash 的任何方法,但是這給我們的包體帶來(lái)了巨大的影響。

現(xiàn)在我們將代碼的模塊化方案改為 ESM,utils.js 部分的代碼如下:

 

  1. export const add = (a, b) => a + b; 
  2. export const subtract = (a, b) => a - b; 
  3. export const multiply = (a, b) => a * b; 
  4. export const divide = (a, b) => a / b; 
  5.  
  6. import { maxBy } from 'lodash-es'
  7.  
  8. export const max = arr => maxBy(arr); 

 

index.js 也改為 ESM 的方式從 utils.js 導(dǎo)入模塊:

 

  1. import { add } from './utils'
  2.  
  3. console.log(add(1, 2)); 

 

使用相同的 webpack 配置,構(gòu)建完畢之后,我們打開(kāi) out.js ,僅有 40 字節(jié),輸出如下:

 

  1. (()=>{"use strict";console.log(1+2)})(); 

值得注意的是,最終的輸出并沒(méi)有包含 utils.js 的任何代碼,而且 lodash 也消失了。而且 terser(webpack 使用的壓縮工具)直接將 add 函數(shù)內(nèi)聯(lián)到了 console.log 內(nèi)部。

有的小朋友可能就會(huì)問(wèn)了(此處采用了李永樂(lè)語(yǔ)法),為什么使用 CommonJS 會(huì)導(dǎo)致輸出的文件大了 16,000 倍?當(dāng)然,這只是用來(lái)展示 CommonJS 與 ESM 差異的案例,實(shí)際上并不會(huì)出現(xiàn)這么大的差異,但是使用 CommonJS 肯定會(huì)導(dǎo)致打包后的體積更大。

一般情況下,CommonJS 模塊的體積更加難優(yōu)化,因?yàn)樗?ES 模塊更加的動(dòng)態(tài)化。為了確保構(gòu)建工具以及壓縮工具能成功優(yōu)化代碼,請(qǐng)避免使用 CommonJS 模塊。

當(dāng)然,如果你只在 utils.js 采用了 ESM 的模塊化方案,而 index.js 還是維持 CommonJS,則包體依舊會(huì)受到影響。

為什么 CommonJS 會(huì)使包體更大?

要回答這個(gè)問(wèn)題,我們需要研究 webpack 的 ModuleConcatenationPlugin 的行為,并且看看它是如何進(jìn)行靜態(tài)分析的。該插件將所有的模塊都放入一個(gè)閉包內(nèi),這會(huì)讓你的代碼在瀏覽器中更快的執(zhí)行。我們來(lái)看看下面的代碼:

 

  1. // utils.js 
  2. export const add = (a, b) => a + b; 
  3. export const subtract = (a, b) => a - b; 

 

 

  1. // index.js 
  2. import { add } from ‘./utils'; 
  3. const subtract = (a, b) => a - b; 
  4.  
  5. console.log(add(1, 2)); 

 

我們有一個(gè)新的 ESM 模塊(utils.js),將其導(dǎo)入 index.js 中,我們還重新定義一個(gè) subtract 函數(shù)。接下來(lái)使用之前的 webpack 配置來(lái)構(gòu)建項(xiàng)目,但是這次,我把禁用壓縮配置。

 

  1. const path = require('path'); 
  2.  
  3. module.exports = { 
  4.   entry: 'index.js'
  5.   output: { 
  6.     filename: 'out.js'
  7.     path: path.resolve(__dirname, 'dist'), 
  8.   }, 
  9. + optimization: { 
  10. +   minimize: false 
  11. + }, 
  12.   mode: 'production'
  13. }; 

 

輸出的 out.js 如下:

 

  1. /******/ (() => { // webpackBootstrap 
  2. /******/  "use strict"
  3.  
  4. // CONCATENATED MODULE: ./utils.js** 
  5. const add = (a, b) => a + b; 
  6. const subtract = (a, b) => a - b; 
  7.  
  8. // CONCATENATED MODULE: ./index.js** 
  9. const index_subtract = (a, b) => a - b; 
  10. console.log(add(1, 2)); 
  11.  
  12. /******/ })(); 

 

輸出的代碼中,所有的函數(shù)都在一個(gè)命名空間里,為了防止沖突,webpack 將 index.js 中的 subtract 函數(shù)重新命名為了 index_subtract 函數(shù)。

如果開(kāi)啟壓縮配置,它會(huì)進(jìn)行如下操作:

  1. 刪除沒(méi)有使用的 subtract 函數(shù)和 index_subtract 函數(shù);
  2. 刪除所有的注釋和空格;
  3. console.log 中直接內(nèi)聯(lián) add 函數(shù);

一些開(kāi)發(fā)人員會(huì)把這種刪除未使用代碼的行為稱為“tree-shaking(樹(shù)搖)”。webpack 能夠通過(guò)導(dǎo)出、導(dǎo)入符號(hào)靜態(tài)的分析 utils.js(在構(gòu)建的過(guò)程中),這使得 tree-shaking 有了可行性。當(dāng)使用 ESM 時(shí),這種行為是默認(rèn)開(kāi)啟的,因?yàn)橄啾扔? CommonJS,它更加易于靜態(tài)分析。

讓我們看看另外的示例,這一次將 utils.js 改為 CommonJS 模塊,而不是 ESM 模塊。

 

  1. // utils.js 
  2. const { maxBy } = require('lodash-es'); 
  3.  
  4. const fns = { 
  5.   add: (a, b) => a + b, 
  6.   subtract: (a, b) => a - b, 
  7.   multiply: (a, b) => a * b, 
  8.   divide: (a, b) => a / b, 
  9.   max: arr => maxBy(arr) 
  10. }; 
  11.  
  12. Object.keys(fns).forEach(fnName => module.exports[fnName] = fns[fnName]); 

 

這個(gè)小小的改動(dòng),明顯影響了輸出的代碼。由于輸出的文本太大,我們只展示其中的一小部分。

 

  1. ... 
  2. (() => { 
  3.  
  4. "use strict"
  5. /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(288); 
  6. const subtract = (a, b) => a - b; 
  7. console.log((0,_utils__WEBPACK_IMPORTED_MODULE_0__/* .add */ .IH)(1, 2)); 
  8.  
  9. })(); 

 

可以看到,最終生成的代碼包含一些 webpack 的 runtime 代碼,這部分代碼負(fù)責(zé)模塊的導(dǎo)入導(dǎo)出的能力。這次并沒(méi)有將 utils.js 和 index.js 所有的變量放到了同一命名空間下,動(dòng)態(tài)引入的模塊都是通過(guò) __webpack_require__ 進(jìn)行導(dǎo)入。

使用 CommonJS 的時(shí)候,我們可以通過(guò)任意的表達(dá)式構(gòu)造導(dǎo)出名稱,例如下面的代碼也是能正常運(yùn)行的:

 

  1. module.exports[(Math.random()] = () => { … }; 

這導(dǎo)致構(gòu)建工具在構(gòu)建時(shí),沒(méi)有辦法知道導(dǎo)出的變量名,因?yàn)檫@個(gè)名稱只有在用戶瀏覽器運(yùn)行時(shí)才能夠真正確定。壓縮工具無(wú)法準(zhǔn)確的知道 index.js 使用了模塊的哪部分內(nèi)容,因此無(wú)法正確的進(jìn)行 tree-shaking。如果我們從 node_modules 導(dǎo)入了 CommonJS 模塊,你的構(gòu)建工具將無(wú)法正確的優(yōu)化它。

對(duì) CommonJS 使用 Tree-shaking

由于 CommonJS 的模塊化方案是動(dòng)態(tài)的,想要分析他們是特別困難的。與通過(guò)表達(dá)式導(dǎo)入模塊的 CommonJS 相比,ESM 模塊的導(dǎo)入始終使用的是靜態(tài)的字符串文本。

在某些情況下,如果你使用的庫(kù)遵循 CommonJS 的相關(guān)的一些約定,你可以使用第三方的 webpack 插件:webpack-common-shake,在構(gòu)建的過(guò)程中,刪除未使用的模塊。盡管該插件增加了 CommonJS 對(duì) tree-shaking 的支持,但并沒(méi)有涵蓋所有的 CommonJS 依賴,這意味著你不能獲得 ESM 相同的效果。

此外,這并非是 webpack 默認(rèn)行為,它會(huì)對(duì)你的構(gòu)建耗時(shí)增加額外的成本。

總結(jié)

  • 為了確保構(gòu)建工具對(duì)你的代碼盡可能的進(jìn)行優(yōu)化,請(qǐng)避免使用 CommonJS 模塊,并在整個(gè)項(xiàng)目中使用 ESM 語(yǔ)法。
  • 下面是一些檢驗(yàn)?zāi)愕捻?xiàng)目是否是最佳實(shí)踐的方法:
  • 使用 Rollup.js 提供的 node-resolve 插件,并開(kāi)啟 modulesOnly 選項(xiàng),表示你的項(xiàng)目只會(huì)使用 ESM。

使用 is-esm 來(lái)驗(yàn)證 npm 安裝的模塊是否使用 ESM。

如果您使用的是Angular,默認(rèn)情況下,如果你依賴了不能進(jìn)行 tree-shaking 的模塊,則會(huì)收到警告。

本文轉(zhuǎn)載自微信公眾號(hào)「 更了不起的前端」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 更了不起的前端公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 更了不起的前端
相關(guān)推薦

2022-05-03 20:48:17

Webpackcommonjsesmodule

2022-02-20 19:02:16

RollupVue 2JavaScrip

2021-05-27 13:33:14

程序員技能開(kāi)發(fā)者

2020-09-27 07:00:00

安卓應(yīng)用Android

2013-10-23 10:51:48

開(kāi)發(fā)模型軟件開(kāi)發(fā)軟件產(chǎn)業(yè)

2021-11-23 09:42:28

Pythonexepy

2009-03-30 14:30:52

2025-02-07 15:58:43

2021-11-17 21:58:02

Python編程語(yǔ)言

2019-08-30 12:02:23

數(shù)據(jù)工程師云廠商

2017-02-10 12:10:59

2023-11-08 08:40:35

JavaScriptS 模塊

2016-10-27 10:53:50

2012-04-17 10:02:49

Windows 8Metro應(yīng)用

2023-10-08 18:05:57

2023-02-14 17:06:31

設(shè)備移植打包刷機(jī)

2010-09-27 08:25:57

2014-08-08 09:41:15

大數(shù)據(jù)

2019-08-26 09:15:09

設(shè)計(jì)技術(shù)人生第一份工作

2009-07-24 09:11:02

Linux體積Linux命令行Linux安裝
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)