基于System.js的微前端实现(插件化)

目录​​​​​​​

 写在前面

一、微前端相关知识

(一)概念

(二) 优势

(三) 缺点

(四)应用场景

(五)现有框架

1. qiankun

2. single-spa

3. SystemJS

二、需求分析

三、流程概览

(一)子项目改造

(二)npm包

(三)system.js插件封装

(四)主项目改造

四、子项目改造

(一)子项目创建

(二)webpack5设置

(三)其他

五、npm包

(一)npm包内容

(二)npm包发布

六、基于system.js的插件工具封装

(一)封装

(二)使用

七、主项目改造

(一)安装依赖

(二)指定路由

八、实现效果

九、开源项目地址 

(一)主项目

(二)子项目

(三)xu-demo-data


 写在前面

        本文所有技术实现的相关代码都已开源,想clone或者fork,请直接拉到文章末尾。

        另外,技术实现借鉴了国内开源软件 kubevela,非常感谢kubevela的各位大佬能开源质量如此之高的代码。且因为本人是行业菜鸡,代码中如有语法错误或其他问题,请在评论区指正,感谢各位。

一、微前端相关知识

        微前端(Micro Frontends)是一种前端架构的模式,旨在将大型前端应用拆分为多个独立的、可组合的小型前端模块。这些模块可以由不同的团队独立开发、部署和维护,最终在用户界面中无缝集成。微前端的核心思想类似于后端的微服务架构,通过模块化的方式降低系统复杂性,提高开发效率和扩展性。

(一)概念

  • 独立部署:每个前端模块可以独立开发和部署,不依赖于其他模块。各个模块之间通过预定义的接口进行通信。

  • 技术无关:不同的前端模块可以使用不同的技术栈(如React、Vue、Angular等),这使得团队能够根据具体需求和技术栈选择合适的工具。

  • 团队独立:微前端允许不同的团队负责不同的模块,降低了团队间的耦合度。每个团队可以独立管理其代码库、开发流程和发布节奏。

  • 界面组合:微前端架构通过组合不同的模块构建完整的用户界面。这可以通过在浏览器中动态加载不同的模块来实现,通常使用iframe、模块联邦(Module Federation)或自定义加载逻辑。

  • 渐进迁移:微前端使得大型遗留系统可以逐步迁移到新技术中,减少一次性重构的风险和成本。

(二) 优势

  • 扩展性强:每个模块独立开发和部署,能够快速迭代和扩展功能。
  • 灵活性高:不同模块可以选择不同的技术和工具,而不影响整个系统。
  • 提高团队效率:独立团队可以并行开发,减少依赖和协调。

(三) 缺点

  • 性能问题:由于多个模块的组合可能导致资源加载和渲染效率问题,需要特别注意优化。
  • 共享状态和通信:不同模块间的数据共享和通信需要标准化和规范化,避免耦合过高。
  • 样式和UI一致性:虽然模块独立开发,但最终呈现给用户的界面需要保持一致的用户体验和样式规范。

(四)应用场景

  • 随着技术的发展,前端应用可能需要从旧的技术栈迁移到新的技术栈。然而,对于大型应用,一次性迁移的风险和成本往往较高。微前端使得可以逐步迁移——某些模块可以采用新的技术栈,而其他部分保持不变。这种方式降低了技术迁移的风险和成本。
  • 微前端允许这些团队各自独立开发、部署和维护自己的模块,而不影响其他团队。这种方式有助于减少依赖、加快开发节奏,并使应用更加易于扩展。

(五)现有框架

1. qiankun

        qiankun 是一个基于 single-spa 的微前端框架,专注于子应用的加载、沙箱隔离以及跨应用的通信管理。qiankun 提供了开箱即用的解决方案来管理多个微前端应用,并在容器应用中将它们集成到一起。它的插件化功能可以帮助不同技术栈的子应用共存。

        官网地址:

qiankun - qiankunicon-default.png?t=O83Ahttps://qiankun.umijs.org/zh

2. single-spa

        single-spa 是一个流行的微前端框架,它允许多个前端框架(如 React、Vue、Angular)在同一个应用中共存,并实现独立加载和渲染。single-spa 的插件化机制允许开发者将每个子应用打包成独立的模块,单独部署和运行。

        官网地址:

https://zh-hans.single-spa.js.org/docs/getting-started-overviewicon-default.png?t=O83Ahttps://zh-hans.single-spa.js.org/docs/getting-started-overview

