前言

demo代码仓库: https://github.com/ericlee33/...

如果有帮忙到的话,欢送点个 star ⭐️ & follow https://github.com/ericlee33

背景

业务开发中遇到的问题

笔者当初在中台部门工作,素日业务开发时,常常遇到一个场景,业务方会提需要过去,须要中台方去帮助定制开发业务插件,以此来补齐业务方须要的性能。

作为前端开发,如果不加以思考,很容易就会陷入到业务之中,实际上是业务方本人的需要,然而却须要咱们来帮忙开发。

在这种时候,咱们须要思考一种计划,来把本人从需要黑洞中抽离进去。为什么不让业务方本人来作为插件开发者,本人开发呢?

有的同学可能会问,间接让需求方在中台我的项目仓库中去开发不就好了?

可不可以业务方自行开发呢?

听起来如同是一种方法,然而当我的项目构造很宏大,比方为多packages组成的monorepo我的项目,插件开发者就会遇到如下问题:

  • 很难疾速理清中台我的项目的代码逻辑
  • 开发前,须要先装置我的项目依赖、本地启动我的项目,尤其是在我的项目宏大的状况下,本地首次编译我的项目,可能须要20分钟甚至更多,对业务方同学来说,几乎是苦楚
  • 中台我的项目会有代码标准,插件开发者一上来也不肯定能间接满足我的项目设立的代码标准,MR的时候须要中台前端同学帮忙做Code Review,也会耗费中台同学的精力
  • 插件开发者的代码逻辑如果有问题,可能会导致中台我的项目线上白屏

指标

整体流程

业务方自行开发过程中,实际上会存在一个很大的痛点,插件开发者不得不深刻到咱们的我的项目中,去理解外部实现细节。并且须要很长时间编译我的项目能力启动

⭐️ 如果,有一种形式,能提供给插件开发者插件模板,并且能用咱们平台的线上环境去近程加载业务方同学本地开发的模板代码,让业务方进行本地开发插件,这样是不是就省去了让业务方了解我方平台代码的老本?

对流程进行拆解

让咱们整顿一下,如果须要实现下面这个流程,咱们须要以下能力

React我的项目中,实现近程组件调用

有人可能会问,React不是能够通过import()语法去动静调用组件吗,这样不能够吗?

这种状况会遇到一个问题,像react是只容许单实例的,插件组件打包时候,须要配置externals,防止两次调用,这样的话就会呈现依赖无奈失常注入的问题。如果有同学感兴趣这块的话后续我会持续写一篇对于这块的文章。

线上环境调试本地调试代码

咱们如果能做到通过某种形式,代理到线上环境接口,劫持接口返回值,来注入本地近程组件对应url地址,是不是就相当于实现了线上环境调试本地调试代码这种黑魔法

插件模板生成

这里为了让开发者体验更好,咱们能够开发一个脚手架,依据开发者不同的要求,去生成不同的模板

插件产物打包上传

这里不细讲,如果感兴趣我后续能够再更一篇文章,思路是有以下两种:

  • 实现一个插件开发者后盾,进行插件对立治理、版本控制、上传操作
  • 实现 VSCode 插件,让开发者在 VSCode 中,就能够将全流程闭环

技术计划

针对以上的想法,我进行了技术计划调研。

近程组件引入

在平台我的项目中,须要引入对打包好的近程组件进行动静加载

这里咱们应用 remote-component 这个npm包来解决这个问题

github地址:https://github.com/Paciolan/r...

什么是近程组件

这里援用npm包文档的阐明

近程组件在运行时从URL加载。它的应用形式与其余React组件雷同

近程组件应用办法

const url =  "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js";const HelloWorld = ({ name }) => <RemoteComponent url={url} name={name} />;const Container = (  <>    <HelloWorld name="Remote" />  </>);

线上环境调试本地调试代码

为了达到这个目标,咱们须要实现以下两点:

  • 搭建代理服务器
  • 劫持浏览器的申请,让浏览器的申请全部打到代理服务器上

搭建代理服务器

代理服务器咱们应用Anyproxy
应用文档

浏览器代理插件

咱们应用SwitchOmega,它能够对浏览器进行proxy代理

插件地址:
https://chrome.google.com/web...

插件模板脚手架

咱们能够应用commander/inquirer/chalknpm包,设计开发脚手架工具

插件产物打包上传

插件开发者后盾

如果是这样去实现的话,那咱们须要在页面中提供以下性能:

  • 新增插件
  • 删除插件
  • 插件产物上传
  • 插件公布
  • 插件版本回退

VSCode插件

VSCode插件开发实现的性能相似于后盾,然而能够免去在后盾登录,以及本地打包,再拖拽产物到后盾再上传的繁琐流程

插件开发API文档: https://code.visualstudio.com...

流程图

开发者视角

<div align="center">
<img width="200" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98d682e2306346bdb5df6386a9f13f28~tplv-k3u1fbpfcp-watermark.image?" />
</div>

平台维护者视角

<div align="center">
<img width="400" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6368c09e1f8f4094b023db1a8f2d05fe~tplv-k3u1fbpfcp-watermark.image?" />
</div>

具体实现

我的项目构造

这里咱们用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,咱们临时把返回的data写死为{},后续近程组件的url会通过代理服务器劫持提供,

