close

前言

低代碼開發平台(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');缺點
組件的依賴共享,需要依賴提前先放到全局,html 模板需要較頻繁改動;
全局對象上要掛載的內容越來越多,影響加載性能,沒有做到真正的按需加載;
依賴版本難以管理。如 A 組件依賴了 loadsh 1.0, 而 B 組件依賴了 lodash 2.0,但是全局對象上的 lodash,同時掛載兩個版本就必然會有衝突,因此版本必須一致;且後續如果某個組件要升級某個依賴的版本,也勢必會影響所以其他組件。
方案二:amd

amd 格式也是一種模塊化方案,這裡我們選擇知名度比較高的 require.js 作為 amd 模塊加載器。

步驟
打包:組件代碼打包為 umd 或 amd 格式,打包時配置 Webpack externals,使打包產物不包含公共的依賴;
上傳:打包的組件 js 上傳到 cdn;
加載&註冊:在需要使用組件時,用 requirejs 獲取組件,並進行註冊。
組件打包

用 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');});缺點
平台代碼(上述代碼的vue-app)也需要編譯為 amd 格式,然後上傳到 cdn 上,開發流程改變,需要定製化的開發平台項目的發布機制。
有些第三方庫沒有提供 amd 或 umd 格式,需要開發者自己開發工具去轉換(此過程中可能有很多坑要踩);
優點
相比於方案一,組件的依賴可以有版本差異且互相不影響。
組件和組件的依賴都可以按需加載,真正做到按需加載。
有現成的加載 css 文件的機制;
方案三:ESModule步驟
打包:組件代碼打包為 esm 格式,打包時配置 Webpack externals, 使打包產物不包含公共的依賴;
上傳:打包的組件 js 上傳到 cdn;
加載&註冊:在需要使用組件時,用 esm 的動態引入獲取組件,並進行註冊;
組件打包

這裡需要注意的是,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 已經支持很好的功能還沒有得到主流瀏覽器的支持
對很多第三方依賴的轉化處理不完善,缺失完善的解決機制。要將第三方依賴的加載全部交給瀏覽器本身來接管,那麼首先開發工具要做的就是將第三方依賴全部轉換為 ESModule 的模塊,而現在 npm 上的絕大部分包都是只支持 CommonJS 版本的,因此這裡的轉換過程通常需要由開發者自己來接管,而這其中有很多底層的問題並沒有得到好的解決。同時,在 ESModule 規範推進的過程中,有許多如 exports.default、exports.__esModule 等利用語法來兼容 ESModule 和 CommonJS 的廢案往往也都被 babel 實現,而且被許多開發者使用並且發布到了 npm 上,這就導致了現在 npm 上的許多包中有大量的廢棄兼容性代碼,而這些代碼往往會對開發工具的轉化造成阻礙。
優點
真正的按需加載
代碼上更加優雅
關於 Webpack 模塊聯邦

基於筆者對模塊聯邦的了解,筆者認為 Webpack 的模塊聯邦,目前更加適合微前端的場景,但是不太適用於低代碼平台的場景。但是筆者對 webpack 模塊聯邦了解不夠深入,判斷不一定準確,歡迎有不同意見的小夥伴在評論區討論。

掃碼查看輕鬆學 TypeScript系列視頻教程

(目前已更新19期)

結論

對比上面三個方案,方案一實現起來最簡單,但是沒有真正實現按需加載,隨着項目規模和需要滿足的業務場景的擴大,組件的公共依賴會越來越多。方案二 、方案三 都能實現真正的按需加載,其中 require.js 雖然聽上去已經是上個世紀的東西了,但是兼容性和坑相對比較少。說到 ESModule, 雖然有兼容性和上面提到的一些格式轉化的問題,但隨着近些年 Vite 、Snowpack 的發展,在未來 ESModule 一定是大勢所趨,目前筆者也正在將負責的我司內部大屏低代碼平台改造為 ESModule 方式加載。

參考
requirejs 中文文檔 [https://www.requirejs-cn.cn/]
ESModule 系列 ㈠ :演進 [https://mp.weixin.qq.com/s/0AHmP70HnLUZeJWQlRtUKw]
Require.js 加載 css 依賴 [https://blog.csdn.net/lihefei_coder/article/details/81333036]
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()