3. SystemJS

        SystemJS 是一种模块加载器,通常用于微前端架构中实现动态模块加载。通过 SystemJS,应用可以在运行时按需加载不同的子应用或插件,而不是在构建时打包所有内容。它常与 single-spa 等微前端框架一起使用。

        GitHub地址:

GitHub - systemjs/systemjs: Dynamic ES module loaderDynamic ES module loader. Contribute to systemjs/systemjs development by creating an account on GitHub.icon-default.png?t=O83Ahttps://github.com/systemjs/systemjs

二、需求分析

        年初,公司产品经理提出了一个概念,叫js热加载热更新,需要什么js文件就动态加载什么js文件。因为目前公司的产品,有两个代码仓库,一个用于企业版,一个用于做开源版。开源版的核心代码与企业版的核心代码无差别,但在定制化和某些功能的支持上,企业版更占据优势。但是,同时维护两个仓库的很多分支又比较耗费人力物力,无论是企业功能下方开源,还是开源功能合并企业都比较繁琐,且合并过程中也有一些风险,因此就想到用这种热更新的模式,开源版与企业版的代码一致,但是企业版可以通过js热加载的方式动态导入一些前端页面或者功能用于实现和开源版的差异化,但这部分企业版的功能又不能放在开源代码中,因为直接放的话,有懂前端的开源用户就可以很轻松的破解代码了,因此想到了动态加载js这个方法来注入前端代码,以防止可以很轻易的就破解代码。

        在经历调研后,发现微前端这个东西貌似很符合我们的需求,但是因为主项目的版本过于老旧(umi 2.X),并不能适配当下主流的微前端插件,而且有些定制化的需求跟主流微前端的插件又不相吻合,所以,在这个背景之下,我参考借鉴了 kubevela 的插件实现方式,最终使用SystemJS实现了插件功能。

kubevela项目地址:

https://github.com/kubevela/kubevelaicon-default.png?t=O83Ahttps://github.com/kubevela/kubevela

三、流程概览

(一)子项目改造

        子项目的改造就一个重点----打包。因为涉及到很多东西,比如插件元数据设置与拷贝,版本更新,数据传输,以及webpack5打包后的文件类型等,都有许多讲究。

(二)npm包

        制作插件需要一个npm依赖包作为数据传递的中转站,子项目打包后会返回一个函数,这个函数在主项目中也有引用,通过system.js导入子项目后,在主项目调用该函数,即可获取子项目中传入的数据,也就是react的组件。从而串通整个流程。

(三)system.js插件封装

        当你完成子项目与npm后,就需要封装system.js用来将子项目打包后的代码动态导入到主项目里。

(四)主项目改造

        主项目的改动其实就比较小了,安装system.js与自己制作的npm依赖,再划定一些路由用于渲染该插件,主项目的使命就算完成了。

四、子项目改造

(一)子项目创建

        这一步很好理解,使用脚手架创建一个react应用,然后再针对于react应用进行webpack5的打包改造。或者直接从github上下载一个使用webpack5打包的react模板。

        package.json文件,这里面有我所有依赖的版本信息,需要的可自行查阅。

