发布网友 发布时间:2024-09-30 08:30
共1个回答
热心网友 时间:2024-10-28 09:40
背景作为一位前端同学肯定对vscode不陌生,相信每位同学电脑上也都有五花八门的个性化配置,那么我们是借助什么东西做到的呢?那就是它丰富的插件生态。本次将讲述插件基本原理并从一个简单的case了解如何制作一个的vscode插件
是什么实现了vscodeElectronvscode底层通过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');2main:指定了插件的入口函数。
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