基于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.mzph.cn/web/55155.shtml

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

相关文章

GO之流程控制

一、流程控制简述 一&#xff09;流程控制的作用 流程控制语句是用来控制程序中语句执行顺序的语句&#xff0c;可以把语句组合成能完成一定功能的小逻辑块 二&#xff09;流程控制的分类 控制语句分为三类&#xff1a;顺序、选择和循环 顺序结构&#xff1a;依次执行&#xf…

通过Express + Vue3从零构建一个用户认证与授权系统(二)数据库与后端项目搭建与实现

前言 上一篇完成了系统的相关设计文档的编写&#xff0c;本文将详细介绍如何一步步使用 TypeScript 和 Express 搭建一个模块化、类型安全的用户认证与授权系统&#xff0c;包括数据库设计、后端项目搭建、用户认证、角色与权限管理、错误处理以及 Swagger 文档集成。 项目准…

Label Studio 半自动化标注

引言 Label Studio ML 后端是一个 SDK,用于包装您的机器学习代码并将其转换为 Web 服务器。Web 服务器可以连接到正在运行的 Label Studio 实例,以自动执行标记任务。我们提供了一个示例模型库,您可以在自己的工作流程中使用这些模型,也可以根据需要进行扩展和自定义。 1…

Ubuntu22.04环境下源码安装OpenCV 4.8.1

因为项目需要用OpenCV对yolov8模型进行推理&#xff0c;通过DNN模块&#xff0c;之前本地的OpenCV版本是4.5.4&#xff08;好像安装完ROS2 humble之后系统就自带了opencv&#xff09;&#xff0c;加载onnx模型一直报错&#xff0c;网上查询到需要4.7以上&#xff0c;干脆直接升…

开发教程 | 插件使用常见问题与调用攻略

Q1&#xff1a;插件是什么&#xff1f; 插件可以理解为是在一些专业领域上的单独的专精模型&#xff0c;比如专门生成PPT的模型、专门生成简历的模型。 大模型本身其实只是一个文字生成工具&#xff0c;只能根据自己在预训练过程中投入的语料以及用户的指令来回答问题。这给大…

内核定时器API实现点灯

1.内核定时器 定时器是一个很常用的功能&#xff0c;需要周期性处理的工作都要用到定时器。 Linux 内核定时器 采用系统时钟来实现&#xff0c;并不是6ull里面的硬件定时器。 Linux 内核定时器使用很简单&#xff0c;只需要提供超时时间(相当于定时值)和定时处理函数即…

500万人报名的软考到底是什么?有什么用?考什么?怎么报名?

软考是目前中国计算机领域最权威的认证考试之一&#xff0c;被广大IT从业者视为职业生涯发展的重要里程碑。通过参加软考&#xff0c;考生可以获得国家级资格认证&#xff0c;证明其具备一定的计算机专业知识和技能。本文将详细介绍软考的相关信息&#xff0c;帮助读者了解软考…

大数据-159 Apache Kylin 构建Cube 准备和测试数据

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

QT TCP服务器/客户端

服务器 首先要在.pro文件中添加network&#xff0c;否则将不能使用QTcpserver QT core gui network#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> #include <QTcpSocket> #define PORT 8000QT_BEGIN_NAMESPACE namesp…

使用Rollup.js快速开始构建一个前端项目

Rollup 是一个用于 JavaScript 项目的模块打包器&#xff0c;它将小块代码编译成更大、更复杂的代码&#xff0c;例如库或应用程序。Rollup 对代码模块使用 ES6 模块标准&#xff0c;它支持 Tree-shaking&#xff08;摇树优化&#xff09;&#xff0c;可以剔除那些实际上没有被…

第7章 网络请求和状态管理

一、Axios 1 Axios概述 Axios是一个基于Promise的HTTP库&#xff0c;可以发送get、post等请求&#xff0c;它作用于浏览器和Node.js中。当运行在浏览器时&#xff0c;使用XMLHttpRequest接口发送请求&#xff1b;当运行在Node.js时&#xff0c;使用HTTP对象发送请求。 Axios的…

【jeston】torch相关环境安装

参考&#xff1a;玩转NVIDIA Jetson &#xff08;25&#xff09;— jetson 安装pytorch和torchvision torch install 安装环境 conda create -n bisenet python3.8 conda activate bisenethttps://forums.developer.nvidia.com/t/pytorch-for-jetson/72048 import torch pri…

java异步多线程Async学习记录

java异步多线程Async学习记录 第1步:声明线程池AsyncConfiguration import org.springframework.context.annotation.Bean; import org.springframework

关联信息融合的知识图补全方法

目前&#xff0c;一些基于知识表示学习的补全方法没有充分考虑多步关系路径中各关系与直接关系之间的关联信息&#xff0c;以及头尾实体类型与直接关系之间的关联信息。 本论文对这些关联信息进行提取和利用&#xff0c;并提出了知识图补全的AiTransE模型。该模型利用首尾实体之…

基于华为云智慧生活生态链设计的智能鱼缸

一. 引言 1.1 项目背景 随着智能家居技术的发展和人们对高品质生活的追求日益增长&#xff0c;智能鱼缸作为一种结合了科技与自然美的家居装饰品&#xff0c;正逐渐成为智能家居领域的新宠。本项目旨在设计一款基于华为云智慧生活生态链的智能鱼缸&#xff0c;它不仅能够提供…

BugReport中的网络差现象

一、摘要 当出现网络不好时(日志关键字“process data stall”)&#xff0c;会出现com.android.networkstack.process 后台进程联网访问“http://www.google.cn/generate_204”进行网络检测的行为&#xff0c;会额外带来功耗电流。遇到这种情况&#xff0c;主要是环境因素&…

Echarts图表柱状图基本用法(横向、纵向、柱宽度、圆角、图表渐变色、图表滚动条、图例样式等)

效果图&#xff1a; JS: function chart(){var chartDom document.getElementById(这里写div的id名称);var myChart echarts.init(chartDom);var option;myChart.clear();//图表清除&#xff0c;用于更新数据重新加载图表option {//编辑图表整体布局宽、高等等grid:{top:20…

Android基于gradle task检查各个module之间资源文件冲突情况

做组件化开发的时候&#xff0c;我们经常会遇到各个不同的module之间资源文件冲突的问题&#xff0c;运行也不报错&#xff0c;但是会出现覆盖的问题&#xff0c;导致运行之后发送错误的效果。 所以我们需要利用一个gradlke 脚本task&#xff0c;来自动化检查资源文件冲突。 …

腾讯云-云直播

云直播&#xff08;Cloud Streaming Services&#xff09;为您提供极速、稳定、专业的直播云端处理服务&#xff0c;根据业务中不同直播场景的需求&#xff0c;云直播提供标准直播、快直播、慢直播和云导播台服务&#xff0c;分别针对大规模实时观看、高并发推流录制及超低延时…

Jenkins配置流水线任务-实践操作(Pipeline-script)

Jenkins配置流水线任务-实践操作(Pipeline-script) 1、新增jenkins 任务&#xff0c;选择流水线 2、参数化 3、流水线配置 pipeline {agent anystages {stage(aoePlugin_mysql) {steps {echo "xxx&#xff0c;数据库:Mysql"echo "${HOST},${USER_NAME}"b…