<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          vscode插件原理淺析與實戰(zhàn)

          共 12318字,需瀏覽 25分鐘

           ·

          2022-05-26 03:15

          術(shù)????



          背景

          作為一位前端同學(xué)肯定對vscode不陌生,相信每位同學(xué)電腦上也都有五花八門的個性化配置,那么我們是借助什么東西做到的呢?那就是它豐富的插件生態(tài)。本次將講述插件基本原理并從一個簡單的case了解如何制作一個的vscode插件

          是什么實現(xiàn)了vscode

          Electron

          vscode底層通過electron開發(fā)實現(xiàn),electron的核心構(gòu)成分別是:chromium、nodejs、native-api

          Chromium( ui 視圖) :通過web技術(shù)棧編寫實現(xiàn)ui界面,其與chrome的區(qū)別是開放開源、無需安裝可直接使用(可以簡單理解chromium是beta體驗版chrome,新特性會優(yōu)先在chromium中體驗并在穩(wěn)定后更新至chrome中)。

          Nodejs (操作桌面文件系統(tǒng)) :通過node-gyp編譯,主要用來操作文件系統(tǒng)和調(diào)用本地網(wǎng)絡(luò)。

          Native-API(操作系統(tǒng)緯度 api :使用Nodejs-C++ Addon調(diào)用操作系統(tǒng)API(Nodejs-C++ Addon插件是一種動態(tài)鏈接庫,采用C/C++語言編寫,可以通過require()將插件加載進NodeJS中進行使用),可以理解是對Nodejs接口的能力拓展。

          Electron 多進程

          • 主進程(main):每一個Electron應(yīng)用只會啟動一個主進程。
          • 渲染進程(render):主進程會通過Chromium的api創(chuàng)建任意多個web頁面,每一個工作區(qū)(workbench)對應(yīng)一個進程,同時是BrowserWindow實例,由于chromeium(chrome)也是多進程的,所以每個頁面都單獨運行在各自的渲染進程中。
          image.png

          例:

          //?主進程
          const?{?ipcMain?}?=?require('electron');

          //?主進程響應(yīng)事件
          ipcMain.on('main_msg',?(event,?arg)?=>?{
          ??console.log(arg);?//?ping
          ??event.reply('renderer-msg-reply',?'pong');
          })
          //?渲染進程(子進程)
          const?{?ipcRenderer?}?=?require('electron');

          //?渲染進程響應(yīng)事件
          ipcRenderer.on('renderer-msg-reply',?(event,?arg)?=>?{
          ??console.log(arg);?//?pong
          })

          //?觸發(fā)主進程響應(yīng)事件
          ipcRenderer.send('main_msg',?'ping');

          對于 vscode 還會有一些其他的進程,比如:

          • 插件進程(Extension):fork渲染進程,每個插件都運行在一個NodeJS宿主環(huán)境中,即插件間共享進程
          • Debug進程:一個特殊的插件進程。
          • Search進程:搜索是密集型任務(wù),單獨占用一個進程。
          • 。。。

          通俗意義上, electron 就是給你搞了一個Chrome瀏覽器的殼子,只是比傳統(tǒng)網(wǎng)頁多了一個訪問桌面文件的功能。

          vscode插件加載基本原理

          https://github.com/microsoft/vscode/tree/main

          插件的結(jié)構(gòu)

          ├──?extensions----------------------------------vscode內(nèi)置插件
          ├──?src
          │???├──?main.js--------------------------------入口文件
          │???├──?bootstrap-fork.js----------------------衍生子進程(渲染進程)
          │???├──?vs
          │???│???└──?workbench-------------------------工作臺
          │???│???├──?base
          │???│???│???├──?browser----------------------瀏覽器api,可操作dom
          │???│???│???├──?common-----------------------公共js代碼
          │???│???│???├──?node-------------------------nodejs?api
          │???│???├──?code
          │???│???│???├──?electron-browser-------------electron渲染進程
          │???│???│???├──?electron-main----------------electron主進程

          插件加載過程

          初始化插件服務(wù)

          在插件初始化構(gòu)造函數(shù)中通過_initialize初始化插件服務(wù)。

          //?src/vs/workbench/services/extensions/electron-browser/extensionService.ts
          //?通過監(jiān)聽生命周期函數(shù),創(chuàng)建ExtensionHostManager
          export?class?ExtensionService?extends?AbstractExtensionService?implements?IExtensionService?{
          ????constructor()?{
          ????????this._lifecycleService.when(LifecyclePhase.Ready).then(()?=>?{
          ????????????//?reschedule?to?ensure?this?runs?after?restoring?viewlets,?panels,?and?editors
          ????????????runWhenIdle(()?=>?{
          ????????????????this?._initialize()?;?//?初始化插件服務(wù)
          ????????????},?50?/*max?delay*/);
          ????????});
          ????}
          }

          //?src/vs/workbench/services/extensions/common/abstractExtensionService.ts
          //?啟動初始化插件服務(wù)方法
          protected?async?_initialize():?Promise?{
          ????perf.mark('code/willLoadExtensions');
          ????this?._startExtensionHosts(?true?,?[])?;
          ????//?...
          }

          private?_startExtensionHosts(isInitialStart:?boolean,?initialActivationEvents:?string[]):?void?{
          ????//?創(chuàng)建插件進程,分別為LocalProcessExtensionHost(本地插件,如個人插件)、RemoteExtensionHost(遠程插件,如WSL?Remote)、WebWorkerExtensionHost(web?worker進程)
          ????const?extensionHosts?=?this._createExtensionHosts(isInitialStart);
          ????extensionHosts.forEach((extensionHost)?=>?{
          ????????//?創(chuàng)建ExtensionHostManager
          ????????const?processManager:?IExtensionHostManager?=?createExtensionHostManager(this._instantiationService,?extensionHost,?isInitialStart,?initialActivationEvents,?this._acquireInternalAPI());
          ????????processManager.onDidExit(([code,?signal])?=>?this._onExtensionHostCrashOrExit(processManager,?code,?signal));
          ????????processManager.onDidChangeResponsiveState((responsiveState)?=>?{?this._onDidChangeResponsiveChange.fire({?isResponsive:?responsiveState?===?ResponsiveState.Responsive?});?});
          ????????this._extensionHostManagers.push(processManager);
          ????});
          }
          fork渲染進程

          fork渲染進程,并加載 extensionHostProcess。由于vscode考慮插件可能會影響啟動性能和IDE自身的穩(wěn)定性,所以通過進程隔離來解決這個問題,插件進程fork渲染進程,保證每個插件都運行在一個nodejs宿主環(huán)境中,不影響IDE及其啟動時間。

          //?src/vs/workbench/services/extensions/common/extensionHostManager.ts
          //?啟動fork渲染進程
          class?ExtensionHostManager?extends?Disposable?{
          ??constructor()?{
          ??????this._proxy?=?this._extensionHost.start()?!.then();
          ??}
          }
          //?src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts
          class?LocalProcessExtensionHost?implements?IExtensionHost?{
          ??public?start():?Promise?|?null?{
          ????//?...
          ????const?opts?=?{
          ??????env:?objects.mixin(objects.deepClone(process.env),?{
          ????????//?加載插件進程,指明插件進程入口
          ????????AMD_ENTRYPOINT:?'vs/workbench/services/extensions/node/extensionHostProcess',
          ??????}),
          ????}

          ????//?衍生子進程(渲染進程)
          ????this._extensionHostProcess?=?fork(getPathFromAmdModule(require,?'bootstrap-fork'),?['--type=extensionHost'],?opts);
          ??}
          }
          初始化插件激活邏輯
          //?src/vs/workbench/services/extensions/node/extensionHostProcess.ts
          import?{?startExtensionHostProcess?}?from?"vs/workbench/services/extensions/node/extensionHostProcessSetup";
          startExtensionHostProcess().catch((err)?=>?console.log(err));

          //?src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
          export?async?function?startExtensionHostProcess():?Promise?{
          ????const?extensionHostMain?=?new?ExtensionHostMain(
          ????????renderer.protocol,
          ????????initData,
          ????????hostUtils,
          ????????uriTransformer
          ????);
          }

          //?src/vs/workbench/services/extensions/common/extensionHostMain.ts
          export?class?ExtensionHostMain?{
          ??constructor()?{
          ????//?必須在創(chuàng)建extensionService之后再調(diào)用initialize,因為initialize本身會依賴extensionService的實例
          ????this._extensionService?=?instaService.invokeFunction(accessor?=>?accessor.get(IExtHostExtensionService));
          ????this._extensionService.initialize();
          ??}
          }
          插件激活
          //?src/vs/workbench/api/node/extHost.services.ts
          import?{?ExtHostExtensionService?}?from?'vs/workbench/api/node/extHostExtensionService';

          //?注冊插件服務(wù)
          registerSingleton(IExtHostExtensionService,?ExtHostExtensionService);

          繼承AbstractExtHostExtensionService

          //?src/vs/workbench/api/node/extHostExtensionService.ts
          export?class?ExtHostExtensionService?extends?AbstractExtHostExtensionService?{
          ????//?...
          }
          //?src/vs/workbench/api/common/extHostExtensionService.ts
          abstract?class?AbstractExtHostExtensionService?extends?Disposable?{
          ??constructor()?{
          ????this._activator?=?this._register(new?ExtensionsActivator());
          ??}

          ??//?根據(jù)activationEvent事件名激活插件,如onCommand
          ??private?_activateByEvent(activationEvent:?string,?startup:?boolean):?Promise?{
          ????return?this._activator.activateByEvent(activationEvent,?startup);
          ??}
          }
          加載流程
          image.png

          簡單實戰(zhàn)

          背景:實現(xiàn)選擇指定目錄右鍵自動生成lynx頁面基本目錄結(jié)構(gòu)的插件。

          目標拆解:

          • 選擇自定義目錄,添加右鍵點擊菜單
          • 輸入lynx頁面名稱
          • 按照模版生成對應(yīng)文件

          環(huán)境準備

          • nodejs
          • vscode
          • 安裝Yeoman[1]VS Code Extension Generator[2]

            • npm?install?-g?yo?generator-code
          • 初始化項目工程

            • yo?code

          具體實現(xiàn)

          //?package.json
          {
          ?"name":?"lynxlowcode",
          ?"displayName":?"LynxLowcode",
          ?"description":?"",
          ?"version":?"0.0.2",
          ?"engines":?{
          ??"vscode":?"^1.62.0"
          ?},
          ?"categories":?[
          ??"Other"
          ?],
          ?"activationEvents":?[
          ??"onCommand:lynxlowcode.newLynxComponent"
          ?],
          ?"main":?"./out/extension.js",
          ?"contributes":?{
          ??"commands":?[
          ???{
          ????"command":?"lynxlowcode.newLynxComponent",
          ????"title":?"新建Lynx組件"
          ???}
          ??],
          ??"menus":?{
          ???"explorer/context":?[
          ????{
          ?????"command":?"lynxlowcode.newLynxComponent",
          ?????"group":?"z_commands",
          ?????"when":?"explorerResourceIsFolder"
          ????}
          ???]
          ??}
          ?},
          ?"scripts":?{
          ??"vscode:prepublish":?"npm?run?compile",
          ??"compile":?"tsc?-p?./",
          ??"watch":?"tsc?-watch?-p?./",
          ??"pretest":?"npm?run?compile?&&?npm?run?lint",
          ??"lint":?"eslint?src?--ext?ts",
          ??"test":?"node?./out/test/runTest.js"
          ?},
          ?"devDependencies":?{
          ??"@types/fs-extra":?"^9.0.13",
          ??"@types/glob":?"^7.1.4",
          ??"@types/mocha":?"^9.0.0",
          ??"@types/node":?"14.x",
          ??"@types/vscode":?"^1.62.0",
          ??"@typescript-eslint/eslint-plugin":?"^4.31.1",
          ??"@typescript-eslint/parser":?"^4.31.1",
          ??"@vscode/test-electron":?"^1.6.2",
          ??"eslint":?"^7.32.0",
          ??"fs-extra":?"^10.0.1",
          ??"glob":?"^7.1.7",
          ??"mocha":?"^9.1.1",
          ??"typescript":?"^4.4.3"
          ?},
          ?"dependencies":?{
          ??"import":?"^0.0.6",
          ??"path":?"^0.12.7"
          ?}
          }

          main:指定了插件的入口函數(shù)。

          activationEvents:指定觸發(fā)事件,當指定事件發(fā)生時才觸發(fā)插件執(zhí)行。需額外關(guān)注*這個特殊的插件類型,因為他在初始化完成后就會觸發(fā)插件執(zhí)行,并不需要任何自定義觸發(fā)事件。

          contributes:描述插件的拓展點,用于定義插件要擴展 vscode 哪部分功能,如commands命令面板、menus資源管理面板等。

          1. 聲明指令

          初始化插件項目成功后會看到上圖的目錄結(jié)構(gòu),其中我們需要重點關(guān)注src目錄和package.json文件,其中src目錄下的extension.ts文件為入口文件,包含activatedeactivate分別作為插件啟動和插件卸載時的生命周期函數(shù),可以將邏輯直接寫在兩個函數(shù)內(nèi)也可抽象后在其中調(diào)用。

          同時我們希望插件在適當?shù)臅r機啟動activate或關(guān)閉deactivate,vscode也給我們提供了多種onXXX的事件作為多種執(zhí)行時機的入口方法。那么我們該如何使用這些事件呢?

          • 事件列表
          //?當打開特定語言時,插件被激活
          onLanguage
          //?當調(diào)用命令時,插件被激活
          onCommand
          //?當調(diào)試時,插件被激活
          onDebug
          //?打開文件夾且該文件夾包含設(shè)置的文件名模式時,插件被激活
          workspaceContains
          //?每當讀取特定文件夾?or?文件時,插件被激活
          onFileSystem
          //?在側(cè)邊欄展開指定id的視圖時,插件被激活
          onView
          //?在基于vscode或?vscode-insiders協(xié)議的url打開時(類似schema),插件被激活
          onUri
          //?在打開自定義設(shè)置viewType的?webview?時,插件被激活
          onWebviewPanel
          //?在打開自定義設(shè)置viewType的自定義編輯器,插件被激活
          onCustomEditor
          //?每當擴展請求具有authentication.getSession()匹配的providerId時,插件被激活
          onAuthenticationRequest
          //?在vscode啟動一段時間后,插件被激活,類似?*?但不會影響vscode啟動速度
          onStartupFinished
          //?在所有插件都被激活后,插件被激活,會影響vscode啟動速度,不推薦使用
          *

          如何使用這些事件呢?我們以onCommand為例。首先需要在package.json文件中注冊activationEventscommands

          {
          ????"activationEvents":?[
          ??????"onCommand:lynxlowcode.newLynxComponent"?//?注冊命令事件
          ?????],
          ????"contributes":?{
          ??????//?標識命令增加了哪些功能
          ??????"commands":?[
          ???????{
          ????????"command":?"lynxlowcode.newLynxComponent",
          ????????"title":?"新建Lynx組件"?//?可根據(jù)title使用command?+?shift?+?p進行搜索
          ???????}
          ??????]
          ?????}
          ?//?...
          }

          然后在extension.ts文件的activate方法中編寫自定義邏輯。

          //?extension.ts
          import?*?as?vscode?from?'vscode';

          //?this?method?is?called?when?your?extension?is?activated
          export?function?activate(context:?vscode.ExtensionContext)?{
          ??//?為命令添加事件
          ??let?init?=?vscode.commands.registerCommand('lynxlowcode.newLynxComponent',?()?=>?{
          ????newLynxComponent();?//?此處是我們的自定義邏輯
          ??});
          ??//?事件回調(diào)棧
          ??context.subscriptions.push(init);
          }

          //?this?method?is?called?when?your?extension?is?deactivated
          export?function?deactivate()?{}
          1. 添加目錄右鍵點擊事件
          //?package.json
          {
          ?//?...
          ?"menus":?{
          ???"explorer/context":?[
          ????{
          ?????"command":?"lynxlowcode.newLynxComponent",
          ?????"group":?"z_commands",?//?位于命令容器面板
          ?????"when":?"explorerResourceIsFolder"?//?資源管理器為目錄
          ????}
          ???]
          ??}
          }
          1. 喚起組件名稱輸入面板
          //?extension.ts
          import?*?as?vscode?from?'vscode';
          import?{?openInputBox?}?from?'./openInputBox';

          //?this?method?is?called?when?your?extension?is?activated
          export?function?activate(context:?vscode.ExtensionContext)?{
          ??let?newLynxComponent?=?vscode.commands.registerCommand('lynxlowcode.newLynxComponent',?(file:?vscode.Uri)?=>?{
          ????/**?喚起輸入框?*/
          ????openInputBox(file);
          ??});
          ??context.subscriptions.push(newLynxComponent);
          }

          //?this?method?is?called?when?your?extension?is?deactivated
          export?function?deactivate()?{}
          //?openInputBox.ts
          import?{?window,?InputBoxOptions,?InputBox,?Uri?}?from?'vscode';
          import?{?pathExists?}?from?'fs-extra';
          import?{?join?}?from?'path';
          import?{?createTemplate?}?from?'./createTemplate';

          /**
          ?*?喚起輸入組件名稱面板
          ?*/
          export?const?openInputBox?=?(file:?Uri):?void?=>?{
          ??/**?新建輸入框?qū)ο?*/
          ??const?inputBox?=?window.createInputBox();

          ??/**?配置placeholder?*/
          ??inputBox.placeholder?=?'請輸入你的組件名稱,按Enter確認';

          ??/**?獲取輸入框的值?*/
          ??const?inputValue?=?inputBox.value;

          ??/**?input值更新回調(diào)?*/
          ??inputBox.onDidChangeValue(async?(value:?string)?=>?{
          ????/**?判斷輸入的名稱是否為空?*/
          ????if?(value.length???????return?'組件名稱不能為空!!!';
          ????}

          ????/**?獲取最終組件完整路徑?*/
          ????const?location?=?join(file.fsPath,?value);

          ????/**?判斷該完整路徑是否已經(jīng)存在?*/
          ????if?(await?pathExists(location))?{
          ??????return?`該?${location}路徑已經(jīng)存在,請換一個名稱或路徑!!!`;
          ????}
          ??}),

          ??/**?input框隱藏回調(diào)?*/
          ??inputBox.onDidHide(()?=>?{
          ????/**?重置輸入框值?*/
          ????inputBox.value?=?'';

          ????/**?重置為可用?*/
          ????inputBox.enabled?=?true;

          ????/**?重置為空閑?*/
          ????inputBox.busy?=?false;
          ??});

          ??/**?確認回調(diào)?*/
          ??inputBox.onDidAccept(async?()?=>?{
          ????/**?禁用輸入框,防止用戶再次輸入?*/
          ????inputBox.enabled?=?false;

          ????/**?將輸入框置為繁忙,等待最終創(chuàng)建結(jié)果?*/
          ????inputBox.busy?=?true;

          ????const?result?=?createTemplate();

          ????if(result)?{
          ??????inputBox.hide();
          ??????window.showInformationMessage('創(chuàng)建成功成功,請查看!!!');
          ????}?else?{
          ??????window.showInformationMessage('創(chuàng)建失敗,請重試!!!');
          ????}
          ????inputBox.enabled?=?true;
          ????inputBox.busy?=?false;
          ??});

          ??/**?展示input輸入框?*/
          ??inputBox.show();
          };
          1. 根據(jù)輸入面板創(chuàng)建模版文件
          import?fs?from?'fs';
          /**
          ?*?創(chuàng)建模版文件
          ?*/
          export?const?createTemplate?=?(location:?string,?name:?string)?=>?{
          ??/**?同步創(chuàng)建文件夾?*/
          ??const?mkdirResult?=?fs.mkdirSync(location,?{
          ????recursive:?true
          ??});

          ??/**?創(chuàng)建文件夾失敗?*/
          ??if?(!mkdirResult)?{
          ????return?false;
          ??}
          ??try?{
          ????/**?新建tsx文件并寫入內(nèi)容?*/
          ????fs.writeFileSync(`${location}/index.tsx`,?`
          import?{?Component?}?from?'@byted-lynx/react-runtime';
          import?'./index.scss';

          interface?${name}PropsType?{}

          interface?${name}StateType?{}

          export?default?class?${name}?extends?Component<${name}PropsType,?${name}StateType>?{
          ??constructor(props:?${name}PropsType)?{
          ????super(props);
          ????this.state?=?{};
          ??}
          ??render():?JSX.IntrinsicElements?{
          ????return?(
          ??????
          ????????${name}組件
          ??????

          ????);
          ??}
          }
          ??`);
          ??/**?新建scss文件?*/
          ??fs.writeFileSync(`${location}/index.scss`,?'');
          ??return?true;
          ??}?catch?(e)?{
          ????console.log(e);
          ????return?false;
          ??}
          };

          可優(yōu)化點

          1. 增加模版類型
          2. 通過下載模版替代寫入字符串文本

            ?? 謝謝支持

            以上便是本次分享的全部內(nèi)容,希望對你有所幫助^_^

            喜歡的話別忘了?分享、點贊、收藏?三連哦~。

            歡迎關(guān)注公眾號 前端Sharing?收獲大廠一手好文章~

            參考資料

            [1]

            Yeoman: https://yeoman.io/

            [2]

            VS Code Extension Generator: https://www.npmjs.com/package/generator-code



            瀏覽 41
            點贊
            評論
            收藏
            分享

            手機掃一掃分享

            分享
            舉報
            評論
            圖片
            表情
            推薦
            點贊
            評論
            收藏
            分享

            手機掃一掃分享

            分享
            舉報
            <kbd id="afajh"><form id="afajh"></form></kbd>
            <strong id="afajh"><dl id="afajh"></dl></strong>
              <del id="afajh"><form id="afajh"></form></del>
                  1. <th id="afajh"><progress id="afajh"></progress></th>
                    <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    亚洲欧美内射 | 欧美一级视频 | 尤物视频黄 | 天天草精品 | 免费躁逼淫片特级国产 |