{"name": "plugin-template","version": "1.0.0","description": "插件体系模板文件","scripts": {"dev": "cross-env NODE_ENV=development webpack serve --config ./build/webpack.dev.config.js --open","build": "cross-env NODE_ENV=production webpack --config ./build/webpack.prod.config.js","build:report": "cross-env NODE_ENV=production REPORT=true webpack --config ./build/webpack.prod.config.js","eslint:init": "eslint --init","prepare": "husky install","lint-staged": "lint-staged","commitlint": "commitlint -e","eslint:fix": "eslint --ext .js,.vue --fix src"},"keywords": [],"author": "xuzhonglin12138","license": "ISC","devDependencies": {"@babel/core": "^7.15.5","@babel/plugin-transform-runtime": "^7.15.0","@babel/preset-env": "^7.15.6","@babel/preset-react": "^7.14.5","@commitlint/cli": "^13.2.1","@commitlint/config-conventional": "^13.2.0","babel-loader": "^8.2.2","babel-plugin-import": "^1.13.8","cache-loader": "^4.1.0","copy-webpack-plugin": "^10.0.0","core-js": "3","cross-env": "^7.0.3","css-loader": "^6.3.0","dart-sass": "^1.25.0","eslint": "^8.1.0","eslint-config-prettier": "^8.3.0","eslint-plugin-prettier": "^4.0.0","eslint-plugin-react": "^7.26.1","eslint-plugin-react-hooks": "^4.2.0","eslint-webpack-plugin": "^3.0.1","html-webpack-plugin": "^5.3.2","http-proxy-middleware": "^3.0.0","husky": "^7.0.4","less": "^4.2.0","less-loader": "^12.2.0","lint-staged": "^11.2.4","mini-css-extract-plugin": "1.6.2","postcss": "^8.3.8","postcss-loader": "^6.1.1","postcss-preset-env": "^6.7.0","prettier": "^2.4.1","replace-in-file-webpack-plugin": "^1.0.6","sass-loader": "^12.1.0","style-loader": "^3.3.0","terser-webpack-plugin": "^5.3.10","thread-loader": "^3.0.4","webpack": "^5.53.0","webpack-bundle-analyzer": "^4.4.2","webpack-cli": "^4.8.0","webpack-dev-server": "^4.2.1","webpack-merge": "^5.8.0"},"dependencies": {"@ant-design/icons": "^5.5.1","@babel/runtime": "^7.15.4","@babel/runtime-corejs3": "^7.15.4","@swc/core": "^1.7.6","antd": "5.19.3","axios": "^0.24.0","js-cookie": "^3.0.5","react": "^16.8.6","react-dom": "^16.8.6","react-intl-universal": "^2.11.3","swc-loader": "^0.2.6","xu-demo-data": "3.0.1"}
}

子项目改造的最重要的一点就是改造打包入口文件。如下:

        左侧是打包的入口,右侧是本地启动的入口,可以看到左侧没有输出点,而是通过插件 xu-demo-data 调用了一个类里的方法,将两个组件以参数的形式传入了这个类中。右侧则是正常react应用的入口文件。至于这个 RainbondRootPagePlugin 是什么,咱们暂时按下不表。后面会提到。

(二)webpack5设置

        webpack5的配置和其他项目的配置其实大同小异。如果对webpack5不了解的可以看我另一篇文章,了解一些基本的配置信息。

webpack的基本介绍与使用-CSDN博客文章浏览阅读1.6k次,点赞28次,收藏30次。Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。_webpackhttps://blog.csdn.net/qq_45799465/article/details/140628293?spm=1001.2014.3001.5502

//文件名 webpack.base.config.jsconst path = require('path')
const webpack = require('webpack')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const ESLintPlugin = require('eslint-webpack-plugin')
const TerserPlugin = require("terser-webpack-plugin");
const { getEntryFile, getPluginId } = require('./utils');const NODE_ENV = process.env.NODE_ENVmodule.exports = {entry: getEntryFile(),context: path.join(process.cwd(), 'src'),// cache: {//   type: 'filesystem',//   buildDependencies: {//     config: [__filename],//   },// },module: {rules: [{test: /\.(png|jpe?g|gif|svg)$/,exclude: /node_modules/,type: 'asset/resource',generator: NODE_ENV === 'development' ? {} : {publicPath: `plugins/${getPluginId()}/img/`,outputPath: 'img/',filename: NODE_ENV === 'development' ? '[hash][ext]' : '[name][ext]',},},{test: /\.css$/,use: ["style-loader", "css-loader"]},{test: /\.(woff|woff2|eot|ttf|otf)(\?v=\d+\.\d+\.\d+)?$/,exclude: /node_modules/,type: 'asset/resource',generator: NODE_ENV === 'development' ? {} : {publicPath: `plugins/${getPluginId()}/fonts`,outputPath: 'fonts/',filename: NODE_ENV === 'development' ? '[hash][ext]' : '[name][ext]',},},{exclude: /(node_modules)/,test: /\.[jt]sx?$/,use: [{loader: 'swc-loader',options: {jsc: {baseUrl: path.resolve(__dirname, 'src'),target: 'es2015',loose: false,parser: {syntax: 'ecmascript', jsx: true, decorators: false,dynamicImport: true,},},},},'babel-loader']}]},plugins: [new ESLintPlugin({extensions: ['js', 'jsx']}),new webpack.ProgressPlugin(),],resolve: {alias: {"@": path.join(__dirname, '..', 'src')},extensions: ['.js', '.jsx', '.json'],modules: [path.resolve(process.cwd(), 'src'), 'node_modules'],unsafeCache: true,},optimization: {runtimeChunk: false,minimize: true,minimizer: [new TerserPlugin({extractComments: false})],}
}
//webpack.prod.config.jsconst path = require('path')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.config')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin');
const { hasReadme, getPackageJsonInfo, getPluginId, getTodayDate } = require('./utils');const REPORT = process.env.REPORT
const prodConfig = {mode: 'production',devtool: 'eval-source-map',output: {clean: true,path: path.join(__dirname, '..', 'dist'),filename: '[name].js',library: {type: 'amd',},},module: {rules: [{test: /\.less$/,exclude: /node_modules/,use: ['style-loader',{loader: 'css-loader',options: {modules: true}},'postcss-loader','less-loader']}]},externals: ['lodash','moment','react','react-dom','xu-demo-data'],plugins: [new MiniCssExtractPlugin({filename: '[name].[contenthash].css',}),new CopyWebpackPlugin({patterns: [{ from: hasReadme() ? 'README.md' : '../README.md', to: '.', force: true },{ from: 'pluginData.json', to: '.' },{ from: '../LICENSE', to: '.', noErrorOnMissing: true },{ from: '../CHANGELOG.md', to: '.', force: true, noErrorOnMissing: true },{ from: '**/*.json', to: '.' }],}),new ReplaceInFileWebpackPlugin([{dir: 'dist',files: ['pluginData.json', 'README.md'],rules: [{search: /\%VERSION\%/g,replace: getPackageJsonInfo().version,},{search: /\%TODAY\%/g,replace: getTodayDate(),},{search: /\%PLUGIN_ID\%/g,replace: getPluginId(),},],},]),]
}if (REPORT) {prodConfig.plugins.push(new BundleAnalyzerPlugin())
}module.exports = merge(baseConfig, prodConfig)

 这基本都是一些基础的配置,用到了一些插件,以及一些方法在后面我都会贴出来,大家看看大概就好,这里唯一需要注意的是:

  output: {clean: true,path: path.join(__dirname, '..', 'dist'),filename: '[name].js',library: {type: 'amd',},},

