实现前端插件化架构设计,将需求开发「交给别人」
实现前端插件架构设计,“给别人”开发需求://github.com/ericlee33背景业务开发中遇到的问题作者现就职于中台部门。平日业务开发中,经常会遇到业务方有需求过来,需要中台方协助定制开发的场景,使用业务插件完成业务方需要的功能。作为一个前端开发,如果不加思索,很容易陷入业务。其实是业务方自己的需求,只是需要我们帮助开发。这个时候,我们就需要想一个方案,把自己从需求的黑洞里抽出来。为什么不让业务方做插件开发者,自己开发呢?有的同学可能会问,直接让需求方在中期项目仓库开发不是很好吗?业务方可以自己开发吗?听起来是一种方式,但是当项目结构非常庞大时,比如由多个package组成的monorepo项目,插件开发者会遇到以下问题:很难快速理清中台的代码逻辑项目。安装项目依赖,在本地启动项目,尤其是项目比较大的时候,第一次在本地编译项目可能需要20分钟甚至更长时间。对于商科学生来说,简直是一种痛苦。中台项目会有代码规范和插件开发。开发人员可能无法直接满足项目设置的代码规范。MR的时候需要中前端同学帮忙做CodeReview,也会消耗中台同学的精力。如果插件开发者的代码逻辑有问题,可能会导致中间出现问题台湾项目在线白屏target整体流程的自研过程中,其实会有很大的痛点。插件开发人员必须深入到我们的项目中才能了解内部实现细节。而且项目编译启动需要很长时间??如果有办法提供插件模板给插件开发者,利用我们平台的在线环境远程加载业务方本地开发的模板代码同学们,让业务方进行本地开发插件,这样是不是节省了让业务方了解我们平台代码的成本?拆解过程我们来梳理一下。如果我们需要实现上述流程,我们需要具备以下能力:在React项目中,实现远程组件调用有人可能会问,React不是可以通过import()语法来动态调用组件吗?这不可能吗?在这种情况下,就会出现问题。例如,react只允许单个实例。插件组件打包时,需要配置externals,避免两次调用。这样的话,就会出现无法正常注入依赖的问题。如果有同学对这方面感兴趣,我以后会继续写一篇关于这方面的文章。在线环境调试本地调试代码如果我们可以通过某种方式代理到在线环境接口,劫持接口的返回值,注入本地远程组件对应的url地址,是否就相当于实现了在线环境调试本地调试代码这个黑魔法?插件模板生成这里为了让开发者有更好的体验,我们可以开发一个脚手架,根据开发者的不同需求生成不同的模板。插件产品在这里打包上传,思路如下:实现一个插件开发者后台,统一插件管理、版本控制、上传操作实现VSCode插件,让开发者使用全流程闭环的技术方案VSCode中的上述想法。开展技术方案研究。远程组件介绍在平台项目中,需要引入动态加载打包好的远程组件。这里我们使用npm包remote-component来解决这个问题。GitHub地址:https://github.com/Paciolan/r...whatIstheremotecomponent这里引用npmpackagedocumentation说明远程组件是在运行时从一个URL加载的。与其他React组件使用方法相同远程组件使用方法consturl="https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js";constHelloWorld=({name})=>;常量容器=(<>>);在线环境调试本地调试代码实现为此,我们需要实现以下两点:搭建代理服务器劫持浏览器请求,让所有浏览器请求都发送到代理服务器。要构建代理服务器,我们使用Anyproxy包和文档浏览器代理插件。我们使用SwitchOmega,浏览器可以使用它的Proxy插件地址:https://chrome.google.com/web...插件模板脚手架我们可以使用commander/inquirer/chalk等npm包来设计和开发脚手架工具,插件产品,打包上传插件开发者如果后台是这样实现的,那么我们需要在页面中提供如下功能:添加插件删除插件产品上传插件发布插件版本回滚VSCode插件VSCode插件开发实现功能与后台类似,但可以在后台移除登录,并本地打包,然后拖拽产品到后台再上传繁琐的流程插件开发API文档:https://code.visualstudio.com...流程图开发者视角
平台维护者视角<divalign="center">
具体实现项目结构这里我们使用pnpm搭建了一个最小的骨架,包括了我们实现黑魔法所需要的各个子项目。├──README.md├──package.json├──packages│├──anyproxy-server//代理服务器,端口11111,劫持浏览器请求│├──egg-server//后台服务,这里提供demo的一个模拟接口,供代理服务器代理注入本地模板项目打包产品js对应的url│├──platform//平台项目│└──plugin-page//插件项目├──pnpm-lock.yaml├──pnpm-workspace.yaml└──tsconfig.json创建后台服务为了展示demo的效果,我们启动一个egg的小后台,只提供一个/api/plugin_detail接口平台项目获取远程组件的URL。我们暂时把返回的数据写成{},后面的远程组件的url会被代理服务器劫持并提供,相当于模拟实现。代理在线接口调试本地代码。import{Service}from'egg';/***TestService*/exportdefaultclassTestextendsService{/***sayHitoyou*@paramname-你的名字*/publicasyncsayHi(){return{代码:0,数据:{},消息:'ok',};}}开发代理服务器包这里我直接提供代理服务器核心代码,我们设置代理服务器运行在本地11111端口,这里我们进入代理后台提供的/api/plugin_detail接口。返回的时候,我们把打包后的产品url添加到本地的模板工程中。这里我们的url是http://localhost:9001/main.bundle.js,后面我会解释这个url是怎么来的constAnyProxy=require('anyproxy');construle=require('./rule/index');constDEFAULT_PORT=11111;constproxyServer=newAnyProxy.ProxyServer({rule:{beforeSendResponse:(requestDetail,responseDetail)=>{if(requestDetail.url.includes('/api/plugin_detail')){constdata=JSON.解析(responseDetail.response.body.toString());data.data.url='http://localhost:9001/main.bundle.js';responseDetail.response.body=JSON.stringify(数据);返回responseDetail;}else{returnresponseDetail;}},},port:DEFAULT_PORT,throttle:10000,forceProxyHttps:true,wsIntercept:false,//不开启websocket代理silent:false,});proxyServer.start();配置浏览器代理插件安装好浏览器代理插件后,我们启动插件,点击添加一个新的配置文件,我们随意命名,命名为test_local,设置从代理到本地代理的端口服务器,这里我们使用上面设置的端口11111完成这一步后,我们就可以将浏览器的请求发送到我们本地的代理服务器了。注意在BypassList中一定要配置<-loopback>,否则代理无法到达这里的本地接口,前期工作准备好了,下面开始开发平台项目,插件页面FAQ。如果代理网站时网站不可信,则需要信任anyproxy的证书。证书的位置对于不同的系统是不同的。例如,MacOS在以下目录中./.anyproxy/certificates/rootCA。crt开发插件要写一个demo插件页面,我们先写一个最小的插件模板,这里直接写死,其实可以用脚手架importReactfrom'react生成一个插件工程模板';import'./test.css';constPlugin:React.FC<{}>=()=>{return(
这是一个远程加载的插件组件 );};exportdefaultPlugin;webpack配置这里我摘录了几行核心配置,主要需要注意的是libraryTarget需要是commonjs格式的,我们需要配置外部组件以避免包装对插件产品产生反应。为了让demo运行起来,我们给devServer配置一个跨域header为*,并运行在9001端口,这样本地插件产品的js文件地址为http://localhost:9001/main.bundle.js输出:{文件名:'[name].bundle.js',libraryTarget:'commonjs',},externals:{react:'react',},devServer:{hot:true,端口:9001,headers:{'Access-Control-Allow-Origin':'*',},},在平台项目中,配置远程组件注入远程组件所需的依赖。由于我们设置了remote组件的externals属性来避免打包react,所以我们需要在platform项目中提供react依赖给plugin模板,这样plugin模板就可以引用react包的依赖/***DependenciesforRemote组件*/module.exports={resolve:{react:require("react")}};我这里贴上webpack配置的代码,主要核心点就是我们需要在webpack.config.js中添加一个Webpack别名,这样RemoteComponent才能加载这个文件。module.exports={resolve:{alias:{"remote-component.config.js":__dirname+"/remote-component.config.js"}}};平台调用远程组件的方式这里我调用的是我们小后台提供的/api/plugin_detail接口,该接口本身不返回url地址,但是当我们开启代理服务器时,我们可以获取到打包后的产品地址本地插件组件importReact,{useEffect,useState}from'react';import{RemoteComponent}from'@paciolan/remote-component';import{Card}from'antd';interfaceIPlatFormEntryProps{}constPlatFormEntry:React.FC
=()=>{const[url,setUrl]=useState('');const[loading,setLoading]=useState(true);useEffect(()=>{setTimeout(async()=>{constrawRes=awaitfetch('http://localhost:7001/api/plugin_detail',{method:'GET',});constres=awaitrawRes.json();consturl=res.data.url;setLoading(false);setUrl(url);},1000);},[]);constHelloWorld=(props)=>;返回({loading?Loading,加载远程组件....
:});};exportdefaultPlatFormEntry;整体流程联调及效果展示可参考项目源码具体实施。启动浏览器代理插件的源码我贴在了文末。我们选择刚才配置的test_local配置项,启动我们monorepo中的4个子项目进行平台项目目录,启动平台项目cdpackages/platformpnpmdev插件项目目录,启动插件项目cdpackages/plugin-pagepnpmdev代理服务器项目目录,启动代理服务cdpackages/anyproxy-serverpnpmdev后端项目目录,启动后端项目cdpackages/egg-serverpnpmdev查看效果访问首页平台工程看效果http://localhost:9000界面返回后,可以加载远程组件内容的demo代码仓库:https://github.com/ericlee33/...如果有帮助,请点Star??&followhttps://github.com/ericlee33欢迎关注前端江湖公众号,我会分享很多有趣的技术文章,希望能帮助更多的人深度参与在前端技术和k紧跟技术趋势