1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 12318字,需瀏覽 25分鐘

         ·

        2022-05-26 03:15

        術(shù)??堅(jiān)??



        背景

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

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

        Electron

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

        Chromium( ui 視圖) :通過web技術(shù)棧編寫實(shí)現(xiàn)ui界面,其與chrome的區(qū)別是開放開源、無需安裝可直接使用(可以簡單理解chromium是beta體驗(yàn)版chrome,新特性會(huì)優(yōu)先在chromium中體驗(yàn)并在穩(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插件是一種動(dòng)態(tài)鏈接庫,采用C/C++語言編寫,可以通過require()將插件加載進(jìn)NodeJS中進(jìn)行使用),可以理解是對(duì)Nodejs接口的能力拓展。

        Electron 多進(jìn)程

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

        例:

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

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

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

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

        對(duì)于 vscode 還會(huì)有一些其他的進(jìn)程,比如:

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

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

        vscode插件加載基本原理

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

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

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

        插件加載過程

        初始化插件服務(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
        //?啟動(dòng)初始化插件服務(wù)方法
        protected?async?_initialize():?Promise?{
        ????perf.mark('code/willLoadExtensions');
        ????this?._startExtensionHosts(?true?,?[])?;
        ????//?...
        }

        private?_startExtensionHosts(isInitialStart:?boolean,?initialActivationEvents:?string[]):?void?{
        ????//?創(chuàng)建插件進(jìn)程,分別為LocalProcessExtensionHost(本地插件,如個(gè)人插件)、RemoteExtensionHost(遠(yuǎn)程插件,如WSL?Remote)、WebWorkerExtensionHost(web?worker進(jìn)程)
        ????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渲染進(jìn)程

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

        //?src/vs/workbench/services/extensions/common/extensionHostManager.ts
        //?啟動(dòng)fork渲染進(jìn)程
        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),?{
        ????????//?加載插件進(jìn)程,指明插件進(jìn)程入口
        ????????AMD_ENTRYPOINT:?'vs/workbench/services/extensions/node/extensionHostProcess',
        ??????}),
        ????}

        ????//?衍生子進(jìn)程(渲染進(jìn)程)
        ????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,因?yàn)閕nitialize本身會(huì)依賴extensionService的實(shí)例
        ????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';

        //?注冊(cè)插件服務(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

        簡單實(shí)戰(zhàn)

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

        目標(biāo)拆解:

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

        環(huán)境準(zhǔn)備

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

          • npm?install?-g?yo?generator-code
        • 初始化項(xiàng)目工程

          • yo?code

        具體實(shí)現(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ā)事件,當(dāng)指定事件發(fā)生時(shí)才觸發(fā)插件執(zhí)行。需額外關(guān)注*這個(gè)特殊的插件類型,因?yàn)樗诔跏蓟瓿珊缶蜁?huì)觸發(fā)插件執(zhí)行,并不需要任何自定義觸發(fā)事件。

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

        1. 聲明指令

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

        同時(shí)我們希望插件在適當(dāng)?shù)臅r(shí)機(jī)啟動(dòng)activate或關(guān)閉deactivate,vscode也給我們提供了多種onXXX的事件作為多種執(zhí)行時(shí)機(jī)的入口方法。那么我們?cè)撊绾问褂眠@些事件呢?

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

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

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

        然后在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. 添加目錄右鍵點(diǎn)擊事件
        //?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?=?'請(qǐng)輸入你的組件名稱,按Enter確認(rèn)';

        ??/**?獲取輸入框的值?*/
        ??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)存在,請(qǐng)換一個(gè)名稱或路徑?。?!`;
        ????}
        ??}),

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

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

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

        ??/**?確認(rèn)回調(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)建成功成功,請(qǐng)查看!??!');
        ????}?else?{
        ??????window.showInformationMessage('創(chuàng)建失敗,請(qǐ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)化點(diǎn)

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

          ?? 謝謝支持

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

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

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

          參考資料

          [1]

          Yeoman: https://yeoman.io/

          [2]

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



          瀏覽 41
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          1. <strong id="7actg"></strong>
          2. <table id="7actg"></table>

          3. <address id="7actg"></address>
            <address id="7actg"></address>
            1. <object id="7actg"><tt id="7actg"></tt></object>
              日本污网站 | 女教师被强在办公室 | 国产骚话淫语叫床视频 | 99热国产精品 | 黄羞羞视频 | 91人妻人人澡人人爽 | 国产一级婬乱片 | 抖音成人毛片免费观看 | 少妇玉梅高潮久久久 | 少妇婷婷av电影在线 |