摘要:同時(shí)它又需要監(jiān)聽(tīng)事件,當(dāng)物聯(lián)網(wǎng)設(shè)備插入計(jì)算機(jī)后做出響應(yīng)。比如以熱啟動(dòng)打開(kāi)物聯(lián)網(wǎng)項(xiàng)目工作區(qū)的啟動(dòng)時(shí)間在表中是毫秒,但是正在運(yùn)行的擴(kuò)展頁(yè)面中大約是毫秒。當(dāng)在中應(yīng)用懶加載時(shí),無(wú)論是否使用打包模塊,沒(méi)有工作區(qū)時(shí)啟動(dòng)速度都遠(yuǎn)快于打開(kāi)物聯(lián)網(wǎng)工作區(qū)。
概述
擴(kuò)展可以讓用戶(hù)在VS Code中向開(kāi)發(fā)工作流程添加新的語(yǔ)言、調(diào)試器和工具。VS Code提供了豐富的可擴(kuò)展模塊,允許擴(kuò)展訪(fǎng)問(wèn)用戶(hù)界面、提供擴(kuò)展功能。
通常情況下VS Code會(huì)安裝多個(gè)擴(kuò)展,所以作為一名擴(kuò)展開(kāi)發(fā)者,我們應(yīng)該時(shí)刻關(guān)注擴(kuò)展的性能,避免拖慢其它擴(kuò)展,甚至是VS Code的主進(jìn)程。
下面是我們?cè)陂_(kāi)發(fā)一款擴(kuò)展時(shí)應(yīng)該遵循的原則:
避免使用同步方法。同步方法會(huì)阻塞整個(gè)Node的進(jìn)程,直到其返回結(jié)果。所以,你應(yīng)該盡可能地使用異步方法。如果發(fā)現(xiàn)很難用異步方法替換同步方法,那么你應(yīng)該考慮一下重構(gòu)代碼。
只引用你需要的模塊。有一些依賴(lài)模塊非常巨大,比如說(shuō)lodash。通常我們不需要lodash的全部方法,所以引用整個(gè)lodash模塊并不合理。lodash的每個(gè)方法都有獨(dú)立的模塊,你應(yīng)該只引用你需要的部分。
謹(jǐn)慎對(duì)待啟動(dòng)條件。大多數(shù)情況下,你的擴(kuò)展并不需要啟動(dòng)。不用使用“*”作為啟動(dòng)條件。如果你的擴(kuò)展確實(shí)需要一直監(jiān)聽(tīng)一些事件,考慮將主要的代碼放在setTimeout里以低優(yōu)先級(jí)運(yùn)行。
按需加載模塊。import ... from ...是比較常用的引用模塊的方法,但是有時(shí)這并不一定是個(gè)好的方法。比如一個(gè)叫做request-promise的模塊,加載起來(lái)會(huì)耗費(fèi)非常多的時(shí)間(在我自己這邊測(cè)試需要1至2秒),但可只能有在特定的情況下我們才會(huì)需要請(qǐng)求遠(yuǎn)程的資源,比如本地的緩存過(guò)期了。
上面提到的前三個(gè)原則很多開(kāi)發(fā)者已經(jīng)遵守了,在這篇文章中,我們會(huì)討論按一種需加載的方法。這種方法要符合我們平時(shí)寫(xiě)TypeScript和JavaScript的習(xí)慣,同時(shí)也要盡可能減少更改現(xiàn)有代碼的工作量。
按需加載模塊 符合習(xí)慣一般來(lái)說(shuō),我們?cè)谀_本的最頂端使用import來(lái)加載模塊,比如下面的代碼:
import * as os from "os";
Node會(huì)同步加載指定的模塊,同時(shí)阻塞后面的代碼。
我們需要一個(gè)新的方法,比如叫做impor吧,用它可以引入模塊,但并不馬上加載這個(gè)模塊:
const osModule = impor("os"); // osModule不可訪(fǎng)問(wèn),因?yàn)閛s模塊還沒(méi)有被加載
為了達(dá)到這一目的,我們需要使用Proxy對(duì)象。Proxy對(duì)象被用來(lái)自定義一些基本操作的行為。
我們可以自定義get方法,只有當(dāng)這個(gè)模塊被調(diào)用時(shí)我們才開(kāi)始加載它。
get: (_, key, reciver) => { if (!mod) { mod = require(id); } return Reflect.get(mod, key, reciver); }
使用Proxy對(duì)象后,osModule是一個(gè)Proxy實(shí)例,并且只有當(dāng)我們調(diào)用它的一個(gè)方法后,os模塊才會(huì)被加載。
const osModule = impor("os"); // os模塊還沒(méi)有被加載 ... const platform = osModule.platform() // os模塊從這里開(kāi)始加載
當(dāng)我們只想使用模塊的一部分時(shí),廣泛使用import {...} for ...的寫(xiě)法??墒沁@讓Node不得不訪(fǎng)問(wèn)這個(gè)模塊來(lái)檢查其屬性值。這樣getter就會(huì)被調(diào)用,模塊也會(huì)在那個(gè)時(shí)候被加載。
使用后臺(tái)任務(wù)加載模塊按需加載還不夠,我們可以進(jìn)一步來(lái)優(yōu)化用戶(hù)體驗(yàn)。在擴(kuò)展啟動(dòng)和用戶(hù)運(yùn)行命令來(lái)加載模塊之間,我們有充足的時(shí)間來(lái)提前加載模塊。
很容易想到的一個(gè)辦法,是創(chuàng)建一個(gè)后臺(tái)任務(wù)來(lái)加載隊(duì)列里的模塊。
時(shí)間線(xiàn)我們開(kāi)發(fā)了一個(gè)名叫Azure IoT Device Workbench的擴(kuò)展,它可以結(jié)合多個(gè)Azure服務(wù)和流行的物聯(lián)網(wǎng)開(kāi)發(fā)板,簡(jiǎn)單地進(jìn)行物聯(lián)網(wǎng)項(xiàng)目的開(kāi)發(fā)、編譯、部署和調(diào)試。
由于Azure IoT Device Workbench涉及到的范圍非常廣泛,所以這個(gè)擴(kuò)展啟動(dòng)起來(lái)非常繁重。同時(shí)它又需要監(jiān)聽(tīng)USB事件,當(dāng)物聯(lián)網(wǎng)設(shè)備插入計(jì)算機(jī)后做出響應(yīng)。
圖一:Azure IoT Device Workbench使用懶加載和正常加載的啟動(dòng)時(shí)間
我們對(duì)比了Azure IoT Device Workbench在多種情況下使用懶加載和正常加載的啟動(dòng)時(shí)間。圖一中由上到下的圖表分別是沒(méi)有工作區(qū)、打開(kāi)非物聯(lián)網(wǎng)項(xiàng)目工作區(qū)和打開(kāi)物聯(lián)網(wǎng)項(xiàng)目工作區(qū)時(shí)啟動(dòng)。左側(cè)的圖表是冷啟動(dòng),右側(cè)是熱啟動(dòng)。冷啟動(dòng)只發(fā)生在第一次安裝擴(kuò)展時(shí),VS Code做一些緩存之后,都將是熱啟動(dòng)。X軸表示時(shí)間,以毫秒為單位。Y軸是已加載的模塊數(shù)量。
With normal load, the extension is activated at end of the chart. We find the extension is activated very advanced with lazy load with both cold boot and warm boot, especially when VS Code launches without workspace open.
對(duì)于沒(méi)有工作區(qū)冷啟動(dòng)的情況,懶加載的啟動(dòng)速度大約有30倍的提升,熱啟動(dòng)時(shí)有大約20倍的提升。打開(kāi)非物聯(lián)網(wǎng)項(xiàng)目工作區(qū)時(shí),冷啟動(dòng)懶加載比正常加載快了10倍,熱啟動(dòng)時(shí)快20倍。當(dāng)VS Code打開(kāi)物聯(lián)網(wǎng)項(xiàng)目時(shí),Azure IoT Device Workbench需要引用大量模塊來(lái)加載項(xiàng)目,即使這樣,我們冷啟動(dòng)時(shí)也偶兩倍的啟動(dòng)速度,熱啟動(dòng)時(shí)有3倍的啟動(dòng)速度。
下面是懶加載的完整時(shí)間線(xiàn):
圖二:Azure IoT Device Workbench使用懶加載的完整時(shí)間線(xiàn)
和圖一一樣,圖二中的圖表也表示冷啟動(dòng)和熱啟動(dòng)下沒(méi)有工作區(qū)、打開(kāi)非物聯(lián)網(wǎng)項(xiàng)目工作區(qū)和打開(kāi)物聯(lián)網(wǎng)項(xiàng)目工作區(qū)。
在圖中可以看到后臺(tái)任務(wù)加載模塊的加載時(shí)間階梯非常清晰。用戶(hù)很難注意到這個(gè)小動(dòng)作,擴(kuò)展啟動(dòng)得非常順暢。
為了使這個(gè)提升性能的方法可以被所有VS Code擴(kuò)展開(kāi)發(fā)者使用,我們發(fā)布了一個(gè)名叫impor的Node模塊,并且我們已經(jīng)將這個(gè)模塊用于Azure IoT Device Workbench。你可以對(duì)代碼進(jìn)行很少的更改就將它應(yīng)用到你的項(xiàng)目中。
模塊打包幾乎所有的VS Code擴(kuò)展都有Node模塊依賴(lài)。因?yàn)镹ode模塊的工作方式,依賴(lài)的曾經(jīng)可能會(huì)非常深。另外,模塊的結(jié)果也可能非常復(fù)雜,也就是Node模塊黑洞所說(shuō)的事情。
為了清理Node模塊,我們使用一個(gè)非常棒的工具,webpack。
webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴(lài)關(guān)系圖(dependency graph),其中包含應(yīng)用程序需要的每個(gè)模塊,然后將所有這些模塊打包成一個(gè)或多個(gè) bundle。Tree shaking
tree shaking 是一個(gè)術(shù)語(yǔ),通常用于描述移除 JavaScript 上下文中的未引用代碼(dead-code)。它依賴(lài)于 ES2015 模塊系統(tǒng)中的靜態(tài)結(jié)構(gòu)特性,例如 import 和 export。這個(gè)術(shù)語(yǔ)和概念實(shí)際上是興起于 ES2015 模塊打包工具 rollup。
使用webpack進(jìn)行tree shaking非常簡(jiǎn)單。我們需要指定一個(gè)入口文件和輸出文件名就可以,剩下的事情webpack會(huì)處理好。
使用tree shaking后,沒(méi)有被引用的文件,包括JavaScript代碼、markdown文件等等都會(huì)被移除。之后webpack會(huì)把所有文件整合成一個(gè)多帶帶的打包文件。
代碼分離把所有代碼都合并成一個(gè)文件可不是一個(gè)好主意。為了與按需加載一同協(xié)作,我們需要把代碼分割成多個(gè)部分,并且只加載我們需要的部分。
現(xiàn)在,需要一種分離代碼的方法是我們需要解決的問(wèn)題。一種可行的方案是將每個(gè)Node模塊分離成一個(gè)文件。不過(guò)手動(dòng)將每個(gè)Node模塊的路徑寫(xiě)進(jìn)webpack配置文件中是無(wú)法接受的。幸好我們可以使用npm-ls來(lái)獲取產(chǎn)品模式下所有的Node模塊。這樣在webpack配置文件的輸出部分,我們使用[name].js作為輸出來(lái)編譯每個(gè)模塊。
應(yīng)用打包后的模塊當(dāng)我們要加載一個(gè)模塊時(shí),比如叫happy-broccoli,Node會(huì)先試著在node_modules文件夾中查找happy-broccoli.js。如果這個(gè)文件不存在,Node接著查找happy-broccoli文件夾下的index.js文件,如果還是找不到,就查看package.json里的main。
為了應(yīng)用打包后的模塊,我們可以把它們放進(jìn)tsc輸出目錄下的node_modeles文件夾里。
如果哪個(gè)模塊不兼容webpack打包,就直接將它復(fù)制到輸出目錄的node_modules文件夾里。
這是一個(gè)擴(kuò)展項(xiàng)目結(jié)構(gòu)的例子:
|- src | |- extension.ts | |- out | |- node_modules | | |- happy-broccoli.js | | |- incompatible-with-bundle-module | | |- package.json | | | |- extension.js | |- node_modules | |- happy-broccoli | |- package.json | | |- incompatible-with-bundle-module | |- package.json | |- package.json |- webpack.config.js |- tsconfig.json
未打包Node模塊時(shí)Azure IoT Device Workbench包含了4368個(gè)文件,打包后只剩下了343個(gè)文件。
Webpack配置實(shí)例"use strict"; const cp = require("child_process"); const fs = require("fs-plus"); const path = require("path"); function getEntry() { const entry = {}; const npmListRes = cp.execSync("npm list -only prod -json", { encoding: "utf8" }); const mod = JSON.parse(npmListRes); const unbundledModule = ["impor"]; for (const mod of unbundledModule) { const p = "node_modules/" + mod; fs.copySync(p, "out/node_modules/" + mod); } const list = getDependeciesFromNpm(mod); const moduleList = list.filter((value, index, self) => { return self.indexOf(value) === index && unbundledModule.indexOf(value) === -1 && !/^@types//.test(value); }); for (const mod of moduleList) { entry[mod] = "./node_modules/" + mod; } return entry; } function getDependeciesFromNpm(mod) { let list = []; const deps = mod.dependencies; if (!deps) { return list; } for (const m of Object.keys(deps)) { list.push(m); list = list.concat(getDependeciesFromNpm(deps[m])); } return list; } /**@type {import("webpack").Configuration}*/ const config = { target: "node", entry: getEntry(), output: { path: path.resolve(__dirname, "out/node_modules"), filename: "[name].js", libraryTarget: "commonjs2", devtoolModuleFilenameTemplate: "../[resource-path]", }, resolve: { extensions: [".js"] } } module.exports = config;與典型的webpack解決方案對(duì)比
不將整個(gè)擴(kuò)展打包,而是對(duì)每個(gè)模塊分別打包會(huì)帶來(lái)很大的好處。使用webpack打包后,擴(kuò)展極有可能會(huì)拋出數(shù)十個(gè)錯(cuò)誤。把每個(gè)模塊分離開(kāi)使調(diào)試變得非常容易。同時(shí),按需加載指定的模塊也能盡可能地降低對(duì)性能的影響。
實(shí)驗(yàn)結(jié)果模塊打包應(yīng)用在使用懶加載的Azure IoT Device Workbench上,來(lái)同正常加載進(jìn)行對(duì)比。
圖三:Azure IoT Device Workbench懶加載打包模塊的啟動(dòng)時(shí)間和正常加載對(duì)比
模塊打包大幅減少了啟動(dòng)時(shí)間。對(duì)于冷啟動(dòng),在一起情況下懶加載甚至加載完所有模塊所消耗的全部時(shí)間都比正常加載所需的時(shí)間少。
正常 | Webpack典型的解決方案* | 懶加載 | 懶加載打包的模塊** | |
---|---|---|---|---|
沒(méi)有工作區(qū),冷啟動(dòng) | 19474 ms | 1116 ms | 599 ms | 196 ms |
沒(méi)有工作區(qū),熱啟動(dòng) | 2713 ms | 504 ms | 118 ms | 38 ms |
非物聯(lián)網(wǎng)項(xiàng)目工作區(qū),冷啟動(dòng) | 11188 ms | 1050 ms | 858 ms | 218 ms |
非物聯(lián)網(wǎng)項(xiàng)目工作區(qū),熱啟動(dòng) | 4825 ms | 530 ms | 272 ms | 102 ms |
物聯(lián)網(wǎng)項(xiàng)目工作區(qū),冷啟動(dòng) | 15625 ms | 1178 ms | 7629 ms | 2001 ms |
物聯(lián)網(wǎng)項(xiàng)目工作區(qū),熱啟動(dòng) | 5186 ms | 588 ms | 1513 ms | 517 ms |
*,** Azure IoT Device Workbench需要的一些模塊與webpack不兼容,沒(méi)有被打包。
表一:Azure IoT Device Workbench在不同情況下的啟動(dòng)時(shí)間
表一中所示的啟動(dòng)時(shí)間是指擴(kuò)展入口最開(kāi)始到activate函數(shù)結(jié)束之間的時(shí)間:
// 開(kāi)始啟動(dòng) import * as vscode from "vscode"; ... export async function activate(context: vscode.ExtensionContext) { ... // 啟動(dòng)完成 } ...
通常啟動(dòng)之前的時(shí)間要比VS Code正在運(yùn)行的擴(kuò)展頁(yè)面中顯示的啟動(dòng)時(shí)間長(zhǎng)。比如以熱啟動(dòng)打開(kāi)物聯(lián)網(wǎng)項(xiàng)目工作區(qū)的啟動(dòng)時(shí)間在表中是517毫秒,但是VS Code正在運(yùn)行的擴(kuò)展頁(yè)面中大約是200毫秒。
典型的webpack解決方案中,啟動(dòng)時(shí)間只有啟動(dòng)模式有關(guān),因?yàn)樗心K都總是以同樣的方式被加載。當(dāng)在Azure IoT Device Workbench中應(yīng)用懶加載時(shí),無(wú)論是否使用打包模塊,沒(méi)有工作區(qū)時(shí)啟動(dòng)速度都遠(yuǎn)快于打開(kāi)物聯(lián)網(wǎng)工作區(qū)。當(dāng)我們打開(kāi)物聯(lián)網(wǎng)項(xiàng)目工作區(qū)時(shí),大部分模塊都被引用,懶加載帶來(lái)的優(yōu)勢(shì)不是很明顯,所以懶加載打包模塊和典型webpack解決方案有相近的啟動(dòng)時(shí)間。
結(jié)論在這篇文章中,提出了一種按需加載打包模塊的方法。一款叫做Azure IoT Device Workbench的繁重?cái)U(kuò)展被用來(lái)在多種情況下測(cè)試這個(gè)方法。并且它的啟動(dòng)速度被提升了數(shù)十倍。在某些情況下,這個(gè)方法比典型的webpack方案帶來(lái)了更優(yōu)異的性能提升。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109512.html
摘要:我覺(jué)得這方面的原因是當(dāng)時(shí)對(duì)和的依賴(lài),導(dǎo)致大家對(duì)的興趣不弄,錯(cuò)過(guò)了最佳時(shí)機(jī),這個(gè)其實(shí)跟百度自己的的技術(shù)棧有很大關(guān)系。這個(gè)阮一峰對(duì)于前端構(gòu)建的變化吐槽過(guò),說(shuō)新的構(gòu)建工具就是的構(gòu)建工具。 文章來(lái)源 最近幾年,前端發(fā)展越來(lái)越迅速,各種萌新加入了前端這個(gè)大家庭,大有趕IOS、超Android的趨勢(shì)呀!同時(shí),萌新們提出了各種前端工作問(wèn)題,除了最基礎(chǔ)的html、css、js三板斧之外,最讓人頭疼的應(yīng)...
摘要:我覺(jué)得這方面的原因是當(dāng)時(shí)對(duì)和的依賴(lài),導(dǎo)致大家對(duì)的興趣不弄,錯(cuò)過(guò)了最佳時(shí)機(jī),這個(gè)其實(shí)跟百度自己的的技術(shù)棧有很大關(guān)系。這個(gè)阮一峰對(duì)于前端構(gòu)建的變化吐槽過(guò),說(shuō)新的構(gòu)建工具就是的構(gòu)建工具。 文章來(lái)源 最近幾年,前端發(fā)展越來(lái)越迅速,各種萌新加入了前端這個(gè)大家庭,大有趕IOS、超Android的趨勢(shì)呀!同時(shí),萌新們提出了各種前端工作問(wèn)題,除了最基礎(chǔ)的html、css、js三板斧之外,最讓人頭疼的應(yīng)...
摘要:個(gè)人還是更加習(xí)慣于斷點(diǎn)調(diào)試。這篇文章將介紹如何配置和來(lái)完成直接在斷點(diǎn)調(diào)試代碼并且在的調(diào)試窗口看到中相同的值?,F(xiàn)在就可以在文件的代碼中打斷點(diǎn)進(jìn)行調(diào)試了。 很多人習(xí)慣在 Chrome 的調(diào)試窗口中調(diào)試 Vue 代碼, 或者直接使用 console.log 來(lái)觀察變量值, 這是非常痛苦的一件事,需要同時(shí)打開(kāi)至少 3 個(gè)窗體。個(gè)人還是更加習(xí)慣于斷點(diǎn)調(diào)試。這篇文章將介紹如何配置 Visual S...
摘要:知乎上也有相關(guān)的討論,開(kāi)發(fā)的下一代編輯器莫非已經(jīng)定義為上一代編輯器了嗎。 這篇是我在知乎的回答,原文在這里:justjavac: VS Code、ATOM這些開(kāi)源文本編輯器的代碼實(shí)現(xiàn)中有哪些奇技淫巧? 研究 V8 比較多,也關(guān)注了一下 vscode 和 atom 的性能,每次 vscode、atom 的 change log 我都會(huì)看一遍。印象最深的是 vscode 1.14 的一次更...
閱讀 3992·2021-11-18 13:21
閱讀 4803·2021-09-27 14:01
閱讀 3121·2019-08-30 15:53
閱讀 2396·2019-08-30 15:43
閱讀 1741·2019-08-30 13:10
閱讀 1522·2019-08-29 18:39
閱讀 897·2019-08-29 15:05
閱讀 3351·2019-08-29 14:14