使用 asar 提升 VSCode 插件的性能

最近在開發(fā)一款 VSCode 插件過程中遇到了一個性能瓶頸,插件的依賴項過于龐大導致插件體積很大,插件安裝和啟動耗時也比較長,影響了用戶的使用體驗。為了解決這個問題,本文提出了一種使用 asarnode_modules 進行打包的方法,經測試插件體積減少了 46%,安裝時間減少了 75% 以上,啟動時間減少了 22%,插件的性能得到了有效提升。

該方法具備一定的通用性,尤其是對插件依賴項過多時有較為明顯的提升。如果你是 VSCode 插件開發(fā)者并且面臨著類似的問題,歡迎嘗試下此方案。

問題分析

性能的主要瓶頸在于插件中的 node_modules 非常龐大,有 117,317 個文件,體積達到了 185.5 MB,而且這些依賴需要在插件運行時使用,vsce(VSCode 插件構建工具)需要把 node_modules 也打包到產物內,導致插件產物(vsix 文件)的體積也很大,且文件零碎,用戶安裝插件時速度很慢。

以下是構建時 vsce 給出的信息,可以看到 vsce 也發(fā)現(xiàn)了這個問題并且給出了一個解決方案:Bundling Extensions

優(yōu)化前的版本

VSCode 提出的優(yōu)化方案主要有兩個方面:

  1. 添加 .vscodeignore 構建時忽略插件運行時不需要的文件
  2. 對插件使用 rollup 等構建工具在 vsce 前進行打包,消滅 node_modules

很遺憾,這個方案無法有效優(yōu)化這個插件,因為一方面這些依賴需要在運行時使用因此不能忽略,另一方面插件中的依賴大量使用了動態(tài) require,因此 rollup 等構建工具無法完整的將所有依賴打包。

如果你的插件依賴沒有遇到動態(tài) require 的問題,推薦直接使用構建工具的方案而不是 asar

因此,順著第二個思路,我們需要找到一種方法將插件的依賴完整打包。

解決方案

在一次調試 VSCode 的過程中,發(fā)現(xiàn)了 VSCode 的 Resources/app 目錄下有一個 node_modules.asar 的文件,頓時受到了啟發(fā)。開發(fā)過 Electron 的朋友應該都接觸過這個文件類型,而 VSCode 也是基于 Electron,VSCode 選擇將自身的依賴構建成 asar 必然有性能方面的考量。那么我們是否也可以利用 asar 對插件的依賴進行處理呢?