ouput选项中,有个library选项,type选择  amd  格式 :

        library: { type: 'amd' }:配置 Webpack 将输出的文件打包为 AMD(异步模块定义)模块。这种模块格式通常用于浏览器端的 JavaScript 应用,能够异步加载依赖项。

(三)其他

        webpack的 externals 选项就是打包时排除某些依赖,对于子项目来说,要尽可能的减少依赖,以减小打包体积,而且子项目是与主项目共用某些依赖的,因此,这些依赖都可以排除打包,然后在使用 system.js 进行动态导入。(后续会在system.js 插件制作中展示)

  externals: ['lodash','moment','react','react-dom','xu-demo-data'],

五、npm包

(一)npm包内容

        这就是npm包的全部内容,可以看到我只是定义了两个类,然后 RainbondRootPagePlugin 继承了RainbondOtherPagePlugin ,在类中定义了一些函数,仅此而已。

        RainbondRootPagePlugin 也就是子项目打包入口中所使用的那个类。

        npm包地址:

xu-demo-data - npm111111. Latest version: 3.0.1, last published: 18 days ago. Start using xu-demo-data in your project by running `npm i xu-demo-data`. There are no other projects in the npm registry using xu-demo-data.icon-default.png?t=O83Ahttps://www.npmjs.com/package/xu-demo-data

export class RainbondOtherPagePlugin {constructor() {this.meta = {};this.OtherPages = undefined;}addOtherPage(page) {this.OtherPages = page;return this;}
}export class RainbondRootPagePlugin extends RainbondOtherPagePlugin {constructor() {super();this.root = undefined;}init(meta) {}setRootPage(root) {this.root = root;return this;}
}

(二)npm包发布

        很简单,初始化,写代码,打包,登录npm账号,上传。具体操作流程可以看我这篇文章,里面有详细的解释

发布NPM包详细流程_npm发包流程-CSDN博客文章浏览阅读500次,点赞7次,收藏11次。首先需要制作一个npm包。按照以下步骤依次执行。相信这一步不需要过多的解释,就是创建了一个文件夹,然后初始化了一下文件夹。然后在生成的package.json文件夹中更改一下自己的配置,例如包名、版本、描述等。然后创建src文件夹,在对应的地方写下你自己的函数即可。_npm发包流程https://blog.csdn.net/qq_45799465/article/details/140853512?spm=1001.2014.3001.5502

六、基于system.js的插件工具封装

(一)封装

