问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

vscode插件原理浅析与实战

发布网友 发布时间:2024-09-30 08:30

我来回答

1个回答

热心网友 时间:2024-10-28 09:40

背景

作为一位前端同学肯定对vscode不陌生,相信每位同学电脑上也都有五花八门的个性化配置,那么我们是借助什么东西做到的呢?那就是它丰富的插件生态。本次将讲述插件基本原理并从一个简单的case了解如何制作一个的vscode插件

是什么实现了vscodeElectron

vscode底层通过electron开发实现,electron的核心构成分别是:chromium、nodejs、native-api

Chromium(ui视图):通过web技术栈编写实现ui界面,其与chrome的区别是开放开源、无需安装可直接使用(可以简单理解chromium是beta体验版chrome,新特性会优先在chromium中体验并在稳定后更新至chrome中)。

Nodejs(操作桌面文件系统):通过node-gyp编译,主要用来操作文件系统和调用本地网络。

Native-API(操作系统纬度api):使用Nodejs-C++Addon调用操作系统API(Nodejs-C++Addon插件是一种动态链接库,采用C/C++语言编写,可以通过require()将插件加载进NodeJS中进行使用),可以理解是对Nodejs接口的能力拓展。

Electron多进程:

主进程(main):每一个Electron应用只会启动一个主进程。

渲染进程(render):主进程会通过Chromium的api创建任意多个web页面,每一个工作区(workbench)对应一个进程,同时是BrowserWindow实例,由于chromeium(chrome)也是多进程的,所以每个页面都单独运行在各自的渲染进程中。

例:

//主进程const{ipcMain}=require('electron');//主进程响应事件ipcMain.on('main_msg',(event,arg)=>{console.log(arg);//pingevent.reply('renderer-msg-reply','pong');})//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');

对于vscode还会有一些其他的进程,比如:

插件进程(Extension):fork渲染进程,每个插件都运行在一个NodeJS宿主环境中,即插件间共享进程

Debug进程:一个特殊的插件进程。

Search进程:搜索是密集型任务,单独占用一个进程。

。。。

通俗意义上,electron就是给你搞了一个Chrome浏览器的壳子,只是比传统网页多了一个访问桌面文件的功能。

vscode插件加载基本原理

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

插件的结构├──extensions----------------------------------vscode内置插件├──src│├──main.js--------------------------------入口文件│├──bootstrap-fork.js----------------------衍生子进程(渲染进程)│├──vs││└──workbench-------------------------工作台││├──base│││├──browser----------------------浏览器api,可操作dom│││├──common-----------------------公共js代码│││├──node-------------------------nodejsapi││├──code│││├──electron-browser-------------electron渲染进程│││├──electron-main----------------electron主进程插件加载过程初始化插件服务

在插件初始化构造函数中通过_initialize初始化插件服务。

//src/vs/workbench/services/extensions/electron-browser/extensionService.ts//通过监听生命周期函数,创建ExtensionHostManagerexportclassExtensionServiceextendsAbstractExtensionServiceimplementsIExtensionService{constructor(){this._lifecycleService.when(LifecyclePhase.Ready).then(()=>{//rescheletoensurethisrunsafterrestoringviewlets,panels,andeditorsrunWhenIdle(()=>{this._initialize();//初始化插件服务},50/*maxdelay*/);});}}//src/vs/workbench/services/extensions/common/abstractExtensionService.ts//启动初始化插件服务方法protectedasync_initialize():Promise<void>{perf.mark('code/willLoadExtensions');this._startExtensionHosts(true,[]);//...}private_startExtensionHosts(isInitialStart:boolean,initialActivationEvents:string[]):void{//创建插件进程,分别为LocalProcessExtensionHost(本地插件,如个人插件)、RemoteExtensionHost(远程插件,如WSLRemote)、WebWorkerExtensionHost(webworker进程)constextensionHosts=this._createExtensionHosts(isInitialStart);extensionHosts.forEach((extensionHost)=>{//创建ExtensionHostManagerconstprocessManager: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自身的稳定性,所以通过进程隔离来解决这个问题,插件进程fork渲染进程,保证每个插件都运行在一个nodejs宿主环境中,不影响IDE及其启动时间。

