DoraCMS 插件化探索(一)
DoraCMS 从今年年初的时候开始有插件化构想,为什么会有这种想法?其实就目前发布的DoraCMS 2.1.3 来讲,对比之前的版本有很多优化,最重要的一点
是建立了比较清晰的代码结构,后台管理基于vue的spa模式,服务端 MVC 架构也非常明显,对比2.1.2 版本服务端抽离出了server 层,让数据操作更加便捷,避免混乱的各种查询。这是 2.1.3 的目录结构:
├─build // webpack 相关配置文件
│
├─client // 客户端文件(前台/后台)
│ │
│ ├─index // 前台组件
│ │
│ ├─manage // 后台组件
│ │
│ └─template // 初始模版
│
├─databak // 默认数据备份目录
│
│
├─logs // 日志目录
│
├─public // 静态文件目录
│ │
│ ├─admin // 后台vue编译后的文件目录
│ │
│ ├─apidoc // api文档目录
│ │
│ ├─plugins // 前台依赖的相关组件
│ │
│ ├─themes // 皮肤目录
│ │
│ ├─ueditor // ueditor插件目录
│ │
│ ├─upload // 文件上传目录
│ │
│ └─vendor // 后台静态dll目录
│
│
├─server // 服务端目录
│ │
│ ├─bootstrap // 前台渲染相关
│ │
│ ├─configs // 系统配置
│ │
│ ├─locales // 国际化
│ │
│ ├─middleware // 中间件
│ │
│ │
│ ├─lib // 核心层
│ │ ├─contorller // 控制器
│ │ │
│ │ ├─model // 数据模型
│ │ │
│ │ ├─service // 数据库操作
│ │ │
│ │ └─utils
│ │ ├─cache // redis缓存
│ │ │
│ │ ├─memoryCache // 内存缓存
│ │ │
│ │ ├─authPower.js // 资源鉴权
│ │ │
│ │ ├─authSession.js // session 鉴权
│ │ │
│ │ ├─authToken.js // token鉴权
│ │ │
│ │ ├─mime.js // 文件类型
│ │ │
│ │ ├─siteFunc.js // 公共方法
│ │ │
│ │ └─validatorUtil.js // 信息校验
│ │
│ │
│ └─routers // 路由
│
│
│
└─views // 前台模板
│
├─dorawhite // 主题目录
│
├─admin.html // 后台管理模板
│
└─adminUserLogin.html // 后台登录模板
先从基于vue后台管理说起,后台管理的界面以及逻辑在 /client/manage 下,每个模块非常清晰:
熟悉 DoraCMS 的童鞋都知道,开发完成后,执行
npm run build
就可以将我们开发的vue工程编译为浏览器可以识别的es5,发布到生产。非常方便,但是在开发过程中,我渐渐发现一些问题:
1、开发了很多模块后,编译速度非常慢。
2、有时候只改了一个vue文件,也需要重新编译,非常浪费资源
3、有些功能模块我现在不想用了,怎样友好的剥离掉?
这些是我们在实际开发场景下经常遇到的问题,于是通过google,我了解到了前端微服务,至今为止,我认为微服务将成为今后大型应用必不可少要考虑的架构。
什么是“微服务”,说通俗一点,我认为的微服务就是模块拆分,相互隔离解耦,互不影响,按需加载等;那么基于 DoraCMS 2.1.3 目前的架构,如何来做微服务呢?通过大量的调研和实践,我找到了一个比较合适的方案 : single-spa , 我做了如下尝试:
1、把每个模块独立出来,每个模块作为一个标准的小app抽离(基于 vue-cli)
2、通过 single-spa 提供的方法将每个模块串联起来,类似下面这样(DoraCMS 的实现相同)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Coexisting Vue Microfrontends</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="importmap-type" content="systemjs-importmap"> <script type="systemjs-importmap"> { "imports": { "navbar": "http://localhost:8080/app.js", "app1": "http://localhost:8081/app.js", "app2": "http://localhost:8082/app.js", "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js", "vue": "https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js", "vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js" } } </script> <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" as="script" crossorigin="anonymous" /> <link rel="preload" href="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js" as="script" crossorigin="anonymous" /> <script src="https://unpkg.com/import-map-overrides@1.7.2/dist/import-map-overrides.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/system.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/amd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-exports.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-register.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/use-default.min.js"></script> </head> <body> <script> (function() { Promise.all([System.import('single-spa'), System.import('vue'), System.import('vue-router')]).then(function (modules) { var singleSpa = modules[0]; var Vue = modules[1]; var VueRouter = modules[2]; Vue.use(VueRouter) singleSpa.registerApplication( 'navbar', () => System.import('navbar'), location => true ); singleSpa.registerApplication( 'app1', () => System.import('app1'), location => location.pathname.startsWith('/app1') ) singleSpa.registerApplication( 'app2', () => System.import('app2'), location => location.pathname.startsWith('/app2') ) singleSpa.start(); }) })() </script> <!-- See https://github.com/joeldenning/import-map-overrides#user-interface --> <import-map-overrides-full show-when-local-storage="overrides-ui"></import-map-overrides-full> </body> </html>
3、处理权限、路由等细节
经过架构变更之后,可以达到这样的效果:
1、每个模块相互独立了,编译出的js彼此分开。
2、按需编译,改哪个模块,编译哪个模块,借助jenkins,更新模块到服务器非常方便:
3、再也不用担心模块过多引起的编译速度过慢了,添加哪个编译哪个
4、后台的所有模块跟服务端基本没有关系了,独立开发,于是即使多个人同时开发后台管理模块,也相互不影响。
PS: single-spa 微服务架构已经应用到 DoraCMS 2.1.4 版本,有兴趣的童鞋在后面发布后可以clone下来试试。