import System from 'systemjs/dist/system.js';
import _ from 'lodash';
import moment from 'moment';
import react from 'react';
import * as ReactDom from 'react-dom';
import * as RbdData from 'xu-demo-data';
import { RainbondRootPagePlugin, RainbondEnterprisePagePlugin } from 'xu-demo-data'export const SystemJS = System;
const cache = {};
const initializedAt = Date.now();SystemJS.registry.set('plugin-loader', SystemJS.newModule({ locate: locateWithCache }));SystemJS.config({baseURL: '/public',defaultExtension: 'js',packages: {plugins: {defaultExtension: 'js',},},meta: {'/*': {esModule: true,authorization: false,loader: 'plugin-loader',}},
});export function exposeToPlugin(name, component) {SystemJS.registerDynamic(name, [], true, function (require, exports, module) {module.exports = component;});
}
exposeToPlugin('lodash', _);
exposeToPlugin('moment', moment);
exposeToPlugin('react', react);
exposeToPlugin('react-dom', ReactDom);
exposeToPlugin('xu-demo-data', RbdData);export async function importPluginModule(meta, regionName) {const path = `/console/regions/${regionName}/static/plugins/${meta.name}`const module = await SystemJS.import(path);return module
}export async function importAppPagePlugin(meta, regionName, type) {const xu = await importPluginModule(meta, regionName).then(function (pluginExports) {const plugin = pluginExports.plugin ? (pluginExports.plugin) :type == 'enterprise' ? new RainbondEnterprisePagePlugin() : new RainbondRootPagePlugin();plugin.init(meta);plugin.meta = meta;return plugin;});return xu
}export function locateWithCache(load, defaultBust = initializedAt) {const { address } = load;const path = extractPath(address);if (!path) {return `${address}?_cache=${defaultBust}`;}const version = cache[path];const bust = version || defaultBust;return `${address}?_cache=${bust}`;
}function extractPath(address) {const match = /\/public\/(plugins\/.+\/module)\.js/i.exec(address);if (!match) {return;}const [_, path] = match;if (!path) {return;}return path;
}

这里有几个重点:

        首先,exposeToPlugin 这个函数的作用就是动态导入一些依赖,这也正好和子项目打包中排除的那些依赖相对应,也就相当于子项目用的依赖是和主项目一致的。

        也就是说子项目和主项目的这些 共用的依赖的版本要一致 !!!

export function exposeToPlugin(name, component) {SystemJS.registerDynamic(name, [], true, function (require, exports, module) {module.exports = component;});
}
exposeToPlugin('lodash', _);
exposeToPlugin('moment', moment);
exposeToPlugin('react', react);
exposeToPlugin('react-dom', ReactDom);
exposeToPlugin('xu-demo-data', RbdData);

        其次,importPluginModule 函数调用的 SystemJS.import  就是引入js文件的关键函数。而这个path,指向的就是线上地址的打包后的入口文件地址。 如果对 SystemJS 不了解的同学可以去看看我上面贴的SystemJS的github地址。

export async function importPluginModule(meta, regionName) {const path = `/console/regions/${regionName}/static/plugins/${meta.name}`const module = await SystemJS.import(path);return module
}export async function importAppPagePlugin(meta, regionName, type) {const xu = await importPluginModule(meta, regionName).then(function (pluginExports) {const plugin = pluginExports.plugin ? (pluginExports.plugin) :type == 'enterprise' ? new RainbondEnterprisePagePlugin() : new RainbondRootPagePlugin();plugin.init(meta);plugin.meta = meta;return plugin;});return xu
}

(二)使用

        导出,然后使用就可以了。

import { importAppPagePlugin } from '../../utils/importPlugins';  importPlugin = (meta, regionName) => {importAppPagePlugin(meta, regionName, 'enterprise').then(res => {this.setState({ app: res, pluginLoading: false })}).catch(err => {this.setState({errInfo: err?.response?.data?.message || err?.message || "An unexpected error occurred.",pluginLoading: false,error: true})})}

        

七、主项目改造

(一)安装依赖

这里没有什么多说的,安装的  SystemJS 版本以及 npm 包版本如下:

(二)指定路由

动态路由。

路由代码

 RbdPlugins.js  