//src/vs/workbench/services/extensions/common/extensionHostManager.ts//启动fork渲染进程classExtensionHostManagerextendsDisposable{constructor(){this._proxy=this._extensionHost.start()!.then();}}//src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.tsclassLocalProcessExtensionHostimplementsIExtensionHost{publicstart():Promise<IMessagePassingProtocol>|null{//...constopts={env:objects.mixin(objects.deepClone(process.env),{//加载插件进程,指明插件进程入口AMD_ENTRYPOINT:'vs/workbench/services/extensions/node/extensionHostProcess',}),}//衍生子进程(渲染进程)this._extensionHostProcess=fork(getPathFromAmdMole(require,'bootstrap-fork'),['--type=extensionHost'],opts);}}初始化插件激活逻辑//src/vs/workbench/services/extensions/node/extensionHostProcess.tsimport{startExtensionHostProcess}from"vs/workbench/services/extensions/node/extensionHostProcessSetup";startExtensionHostProcess().catch((err)=>console.log(err));//src/vs/workbench/services/extensions/node/extensionHostProcessSetup.tsexportasyncfunctionstartExtensionHostProcess():Promise<void>{constextensionHostMain=newExtensionHostMain(renderer.protocol,initData,hostUtils,uriTransformer);}//src/vs/workbench/services/extensions/common/extensionHostMain.tsexportclassExtensionHostMain{constructor(){//必须在创建extensionService之后再调用initialize,因为initialize本身会依赖extensionService的实例this._extensionService=instaService.invokeFunction(accessor=>accessor.get(IExtHostExtensionService));this._extensionService.initialize();}}插件激活//src/vs/workbench/api/node/extHost.services.tsimport{ExtHostExtensionService}from'vs/workbench/api/node/extHostExtensionService';//注册插件服务registerSingleton(IExtHostExtensionService,ExtHostExtensionService);

继承AbstractExtHostExtensionService

//src/vs/workbench/api/node/extHostExtensionService.tsexportclassExtHostExtensionServiceextendsAbstractExtHostExtensionService{//...}//src/vs/workbench/api/common/extHostExtensionService.tsabstractclassAbstractExtHostExtensionServiceextendsDisposable{constructor(){this._activator=this._register(newExtensionsActivator());}//根据activationEvent事件名激活插件,如onCommandprivate_activateByEvent(activationEvent:string,startup:boolean):Promise<void>{returnthis._activator.activateByEvent(activationEvent,startup);}}加载流程简单实战

背景:实现选择指定目录右键自动生成lynx页面基本目录结构的插件。

目标拆解:

选择自定义目录,添加右键点击菜单

输入lynx页面名称

按照模版生成对应文件

环境准备

nodejs

vscode

安装Yeoman和VSCodeExtensionGenerator

//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');0

初始化项目工程

//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');1具体实现//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');2

main:指定了插件的入口函数。

activationEvents:指定触发事件,当指定事件发生时才触发插件执行。需额外关注*这个特殊的插件类型,因为他在初始化完成后就会触发插件执行,并不需要任何自定义触发事件。

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

声明指令

初始化插件项目成功后会看到上图的目录结构,其中我们需要重点关注src目录和package.json文件,其中src目录下的extension.ts文件为入口文件,包含activate和deactivate分别作为插件启动和插件卸载时的生命周期函数,可以将逻辑直接写在两个函数内也可抽象后在其中调用。

同时我们希望插件在适当的时机启动activate或关闭deactivate,vscode也给我们提供了多种onXXX的事件作为多种执行时机的入口方法。那么我们该如何使用这些事件呢?

事件列表

//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');3

如何使用这些事件呢?我们以onCommand为例。首先需要在package.json文件中注册activationEvents和commands。

//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');4

然后在extension.ts文件的activate方法中编写自定义逻辑。

