前言
低代碼開發平台(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>方案一:放在全局對象上步驟打包:組件代碼打包為 umd 格式,打包時配置 Webpack externals, 使打包產物不包含公共的依賴;
上傳:打包的組件 js 上傳到 cdn;
加載:在需要使用組件時,插入一個 script ,在這個 script 中將組件放在一個全局對象上;
註冊:在 script 插入完成後,從全局對象上獲取組件,並進行註冊;
首先需要增加一個入口文件
importComponentfrom'./index.vue';if(!window.share){window.share={};}window.share[Component.name]=Component;以上面的入口文件為入口,用 Webpack 打包為 umd 格式
//組件打包Webpack配置constpath=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:[newVueLoaderPlugin()],externals:{vue:'vue',lodash:'lodash',}};html 模板組件公共依賴都需要先加入到模板 html 中
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>Document</title><scriptsrc="https://cdn/vue.global.js"></script><scriptsrc="https://cdn/lodash@4.17.21.min.js"></script></head><body><divid="root"></div></body></html>組件加載邏輯constloadComponent=(name)=>newPromise((resolve)=>{constscript=document.createElement('script');script.src=`http://xxx/${name}.js`;script.onload=script.onreadystatechange=function(){resolve();};document.querySelector('head').appendChild(script);})constaddComp=async(name)=>{awaitloadComponent(name);//註冊組件,其中app為Vue應用實例對象app.component(name,window.share[name]);}//動態註冊組件addComp('A');缺點amd 格式也是一種模塊化方案,這裡我們選擇知名度比較高的 require.js 作為 amd 模塊加載器。
步驟用 amd 格式來做遠程加載時不需要像方案一一樣,增加額外的入口文件,可以直接將 .vue 文件作為入口。以下是 Webpack 打包配置示例
//組件打包Webpack配置constpath=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:[newVueLoaderPlugin()],externals:{vue:'vue',lodash:'lodash',}};html 模板<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>Document</title><scriptsrc="./require.js"></script></head><body><divid="app"></div></body></html>組件加載邏輯//main.jsrequirejs.config({baseUrl:'https://cdn.xxx.com',map:{'*':{css:'require-css',},},paths:{echarts:'echarts@5.1.1',vueDemo:'vue-demo',vue:'vue@3.2.37',moment:'https://cdn/moment@2.29.1.min',},shim:{'ant-design-vue':['css!https://cdn/ant-design-vue@2.1.6.min.css'],},});requirejs(['vue','vue-demo','vue-app'],function(vue,vueDemoModule,VueAppModule){constapp=Vue.createApp(VueAppModule.default);app.component('vue-demo',vueDemoModule.default);constvm=app.mount('#app');});缺點這裡需要注意的是,externals 配置項中直接把公共依賴配置為 cdn 地址;
importpathfrom'path';importVueLoaderfrom'vue-loader';constVueLoaderPlugin=VueLoader.VueLoaderPlugin;const__dirname=path.resolve();exportdefault{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:[newVueLoaderPlugin()],externals:{vue:'https://cdn.jsdelivr.net/npm/vue@3.2.37/dist/vue.esm-browser.js','lodash':'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.js'}};使用上述配置打包後產物,中會把 'vue' 替換為 externals 中的 cdn 地址
//輸入importVuefrom'vue';//輸出結果importVuefrom'https://cdn.jsdelivr.net/npm/vue@3.2.37/dist/vue.esm-browser.js';組件加載邏輯constlist=ref([]);constaddComp=async()=>{constVueDemo=awaitimport(/*@vite-ignore*/`http://cdn/components/vue-demo.esm.js`)window.app.component('vue-demo',VueDemo.default);list.value.push({key:newDate().valueOf(),name:'vue-demo'});}vite 配置需要注意的是要保證本地開發時引入的 vue 也是遠程的,所以需要在 vite 的配置文件中增加 alias 配置。
//vite.config.jsimport{defineConfig}from'vite';importvuefrom'@vitejs/plugin-vue';//https://vitejs.dev/config/exportdefaultdefineConfig({plugins:[vue()],resolve:{alias:{'vue':'https://cdn.jsdelivr.net/npm/vue@3.2.37/dist/vue.esm-browser.js'}}})缺點基於筆者對模塊聯邦的了解,筆者認為 Webpack 的模塊聯邦,目前更加適合微前端的場景,但是不太適用於低代碼平台的場景。但是筆者對 webpack 模塊聯邦了解不夠深入,判斷不一定準確,歡迎有不同意見的小夥伴在評論區討論。
掃碼查看輕鬆學 TypeScript系列視頻教程
(目前已更新19期)
結論對比上面三個方案,方案一實現起來最簡單,但是沒有真正實現按需加載,隨着項目規模和需要滿足的業務場景的擴大,組件的公共依賴會越來越多。方案二 、方案三 都能實現真正的按需加載,其中 require.js 雖然聽上去已經是上個世紀的東西了,但是兼容性和坑相對比較少。說到 ESModule, 雖然有兼容性和上面提到的一些格式轉化的問題,但隨着近些年 Vite 、Snowpack 的發展,在未來 ESModule 一定是大勢所趨,目前筆者也正在將負責的我司內部大屏低代碼平台改造為 ESModule 方式加載。
參考