//RbdPlugins.jsimport React, { Component } from 'react';
import { Spin, Card, Button } from 'antd';
import { connect } from 'dva';
import { importAppPagePlugin } from '../../utils/importPlugins';
import { getRainbondInfo } from '../../services/api';
import PageHeaderLayout from '../../layouts/PageHeaderLayout';
import RbdPluginsCom from '../../components/RBDPluginsCom'
import Global from '@/utils/global';
import PluginUtil from '../../utils/pulginUtils';
import styles from './index.less';@connect(({ user, teamControl, global }) => ({user: user.currentUser,
}))
export default class Index extends Component {constructor(props) {super(props);this.state = {app: {},plugins: {},loading: true,pluginLoading: true,error: false,errInfo: '',};}componentDidMount() {this.getPluginsList();}importPlugin = (meta, regionName) => {importAppPagePlugin(meta, regionName, 'enterprise').then(res => {this.setState({ app: res, pluginLoading: false })}).catch(err => {this.setState({errInfo: err?.response?.data?.message || err?.message || "An unexpected error occurred.",pluginLoading: false,error: true})})}getPluginsList = () => {const type = PluginUtil.getCurrentViewPosition(window.location.href);type === 'Platform' ? this.loadEnterpriseClusters() : this.loadPluginList();};loadEnterpriseClusters = () => {const { dispatch } = this.props;const enterpriseId = Global.getCurrEnterpriseId();dispatch({type: 'region/fetchEnterpriseClusters',payload: { enterprise_id: enterpriseId },callback: (res) => {if (res.status_code === 200 && res.list?.[0]?.region_name) {this.loadPluginList(res.list[0].region_name);}},});};loadPluginList = (regionName) => {const {dispatch,match,isCom,user,} = this.props;let pluginId= ''if(isCom){pluginId = Global.getComponentPluginType()}else{pluginId = match.params.pluginId}const enterpriseId = Global.getCurrEnterpriseId() || user?.enterprise_id;const currentRegionName = regionName || Global.getCurrRegionName();dispatch({type: 'global/getPluginList',payload: { enterprise_id: enterpriseId, region_name: currentRegionName },callback: (res) => {if (res && res.list) {const plugin = res.list.find((item) => item.name === pluginId) || {};this.setState({ plugins: plugin, loading: false }, () => {if (plugin.plugin_type === 'JSInject') {this.importPlugin(plugin, currentRegionName);}});}},handleError: () => {this.setState({ plugins: {}, loading: false });},});};render() {const { plugins, loading } = this.state;const {isCom = false} = this.propsreturn (<>{!loading ? (isCom ? <RbdPluginsCom {...this.state}/> :<PageHeaderLayout title={plugins?.name} content={plugins?.description} pluginSVg={plugins?.icon}><RbdPluginsCom {...this.state}/></PageHeaderLayout>) : (<div style={{ width: '100%', height: 500, display: 'flex', justifyContent: 'center', alignItems: 'center' }}><Spin size="large" /></div>)}</>);}
}

RbdPluginsCom.js 

import React, { Component } from 'react';
import { Spin, Card, Button } from 'antd';
import Result from '../Result';
import PluginsUtiles from '../../utils/pulginUtils'
import Global from '../../utils/global'
import styles from './index.less';export default class index extends Component {constructor(props) {super(props);this.state = {};}// 判断是否为多视图插件isMultiViewPlugin = () => {const { plugins } = this.props;const str = PluginsUtiles.isCurrentPluginMultiView(window.location.href, plugins.plugin_views)return str}// 渲染插件rbdPluginsRender = () => {const { app, plugins, pluginLoading, error, errInfo, dispatch, reduxInfo } = this.props;const key = this.isMultiViewPlugin()const AppPagePlugin = app[key] ? app[key] : falsereturn pluginLoading ? (<div style={{ width: '100%', height: 500, display: 'flex', justifyContent: 'center', alignItems: 'center' }}><Spin size="large" tip="插件内容加载中..." /></div>) : (error ? (<Card style={{ marginTop: 20 }}><Resulttype="error"title='插件加载失败'description={`错误信息:${errInfo}`}actions={<Button onClick={() => { console.log('点了一下'); }}>查看文档</Button>}style={{marginTop: 48,marginBottom: 16}}/>,</Card>) : (AppPagePlugin &&<AppPagePlugincolorPrimary={Global.getPublicColor('primary-color')}currentLocale='en'/>));}// 渲染iframeiframeRender = () => {const { app, plugins, pluginLoading, error, errInfo } = this.props;return <div style={{ height: '100vh' }}><iframesrc={plugins?.fronted_path}style={{ width: '100%', height: '100%' }}id={plugins?.name}sandbox="allow-same-origin allow-scripts allow-popups allow-forms"scrolling="auto"frameBorder="no"border="0"marginWidth="0"marginHeight="0"/></div>}render() {const { plugins } = this.props;return (<>{plugins?.plugin_type === 'JSInject'?(this.rbdPluginsRender()) : (this.iframeRender())}</>)}
}

