当前位置:网站首页>浅谈低代码平台远程组件加载方案
浅谈低代码平台远程组件加载方案
2022-08-03 12:18:00 【CRMEB商城源码】
前言
低代码开发平台(LCDP)是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台。通过可视化进行应用程序开发的方法,使具有不同经验水平的开发人员可以通过图形化的用户界面,使用拖拽组件和模型驱动的逻辑来创建网页和移动应用程序。这两年越来越多的公司和开发人员开始自研低代码平台来达到降本提效的目的。今天和大家分享一下低代码平台开发过程中遇的一个问题和对应的解决思路。
问题
低代码平台之所以不需要写代码是因为平台提供了很多可配置的组件,让平台的用户可以通过配置的方式生成自己想要的产物。那么如果想要能配置出更多的效果,就需要保证物料库足够丰富。
如果物料组件很多,就需要按需加载组件。现有的开发工具如 webpack 也支持代码分割。但是在低代码平台的开发场景中,平台应用是和组件分离的,需要用户在选择某个组件的时候,要加载远程组件代码。
加载方案
组件代码
我们以 vue 框架为例,假如当前有一个组件 A,代码如下,如何远程加载这个组件呢?
<template>
<div class="wp">{{text}}</div>
</template>
<script>
import { defineComponent, ref } from 'vue';
import _ from 'lodash';
export default defineComponent({
setup(props) {
console.log(_.get(props, 'a'));
return {
onAdd,
option,
size,
text: 'hello world',
};
},
});
</script>
<style>
.wp {
color: pink;
}
</style>
方案一:放在全局对象上
步骤
1.打包:组件代码打包为 umd 格式,打包时配置 webpack externals, 使打包产物不包含公共的依赖;
2.上传:打包的组件 js 上传到 cdn;
3.加载:在需要使用组件时,插入一个 script ,在这个 script 中将组件放在一个全局对象上;
4.注册:在 script 插入完成后,从全局对象上获取组件,并进行注册;
组件打包
首先需要增加一个入口文件
import Component from './index.vue';
if(!window.share) {
window.share = {};
}
window.share[Component.name] = Component;
以上面的入口文件为入口,用 webpack 打包为 umd 格式
// 组件打包 webpack 配置
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'production',
entry: path.resolve(__dirname, './comps/index.js'),
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
library: { type: 'umd' }
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
exclude: /node_modules/,
},
{
test: /\.js$/,
loader: 'babel-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin()
],
externals: {
vue: 'vue',
lodash: 'lodash',
}
};
html 模板
组件公共依赖都需要先加入到模板 html 中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn/vue.global.js"></script>
<script src="https://cdn/[email protected]"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
组件加载逻辑
const loadComponent = (name) => new Promise((resolve) => {
const script = document.createElement('script');
script.src=`http://xxx/${name}.js`;
script.onload = script.onreadystatechange = function(){
resolve();
};
document.querySelector('head').appendChild(script);
})
const addComp = async (name) => {
await loadComponent(name);
// 注册组件,其中 app 为 Vue 应用实例对象
app.component(name, window.share[name]);
}
// 动态注册组件
addComp('A');
缺点
1.组件的依赖共享,需要依赖提前先放到全局,html 模板需要较频繁改动;
2.全局对象上要挂载的内容越来越多,影响加载性能,没有做到真正的按需加载;
3.依赖版本难以管理。如 A 组件依赖了 loadsh 1.0, 而 B 组件依赖了 lodash 2.0,但是全局对象上的 lodash,同时挂载两个版本就必然会有冲突,因此版本必须一致;且后续如果某个组件要升级某个依赖的版本,也势必会影响所以其他组件。
方案二:amd
amd 格式也是一种模块化方案,这里我们选择知名度比较高的 require.js 作为 amd 模块加载器。
步骤
1.打包:组件代码打包为 umd 或 amd 格式,打包时配置 webpack externals,使打包产物不包含公共的依赖;
2.上传:打包的组件 js 上传到 cdn;
3.加载&注册:在需要使用组件时,用 requirejs 获取组件,并进行注册。
组件打包
用 amd 格式来做远程加载时不需要像方案一一样,增加额外的入口文件,可以直接将 .vue 文件作为入口。以下是 webpack 打包配置示例
// 组件打包 webpack 配置
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'production',
entry: path.resolve(__dirname, './comps/index.vue'),
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
library: { type: 'umd' } // 输出 amd 或者 umd
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
exclude: /node_modules/,
},
{
test: /\.js$/,
loader: 'babel-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin()
],
externals: {
vue: 'vue',
lodash: 'lodash',
}
};
html 模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./require.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
组件加载逻辑
// main.js
requirejs.config({
baseUrl: 'https://cdn.xxx.com',
map: {
'*': {
css: 'require-css',
},
},
paths: {
echarts: '[email protected]',
vueDemo: 'vue-demo',
vue: '[email protected]',
moment: 'https://cdn/[email protected]',
},
shim: {
'ant-design-vue': ['css!https://cdn/[email protected]'],
},
});
requirejs(['vue', 'vue-demo', 'vue-app'], function (vue, vueDemoModule, VueAppModule) {
const app = Vue.createApp(VueAppModule.default);
app.component('vue-demo', vueDemoModule.default);
const vm = app.mount('#app');
});
缺点
1.平台代码(上述代码的vue-app)也需要编译为 amd 格式,然后上传到 cdn 上,开发流程改变,需要定制化的开发平台项目的发布机制。
2.有些第三方库没有提供 amd 或 umd 格式,需要开发者自己开发工具去转换(此过程中可能有很多坑要踩);
优点
1.相比于方案一,组件的依赖可以有版本差异且互相不影响。
2.组件和组件的依赖都可以按需加载,真正做到按需加载。
3.有现成的加载 css 文件的机制;
方案三:ESModule
步骤
1.打包:组件代码打包为 esm 格式,打包时配置webpack externals, 使打包产物不包含公共的依赖;
2.上传:打包的组件 js 上传到 cdn;
3.加载&注册:在需要使用组件时,用 esm 的动态引入获取组件,并进行注册;
组件打包
这里需要注意的是,externals 配置项中直接把公共依赖配置为 cdn 地址;
import path from 'path';
import VueLoader from 'vue-loader';
const VueLoaderPlugin = VueLoader.VueLoaderPlugin;
const __dirname = path.resolve();
export default {
mode: 'development',
entry: path.resolve(__dirname, './src/vue-demo.vue'),
output: {
filename: 'vue-demo.esm.js',
path: path.resolve(__dirname, 'components'),
library: { type: 'module' }
},
experiments: { outputModule: true },
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
exclude: /node_modules/,
},
{
test: /\.js$/,
loader: 'babel-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin()
],
externals: {
vue: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm-browser.js',
'lodash': 'https://cdn.jsdelivr.net/npm/[email protected]/lodash.js'
}
};
使用上述配置打包后产物,中会把 'vue' 替换为 externals 中的 cdn 地址
// 输入
import Vue from 'vue';
// 输出结果
import Vue from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm-browser.js';
组件加载逻辑
const list = ref([]);
const addComp = async () => {
const VueDemo = await import(/* @vite-ignore */`http://cdn/components/vue-demo.esm.js`)
window.app.component('vue-demo', VueDemo.default);
list.value.push({ key: new Date().valueOf(), name: 'vue-demo' });
}
vite 配置
需要注意的是要保证本地开发时引入的 vue 也是远程的,所以需要在 vite 的配置文件中增加 alias 配置。
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'vue': 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm-browser.js'
}
}
})
缺点
1.兼容性问题:很多 Webpack 已经支持很好的功能还没有得到主流浏览器的支持
2.对很多第三方依赖的转化处理不完善,缺失完善的解决机制。要将第三方依赖的加载全部交给浏览器本身来接管,那么首先开发工具要做的就是将第三方依赖全部转换为 ESModule 的模块,而现在 npm 上的绝大部分包都是只支持 CommonJS 版本的,因此这里的转换过程通常需要由开发者自己来接管,而这其中有很多底层的问题并没有得到好的解决。同时,在 ESModule 规范推进的过程中,有许多如exports.default、exports.__esModule等利用语法来兼容 ESModule 和 CommonJS 的废案往往也都被 babel 实现,而且被许多开发者使用并且发布到了 npm 上,这就导致了现在 npm 上的许多包中有大量的废弃兼容性代码,而这些代码往往会对开发工具的转化造成阻碍。
优点
1.真正的按需加载
2.代码上更加优雅
关于 webpack 模块联邦
基于笔者对模块联邦的了解,笔者认为 Webpack 的模块联邦,目前更加适合微前端的场景,但是不太适用于低代码平台的场景。但是笔者对 webpack 模块联邦了解不够深入,判断不一定准确,欢迎有不同意见的小伙伴在评论区讨论。
结论
对比上面三个方案,方案一实现起来最简单,但是没有真正实现按需加载,随着项目规模和需要满足的业务场景的扩大,组件的公共依赖会越来越多。方案二 、方案三 都能实现真正的按需加载,其中 require.js 虽然听上去已经是上个世纪的东西了,但是兼容性和坑相对比较少。说到 ESModule, 虽然有兼容性和上面提到的一些格式转化的问题,但随着近些年 Vite 、Snowpack 的发展,在未来 ESModule 一定是大势所趋,目前笔者也正在将负责的我司内部大屏低代码平台改造为 ESModule 方式加载。
源码附件已经打包好上传到百度云了,大家自行下载即可~
链接: https://pan.baidu.com/s/14G-bpVthImHD4eosZUNSFA?pwd=yu27
提取码: yu27
百度云链接不稳定,随时可能会失效,大家抓紧保存哈。
如果百度云链接失效了的话,请留言告诉我,我看到后会及时更新~
开源地址
码云地址:
http://github.crmeb.net/u/defu
Github 地址:
边栏推荐
- How does Filebeat maintain file state?
- 4500字归纳总结,一名软件测试工程师需要掌握的技能大全
- Knowledge Graph Question Answering System Based on League of Legends
- Feature dimensionality reduction study notes (pca and lda) (1)
- Autumn recruitment work
- [Verilog] HDLBits Problem Solution - Circuits/Sequential Logic/Latches and Flip-Flops
- 零信任架构分析【扬帆】
- I in mother's womb SOLO20 years
- ROS中编译通过但是遇到可执行文件找不到的问题
- R语言使用ggpubr包的ggtexttable函数可视化表格数据(直接绘制表格图或者在图像中添加表格数据)、使用tab_add_vline函数自定义表格中竖线(垂直线)的线条类型以及线条粗细
猜你喜欢
622. 设计循环队列
nacos app
(through page) ali time to upload the jar
学习软件测试需要掌握哪些知识点呢?
基于Sikuli GUI图像识别框架的PC客户端自动化测试实践
类型转换、常用运算符
Explain the virtual machine in detail!JD.com produced HotSpot VM source code analysis notes (with complete source code)
(通过页面)阿里云云效上传jar
word标尺有哪些作用
From the physical level of the device to the circuit level
随机推荐
awk入门教程
信创建设看广州|海泰方圆亮相2022 信创生态融合发展论坛
一文带你弄懂 CDN 技术的原理
bash if conditional judgment
随机森林项目实战---气温预测
一次内存泄露排查小结
LeetCode-48. 旋转图像
通过点击CheckBox实现背景变换小案例
超多精美礼品等你来拿!2022年中国混沌工程调查启动
博客记录生活
PolarFormer: Multi-camera 3D Object Detection with Polar Transformers 论文笔记
《数字经济全景白皮书》金融数字用户篇 重磅发布!
bash if条件判断
Vs Shortcut Keys---Explore Different Programming
基于php志愿者服务平台管理系统获取(php毕业设计)
浅谈程序员的职业操守
使用.NET简单实现一个Redis的高性能克隆版(一)
Mysql重启后innodb和myisam插入的主键id变化总结
从零开始C语言精讲篇5:指针
利用ChangeStream实现Amazon DocumentDB表级别容灾复制