关于前端:实现前端插件化架构设计将需求开发交给别人

55次阅读

共计 6285 个字符,预计需要花费 16 分钟才能阅读完成。

前言

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/platform
pnpm dev

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

cd packages/plugin-page
pnpm dev

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

cd packages/anyproxy-server
pnpm dev

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

cd packages/egg-server
pnpm dev

查看成果

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

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

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

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

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

正文完
 0