八、实现效果

/console/regions/rainbond/static/plugins/app-view?_cache=1728984496265 这个地址存放的就是子项目打包后的入口文件,这里也成功渲染出来了。

到此为止,所有的流程都已经串通了,插件功能也实现了。

九、开源项目地址 

(一)主项目

https://github.com/goodrain/rainbond-ui/tree/V6.0icon-default.png?t=O83Ahttps://github.com/goodrain/rainbond-ui/tree/V6.0

(二)子项目

https://github.com/xuzhonglin12138/plugin-template/tree/mainicon-default.png?t=O83Ahttps://github.com/xuzhonglin12138/plugin-template/tree/main

(三)xu-demo-data

xu-demo-data - npm111111. Latest version: 3.0.1, last published: 18 days ago. Start using xu-demo-data in your project by running `npm i xu-demo-data`. There are no other projects in the npm registry using xu-demo-data.icon-default.png?t=O83Ahttps://www.npmjs.com/package/xu-demo-data

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/451822.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习课程学习周报十七

机器学习课程学习周报十七 文章目录 机器学习课程学习周报十七摘要Abstract一、机器学习部分1. 变分推断/推理1.1 证据下界1.2 q ( z ) {q(z)} q(z)的选取 2. VAE2.1 Auto-Encoder的简单回顾2.2 为什么提出VAE2.3 VAE的数学原理 3. Diffusion Model的数学原理3.1 Training算法…

【C语言】递归函数变量的作用域

变量的作用域 全局变量&#xff1a;在整个程序内可以识别&#xff0c;且唯一。 局部变量&#xff1a;仅在当前函数内有效&#xff0c;比如main函数、用户自定义函数。 递归函数 指的是一类函数&#xff0c;函数调用自身&#xff0c;包括递推和回归。使用递归的方式&#xff0c…

JavaWeb 19 AJAX

目录 一、什么是AJAX 同步交互和异步交互 同步交互 异步交互 Ajax工作原理 Ajax实现方式 原生JavaScript方式进行ajax(了解)&#xff1a; "我就是希望你好&#xff0c;就像很多人希望我好一样&#xff0c;特别简单&#xff0c;特别真挚。也不为了什么&#xff0c;就是希望…

从0开始深度学习(14)——模型选择、欠拟合、过拟合

① 模型在训练数据上拟合的比在潜在分布中更接近的现象&#xff0c;就叫过拟合&#xff08;overfitting&#xff09; ② 用于对抗过拟合的技术称为正则化&#xff08;regularization&#xff09; 1 训练误差和泛化误差 ①训练误差&#xff08;training error&#xff09;&…

Gee引擎配置微端后登录游戏黑屏怎么办?

GEE引擎配置微端后登录游戏黑屏怎么办&#xff1f;今天飞飞和你们分享GEE引擎配置微端后游戏黑屏的解决办法&#xff0c;希望可以帮助到你~ 1、端口不对 微端没有更新&#xff0c;玩家进入游戏是地图跟装备都看不见&#xff0c;是漆黑的&#xff0c;微端显示连接失败&#xff…

c语言字符串函数strstr,strtok,strerror

1&#xff0c;strtok函数的使用和模拟实现 char * strtok(char * str,const char * sep) 会有static修饰变量&#xff0c;有记忆功能&#xff0c;会保存字符串的位置&#xff0c;下次找再继续找。 1)sep参数指向一个字符串&#xff0c;它包含了0个或者多个由sep字符中一个或…

极客wordpress模板

这是一个展示WordPress主题的网页设计。页面顶部有一个导航栏&#xff0c;包含多个选项&#xff0c;如“关于我们”、“产品中心”、“案例展示”、“新闻动态”、“联系我们”和“技术支持”。页面中间部分展示了多个产品&#xff0c;每个产品都有一个图片和简短的描述。页面下…

JUC并发编程进阶2:CompletableFuture

1 Future接口理论知识复习 Future接口&#xff08;FutureTask实现类&#xff09;定义了操作异步任务执行一些方法&#xff0c;如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等 举例&#xff1a;比如主线程让一个子线程去执行任务…

TCP(三次握手)和UDP(面向无连接)的原理以及区别