这样就相当于是去模仿实现,代理线上接口调试本地代码了
import { Service } from 'egg';/** * Test Service */export default class Test extends Service {  /**   * sayHi to you   * @param name - your name   */  public async sayHi() {    return {      code: 0,      data: {},      msg: 'ok',    };  }}

开发代理服务器包

这里我间接提供代理服务器外围代码,咱们设置代理服务器运行在本地11111端口,这里咱们去代理后盾提供的/api/plugin_detail接口,在返回时,减少注入本地模板我的项目打包好的产物url,这里咱们的urlhttp://localhost:9001/main.bundle.js,后续会解说这个url是怎么来的

const AnyProxy = require('anyproxy');const rule = require('./rule/index');const DEFAULT_PORT = 11111;const proxyServer = new AnyProxy.ProxyServer({  rule: {    beforeSendResponse: (requestDetail, responseDetail) => {      if (requestDetail.url.includes('/api/plugin_detail')) {        const data = JSON.parse(responseDetail.response.body.toString());        data.data.url = 'http://localhost:9001/main.bundle.js';        responseDetail.response.body = JSON.stringify(data);        return responseDetail;      } else {        return responseDetail;      }    },  },  port: DEFAULT_PORT,  throttle: 10000,  forceProxyHttps: true,  wsIntercept: false, // 不开启websocket代理  silent: false,});proxyServer.start();

配置浏览器代理插件

装置好浏览器代理插件之后,咱们启动插件,点击新增new profile,咱们轻易起个名字,就叫test_local设置代理到本地代理服务器的端口,这里咱们应用下面设置的11111端口。进行完这一步之后,咱们就能将浏览器的申请打到咱们本地代理服务器的上了

留神,要在Bypass List中配置<-loopback>,不然代理不到本地接口

到这里,前置工作就筹备好了,上面咱们开始开发平台我的项目,和插件页面

FAQ

  1. 如果代理网站时,网站不被信赖,须要信赖anyproxy的证书,不同零碎证书地位不同,例如MacOS在如下目录 ./.anyproxy/certificates/rootCA.crt

开发插件

写一个demo插件页面

咱们首先写一个最简插件模板,这里咱们间接写死,理论能够用脚手架生成一个插件我的项目模板进去

import React from 'react';import './test.css';const Plugin: React.FC<{}> = () => {  return (    <div      style={{        color: 'red',        fontSize: '32px',      }}    >      这个是近程加载的插件组件    </div>  );};export default Plugin;

webpack配置

这里我抽离几行外围配置,次要要留神libraryTarget需为commonjs格局,以及咱们须要配置externals,防止打包react到插件产物中。

为了demo能跑起来,咱们给devServer配置跨域头为*,并且运行到9001端口,这样本地插件产物js文件的地址就会是http://localhost:9001/main.bundle.js

  output: {    filename: '[name].bundle.js',    libraryTarget: 'commonjs',  },  externals: {    react: 'react',  },  devServer: {    hot: true,    port: 9001,    headers: {      'Access-Control-Allow-Origin': '*',    },  },

在平台我的项目中,配置近程组件

注入近程组件所需的依赖

因为近程组件咱们设置了externals属性,防止打包react,咱们在平台我的项目中,须要提供react依赖给插件模板,这样插件模板才能够援用到react包的依赖

/** * Dependencies for Remote Components */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地址,然而当咱们开启代理服务器时,就能够获取到本地插件组件打包产物的地址了

import React, { useEffect, useState } from 'react';import { RemoteComponent } from '@paciolan/remote-component';import { Card } from 'antd';interface IPlatFormEntryProps {}const PlatFormEntry: React.FC<IPlatFormEntryProps> = () => {  const [url, setUrl] = useState('');  const [loading, setLoading] = useState(true);  useEffect(() => {    setTimeout(async () => {      const rawRes = await fetch('http://localhost:7001/api/plugin_detail', {        method: 'GET',      });      const res = await rawRes.json();      const url = res.data.url;      setLoading(false);      setUrl(url);    }, 1000);  }, []);  const HelloWorld = (props) => <RemoteComponent url={url} {...props} />;  return (    <Card title={'主利用启动结束'}>      {loading ? <div>Loading,正在加载近程组件.....</div> : <HelloWorld />}    </Card>  );};export default PlatFormEntry;

整体流程联调以及成果展现

具体实现能够参考我的项目源码,我曾经将源码贴在文章最下方

启动浏览器代理插件

咱们抉择咱们刚刚配置好的test_local配置项

启动咱们monorepo内的4个子我的项目

进行平台我的项目目录,启动平台我的项目

cd packages/platformpnpm dev

进行插件我的项目目录,启动插件我的项目

cd packages/plugin-pagepnpm dev

进行代理服务器我的项目目录,启动代理服务

cd packages/anyproxy-serverpnpm dev

进行后端我的项目目录,启动后端我的项目

cd packages/egg-serverpnpm dev

查看成果

拜访平台我的项目首页,即可看到成果
http://localhost:9000

期待接口返回后,即可加载出近程组件内容

demo代码仓库: https://github.com/ericlee33/...

如果有帮忙到的话,欢送点个 Star ⭐️ & follow https://github.com/ericlee33

欢送关注前端江湖行公众号,我会分享很多乏味的技术文章,心愿帮忙更多的人深耕前端技术,紧跟技术潮流。