我們先看下 Electron 對 asar 的介紹(https://github.com/electron/asar):

asar 是一種簡單的擴展存檔格式,它和 tar 類似,無需壓縮即可將所有文件連接在一起,同時具備隨機訪問支持

asar 的優(yōu)點:

  • 支持隨機訪問
  • 使用 JSON 存儲文件信息
  • 容易解析

由此來看,asar 很匹配我們的需求。接下來就具體介紹如何在插件中使用。

構建 asar 文件

asar 的構建非常簡單,首先安裝:

$ npm install asar -D

然后在 package.json 添加一個運行腳本:

{
  "build:asar": "asar pack ./node_modules ./dist/node_modules.asar"
}

運行 npm run build:asar 即可得到 asar 文件

忽略 node_modules

得到 node_modules.asar 后,插件就不再需要 node_modules 目錄,因此需要在 .vscodeignore 文件中添加一下:

node_modules

讓 VSCode 支持加載 asar

由于 VSCode 是基于 Electron 的,因此天然能加載 asar 中的文件。例如插件中有一個依賴項 miniprogram-ci,我們通過將路徑改為以下方式即可在插件中加載 asar 文件中的依賴項:

// 將 require('miniprogram-ci') 替換為:
require('./node_modules.asar/miniprogram-ci')

然而我們很難將 node_modules 中所有依賴項中的 require 也都改寫成這種形式,這會導致依賴項的依賴無法加載。為了解決這個問題,我研究了下 VSCode 的源代碼,發(fā)現(xiàn) VSCode 是采用改寫 Node 模塊加載的行為實現(xiàn)的。

讓 require 支持查找 asar

首先我們回顧下 Node 是如何加載依賴的。當 require('miniprogram-ci') 時,Node 會先生成所有可能的查找路徑(paths),例如這個項目的插件入口位于 dist/extension/index.js,引用該依賴時 Node 會形成以下查找路徑并按順序嘗試,直到成功

/Users/[用戶名]/.vscode/extensions/[插件名]/dist/extension/node_modules
/Users/[用戶名]/.vscode/extensions/[插件名]/dist/node_modules
/Users/[用戶名]/.vscode/extensions/[插件名]/node_modules(成功)
/Users/[用戶名]/.vscode/extensions/node_modules
…

因此默認情況下 Node 是不會默認查找 node_modules.asar 的,我們可以通過修改 Node 的這一查找行為將 asar 的路徑也塞到 paths

參考 VSCode 源碼中的實現(xiàn)(bootstrap-node.js),通過改寫 Module._resolveLookupPaths 即可達到目的。

在插件入口文件最上方增加以下代碼:

const Module = require('module');
const path = require('path');
const asarPath = path.join(__dirname, '..', 'node_modules.asar');
const nodeModulesPath = path.join(__dirname, '..', '..', 'node_modules');
const originalResolveLookupPaths = Module._resolveLookupPaths;

Module._resolveLookupPaths = function (moduleName, parent) {
  const paths = originalResolveLookupPaths(moduleName, parent);

  if (Array.isArray(paths)) {
    for (let i = 0, len = paths.length; i < len; i++) {
      if (paths[i] === nodeModulesPath) {
        paths.splice(i, 0, asarPath);
        break;
      }
    }
  }

  return paths;
};

即當 require 某個模塊時,搜索其查找路徑 paths 是否存在插件根目錄下的 node_modules 路徑,如有則將 asar 文件路徑插入到 paths 中。搜索的目的是為了確認這個模塊是否是 node_modules 下的,排除 Node 內置模塊和其它情況的引用。

優(yōu)化效果

至此,插件就可以擺脫 node_modules,性能得到很大的提升。我們從三個方面對比下前后的性能變化:

插件體積

優(yōu)化前:67.7 MB,110,223 個文件,構建用時 3m 5s

優(yōu)化后:36.4 MB,764 個文件,構建用時 2m 56s

插件體積減少了 46%,構建用時持平,并沒有因為增加了 asar 構建環(huán)節(jié)而顯著變慢

優(yōu)化后的版本

優(yōu)化前后的 vsix 產物可以在這里看到,其中版本 1.4.7 為優(yōu)化前,1.4.8 為優(yōu)化后:
https://github.com/crazyurus/miniprogram-vscode-extension/releases

構建用時的變化可以在 GitHub Actions 中看到:https://github.com/crazyurus/miniprogram-vscode-extension/actions

插件安裝用時

安裝時間 VSCode 沒有直接給出,大致測量了下:

優(yōu)化前:1m 以上

優(yōu)化后:15s 左右

插件安裝用時也有大幅提升,且用時的減少可以有效減緩用戶網(wǎng)絡導致安裝失敗的影響,有效提升了安裝成功率

插件加載用時

VSCode 每次加載插件都會統(tǒng)計用時,可以在插件列表或詳情頁看到:

插件加載用時

優(yōu)化前:95ms

優(yōu)化后:74ms

插件加載用時也有一定提升,主要避免了大量零碎文件的加載導致的 I/O 消耗

進一步優(yōu)化

node_modules 中的所有依賴并不是插件運行時都需要的,我們可以將 devDependencies 排除在外不打包到 asar 中,進一步減少體積。

  1. 可以通過 npm install --production 實現(xiàn)僅安裝 dependencies 中的依賴,此時 node_modules 中的依賴只與運行時相關。得到 asar 產物后再安裝全部依賴完成插件的其它構建步驟,感興趣的朋友可以嘗試下。

  2. 也可以通過增加參數(shù) asar --unpack-dir 將部分體積大的依賴排除,例如:

$ asar pack ./node_modules ./dist/node_modules.asar --unpack-dir "{@types,ts-node,typescript}"

排除掉插件開發(fā)和構建環(huán)節(jié)有關 TypeScript 的依賴,asar 會將這些依賴復制到 node_modules.asar.unpacked 文件夾下,我們只需刪除或忽略這個文件夾即可。

最后

向大家推廣一下這款 VSCode 插件 微信小程序開發(fā)工具,支持 WXML 等小程序特有語法的高亮和代碼提示,以及小程序的預覽、上傳、體積分析等,歡迎開發(fā)小程序的朋友體驗以及反饋意見。

另外這個插件是開源的,對 VSCode 插件開發(fā)感興趣的朋友也可以了解下:https://github.com/crazyurus/miniprogram-vscode-extension

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容