TCP(三次握手)和UDP&#xff08;面向无连接&#xff09;的原理以及区别 网络协议是每个前端工程师都必须要掌握的知识&#xff0c;TCP/IP 中有两个具有代表性的传输层协议。 概述 &#x1f4e1;TCP&#xff08;Transmission Control Protocol&#xff09;是一种网络协议&#…

【opengles】笔记1:屏幕坐标与归一化坐标(NDC)的转换

参考资料 OpenGL希望在所有顶点着色器运行后&#xff0c;所有我们可见的顶点都变为标准化设备坐标(Normalized Device Coordinate, NDC)。也就是说&#xff0c;每个顶点的x&#xff0c;y&#xff0c;z坐标都应该在-1.0到1.0之间&#xff0c;超出这个坐标范围的顶点都将不可见。…

【Python】NumPy(二):数组运算、数据统计及切片索引、广播机制

目录 Numpy数组 数组的基本运算 乘法 加法 数组的数据统计 平均值 中位数 最大值和最小值 求和 累积和 标准差 方差 切片和索引 索引 一维数组的索引 二维数组的索引 获取多个元素 布尔索引 切片 一维数组切片 二维数组切片 多维数组切片 广播机制 规则 …

Seata序列化优化

Apache Seata(incubating) 是一款开源的分布式事务解决方案&#xff0c;致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 本篇文章主要介绍Seata序列化实现优化。Seata对于网络传输数据&#xff0c;提供了多种序列化实现&#xff0c;包含Seata自身的序列化实现、…

一元n次多项式加法【数据结构-链表】

一元n次多项式定义如下&#xff1a; 其中Ai​为实数&#xff0c;i为不小于0的整数。在完成“一元n次多项式输入输出”题目的基础上实现一元n次多项式的加法。要求用链表实现上述一元n次多项式的操作。 输入格式: 有两个一元n次多项式&#xff0c;例如分别为&#xff1a; f(X)…

80.【C语言】数据结构之时间复杂度

目录 1.数据结构的定义 2.算法的定义 3.算法的效率 1.衡量一个算法的好坏的方法 例题:计算以下代码的循环次数 2.大O的渐进表示法 练习1:求下列代码的时间复杂度 练习2:求下列代码的时间复杂度 练习3:求下列代码的时间复杂度 练习4:求下列代码的时间复杂度 4.总结:计…

Leetcode—1115. 交替打印 FooBar【中等】(多线程)

2024每日刷题&#xff08;180&#xff09; Leetcode—1115. 交替打印 FooBar C实现代码 class FooBar { private:int n;sem_t fooSem;sem_t barSem;public:FooBar(int n) {this->n n;sem_init(&fooSem, 0, 1);sem_init(&barSem, 0, 0);}~FooBar() {sem_destroy(&…

mac安装brew时踩坑解决方案

安装包 mac上如果按照git等工具可能会使用brew&#xff0c;例如使用&#xff1a;$ brew install git命令&#xff0c;如果电脑没有按照brew&#xff0c;则会提示&#xff1a;zsh: command not found: brew 解决方案 需要我们打开brew的官网https://brew.sh/&#xff0c;复制…

机器学习|Pytorch实现天气预测

机器学习|Pytorch实现天气预测 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 电脑系统&#xff1a;Windows11 显卡型号&#xff1a;NVIDIA Quadro P620 语言环境&#xff1a;python 3.9.7 编译器&#x…

得物App3D创新应用引关注,世界设计之都大会启幕

近日&#xff0c;2024世界设计之都大会&#xff08;WDCC&#xff09;在上海盛大启幕。此次大会以“设计无界 新质生长”为主题&#xff0c;汇聚了全球设计领域的精英与前沿成果&#xff0c;展现了设计作为新质生产力的巨大潜力。主场展览占据了整整3个楼面&#xff0c;总面积达…

进程间关系与守护进程

一、进程组 1.1、什么是进程组 提到进程的概念&#xff0c; 其实每一个进程除了有一个进程 ID(PID)之外 还属于一 个进程组。进程组是一个或者多个进程的集合&#xff0c; 一个进程组可以包含多个进程。 每一 个进程组也有一个唯一的进程组 ID(PGID)&#xff0c; 并且这个 PG…

SCI英文文献阅读工具【全文翻译】【逐句翻译】

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 SCI英文文献阅读工具【全文翻译】【逐句翻译】 1. 全文翻译【DeepL】 适用于泛读网址&#xff1a;https://www.deepl.com/zh/translator/files 1.1 前提 文档大小&#xff1a;pdf文档不超过5M&#xff08;可先…