//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');5添加目录右键点击事件//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');6唤起组件名称输入面板//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');7//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');8根据输入面板创建模版文件//渲染进程(子进程)const{ipcRenderer}=require('electron');//渲染进程响应事件ipcRenderer.on('renderer-msg-reply',(event,arg)=>{console.log(arg);//pong})//触发主进程响应事件ipcRenderer.send('main_msg','ping');9可优化点

增加模版类型

通过下载模版替代写入字符串文本

End

??谢谢支持

以上便是本次分享的全部内容,希望对你有所帮助^_^

喜欢的话别忘了?分享、点赞、收藏?三连哦~。

欢迎关注公众号?ELab团队?收获大厂一手好文章~

我们来自字节跳动,是旗下大力教育前端部门,负责字节跳动教育全线产品前端开发工作。

我们围绕产品品质提升、开发效率、创意与前沿技术等方向沉淀与传播专业知识及案例,为业界贡献经验价值。包括但不限于性能监控、组件库、多端技术、Serverless、可视化搭建、音视频、人工智能、产品设计与营销等内容。

欢迎感兴趣的同学在评论区或使用内推码内推到作者部门拍砖哦?

字节跳动校/社招投递链接:

https://job.toutiao.com/s/FCbeMRg

内推码:GDUVRJJ

原文:https://juejin.cn/post/7099838116789387295
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
梦见老朋友开火车 正版PSP和山寨PSP有什么大差距吗? 山寨PSP是啥意思? 关于山寨PSP 为啥PSP不能山寨 呵呵 请问下PSP有没有山寨的?哪里有卖 有没有山寨版的psp? 虾不能和什么吃 大青虾跟什么不能一起吃 第一次打卡无锡,有哪些值得品尝的美食? 抽丝剥茧:Electron与Node.js的奇葩Bug 桌面端|Electron 几个开发调试小技巧 electron会将chrome换成edge吗 怎么使electron支持所有chrome 接二连三倒霉怎么破解 接二连三倒霉怎么破解方法 飞猫智联M10能用5G网络吗? W7的壁纸在哪个文件夹啊 为什么我W7电脑每次开机都会显示"您有等待写入光盘的文件"但插入光盘却... ...一些文件看见放C盘里的 users/dell/AppData里了 但是我打开到dell以... 做梦,梦见自己的左手手掌上突然长了一种没见过的果实,吓死了,死命把它... ...删除C:\Users\dell\AppData\Roaming\Microsoft\Windows\Start Menu... ...Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu... ...打开C:\Users\roaming\AppData\Roaming\Microsoft\Windows\Network S... ...7的C:\用户\Default\AppData\Roaming\Microsoft\Internet 2021上海冬至吃什么冬至上海人桌上必备食物 C:\Users\Administrator\AppData\Roaming\这个我怎么找不到,各位谁能... 上海冬至吃什么 上海冬至吃什么东西 奇异果会员在哪领取 电视奇异果怎么领vip 为什么浏览器不能加载插件? 将网页变成桌面应用的6种方式 索泰N9600GSO-512D3 HD 米格版Green显卡接口 2款9600gso哪个游戏性能强? 我的CPU是AMD 5200+ 配个索泰N9600GSO-512D3米格版这个显卡可以不?性 ... AMD8450和影驰9600GSO加强版X2 512M 玩鬼泣4不流畅!怎么回事!高手帮看 ... ...com/MA7GSO42/谁知道这个视频里的插曲叫什么名字? appdate是哪个文件夹 appdate在哪里 多少分能被西昌学院录取 西昌学院在各省的录取分数线是多少? 砖瓦房建造成本 美的电风扇转到蛮快的,怎么就是没风的 电脑开机死机是怎么回事呢? 磨钨钢光洁度0.05um选择什么粒度金刚石? 微信号登不去怎么办,急,又手机号登也登不上了,怎么办啊? 肚子左下腹疼是怎么回事_百度拇指医生 女性肚子左下边疼怎么回事 每天晚上睡觉的时候肚子左下边会痛也就是肚脐左边会痛一下子,过一下... 肚子左下边疼怎么办 小肚子左下边疼是怎么回事啊 ...锁屏怎么设置华华p40刷机后锁屏无法设置怎么办?