基于 vue 如何实现一个可插拔式的系统 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
duanzs
V2EX    问与答

基于 vue 如何实现一个可插拔式的系统

  •  
  •   duanzs 2019-07-10 09:40:40 +08:00 5452 次点击
    这是一个创建于 2289 天前的主题,其中的信息可能已经有所发展或是发生改变。

    简单来说:写一个大项目,然后有很多小项目,大项目可以动态引用小项目(页面或组件)而无须重新打包发布 举个例子:大项目就好比是 vscode,小项目就是 vscode 的一个个插件,可以随意下载使用插件而不用每次都更新 vscode 版本,并且插件可以单独升级

    43 条回复    2020-07-23 23:59:04 +08:00
    SilentDepth
        1
    SilentDepth  
       2019-07-10 10:16:51 +08:00
    这不就是各种 Vue 组件库的效果?

    活用 Vue.use()、Vue.component()、<component :is="?">
    duanzs
        2
    duanzs  
    OP
       2019-07-10 10:23:15 +08:00
    @SilentDepth 请注意审题哈 项目是分开的,而且小项目升级部署都不用发布大项目
    SilentDepth
        3
    SilentDepth  
       2019-07-10 10:26:35 +08:00
    是啊,element-ui 和业务应用的项目也是分开的啊,如果 element-ui 的引用方式是 CDN ( HTTP 链接),人家更新也不需要你重新发布业务应用啊
    mozhizhu
        4
    mozhizhu  
       2019-07-10 10:28:59 +08:00
    其实跟 webpack 的热更新是差不多的想法;
    比如服务器渲染的方法,例如 nuxt ;
    duanzs
        5
    duanzs  
    OP
       2019-07-10 10:42:35 +08:00
    @SilentDepth 他就是个样式库,没可比性吧
    SilentDepth
        6
    SilentDepth  
       2019-07-10 10:46:16 +08:00
    @duanzs #5 那好,Vue Router,这个不是样式库吧,要方法有方法,要组件有组件,你可以看看它是怎么做的。
    SilentDepth
        7
    SilentDepth  
       2019-07-10 10:50:01 +08:00
    @duanzs #5 不要先入为主觉得 element-ui 就是个组件库所以没有参考性,其实大家的本质都是一样的,无非是动态注册的东西不一样而已。
    duanzs
        8
    duanzs  
    OP
       2019-07-10 11:08:34 +08:00
    @SilentDepth 我觉的咱俩理解不一致,我的需求是能动态引入而无需重新打包发布的
    wly19960911
        9
    wly19960911  
       2019-07-10 11:20:11 +08:00
    首先理解路由懒加载原理,你就知道什么解决方案了。使用 requirejs 应该可以做到,但是现在的编译环境应该不行,因为项目的依赖已经完全被处理过了。
    sohu022
        10
    sohu022  
       2019-07-10 11:23:17 +08:00
    目前我们这边也在基于 Vue 开发一个类似的项目管理平台, 主应用包含核心和通用的功能, 同时提供给用户开发插件的能力, 系统预埋一些扩展点, 用户可以使用插件来扩展系统的支持自定义扩展的块、 菜单、路由 等功能,
    主应用与所有的插件都是单独编译的, 插件编译好跟随插件的后台服务一起打包成一个插件的 Java jar 包, 上传到系统中, 在管理后台可以针对特定的项目启用该插件, 应该跟楼主说的这种比较类似了
    sohu022
        11
    sohu022  
       2019-07-10 11:24:20 +08:00
    每一个插件也可以单独启用某一个版本的该插件
    duanzs
        12
    duanzs  
    OP
       2019-07-10 11:31:38 +08:00
    @wly19960911 应该不满足我需求,因为我要求主项目不需要每次都重新构建发布
    SilentDepth
        13
    SilentDepth  
       2019-07-10 11:31:49 +08:00
    @duanzs #8 我说的就是「动态引入而无需重新打包发布」啊。

    Vue 动态添加应用能力就是靠 Vue.use( )(以及相关的 Vue.mixin( )、Vue.component( )、Vue.directive( ) 等),接受的参数就是一个 Plain JS Object,那么你的问题就变成「如何在不重新编译引用者的情况下获得这个 Plain JS Object 」。最简单的,如楼上所说用 RequireJS,或者自己实现一个简单的异步模块加载器(动态添加 <script>,用全局函数得到模块内容),只要目标模块地址可以在运行时确定,模块引用者(主业务应用)就不需要重新编译和发布,目标模块暴露一个 install 方法传给 Vue.use( ) 去调用,就完事了。

    我举 element-ui 和 vue-router 的例子,是因为它们也是这么做的。
    duanzs
        14
    duanzs  
    OP
       2019-07-10 11:32:38 +08:00
    @sohu022 听起来别无二致,能普及一下技术栈吗
    duanzs
        15
    duanzs  
    OP
       2019-07-10 11:37:55 +08:00
    @SilentDepth 我觉得还是有区别,你描述的引入 element-ui 和 vue-router,是因为你提前知道要引入这俩模块,但是你想一下 vscode,开发 vscode 的时候他们知道有多少插件吗?
    SilentDepth
        16
    SilentDepth  
       2019-07-10 11:40:26 +08:00
    @duanzs #15 这个不重要呀,你需要的只是一个包的地址而已。我提 element-ui 和 vue-router 是为了引入具体场景来强调「异步加载」这个事儿,是不是 element-ui 不是问题的重点。
    Rorysky
        17
    Rorysky  
       2019-07-10 11:40:30 +08:00 via iPhone
    后端 动态链接库
    SilentDepth
        18
    SilentDepth  
       2019-07-10 11:41:30 +08:00
    unpkg.com 上那么多包,你随便选一个得到地址,架设你刚好选的就是 element-ui,接下来不就和正常加载 element-ui 一个过程了?
    learnshare
        19
    learnshare  
       2019-07-10 11:42:20 +08:00
    element 可不是个样式库,兄弟
    你需要解决的是按需加载、注册和执行
    Rorysky
        20
    Rorysky  
       2019-07-10 11:45:14 +08:00 via iPhone
    前端比后端设计模式落后一个世纪,这个问题上个世纪都解决的很好
    doublleft
        21
    doublleft  
       2019-07-10 11:50:55 +08:00
    @duanzs 我也遇到并设计过这样的系统,并且走了很深(将近一年),分享下历程 希望对你有帮助吧。

    可插拔系统要求的是各个小组件组成的大系统。小组件设计要支持单独新增、发布、独立测试,大系统不用一起打包发布。我的业务场景是实现一个几百张的表单、表格、图形的查询页面。很多组件是可以重用的。

    1. 最开始是用 iframe 实现,封装成大系统+若干小系统,每个系统都是独立的 react 工程,关键点是要解决的是组件之间通讯问题。后来因为维护太多小系统,又用 webpack 封装了统一的打包发布脚手架和 CI/CD,这是第一版。

    2. 第二版我动手设计了一个 runtime 的渲染项目,把之前的小工程改为 npm 包 一个小组件。根据访问 router 向服务器拉取组件配置,这个配置描述了页面有哪些组件、他们的位置 类别 事件 关联组件等,比如 input 放在哪里。这些颗粒组件也是事先写好、独立测试发布、低耦合、不含业务的。不过最终还是有拆解不了的,当时解决办法就是单独放在业务工程,打包好一起下发。

    这样做看似优雅,其实还是有很多问题和优化空间,比如线上并行版本太多、很考验封装能力、框架升级要 rebuild 所有,要严格的设计状态控制等。

    其实如果继续发展下次我想应该是一个类似“中台”表单设计器,我当时已经封装了不少基础组件( Input、Button、Select、Radio 等),定制了超级复杂的 Table (支持多表关联 下钻 子 Table 固定行列),又整合了 E-Chats 进去,又实现了全数据驱动,完全可以实现运营同学手动拖拽设计表单。

    这套系统根据业务需求,最后支持全国几万家门店定制的数百个表单查询,不过后来因为不是重点项目,慢慢就被剥离出来了。
    wly19960911
        22
    wly19960911  
       2019-07-10 12:06:27 +08:00 via Android
    @Rorysky 前端的 script 标签不是动态链接库了?

    他要的不是动态链接库,而是动态链接模块,在原有的运行模块中再次启动一个模块。

    我只学过 Java,我不知道怎么在运行中的项目怎么插入一个 controller …至少可能实现也很麻烦。
    Rorysky
        23
    Rorysky  
       2019-07-10 12:09:23 +08:00
    @wly19960911 不扯别的,本质都是 对象 及 接口调用
    loading
        24
    loading  
       2019-07-10 12:13:00 +08:00 via Android
    @SilentDepth 还敢用 cdn 不指明版本号?上次圣诞节下雪的事忘了?
    wly19960911
        25
    wly19960911  
       2019-07-10 12:13:14 +08:00 via Android
    @Rorysky 但是框架没有整合,现在要求框架拥有动态模块的能力。就跟楼上一样对框架进行了 hack 或者直接 script 使用框架的库(只有库),做到当然可以。
    SilentDepth
        26
    SilentDepth  
       2019-07-10 12:46:38 +08:00
    @loading #24 我有建议不指明版本号吗?都说了 element-ui 和 vue-router 是针对「异步加载」的举例,咱能聊聊异步加载方面的事儿不?

    而且,antd 跟版本号有啥关系?你怎么知道你锁定的不是下雪版本呢?
    lecion
        27
    lecion  
       2019-07-10 13:11:09 +08:00 via Android
    听起来有点微前端的意思
    duanzs
        28
    duanzs  
    OP
       2019-07-10 14:06:03 +08:00
    @SilentDepth 所以这就是区别点,因为我需求是预先不知道有哪些子模块
    duanzs
        29
    duanzs  
    OP
       2019-07-10 14:06:21 +08:00
    @Rorysky 能详细一点吗
    duanzs
        30
    duanzs  
    OP
       2019-07-10 14:07:13 +08:00
    @learnshare 对,所以我就是问怎么解决
    duanzs
        31
    duanzs  
    OP
       2019-07-10 14:09:01 +08:00
    @Rorysky 是的,所以现在前端很多东西都在借鉴后端
    duanzs
        32
    duanzs  
    OP
       2019-07-10 14:12:30 +08:00
    @doublleft 感谢分享
    SilentDepth
        33
    SilentDepth  
       2019-07-10 14:30:17 +08:00
    我不明白你为什么要纠结于「是否预先知晓模块信息」这一点。

    所有模块都可以转换成一个 JS 文件。我们约定这个 JS 文件会以某种方式输出一个 Plain JS Object,并且包含一个 install 方法,这样就可以通过 Vue 插件机制 ( https://cn.vuejs.org/v2/guide/plugins.html) 动态注册到 Vue 应用中。接下来你只需要解决「获取这个 JS 文件」的问题即可。解决起来也不难,理论上任何异步加载 JS 资源的方案都可以。在整个这个过程中你的主业务应用不需要做任何变化(当然主业务应用本身要实现插件系统的底层支持)。

    至于从哪里加载,当然会有一个后端服务提供模块清单(就好比 VS Code 的插件搜索),但这个事情是业务的事情,并且与主业务应用无关了(除非搜索功能本身是主业务应用提供的)。确定待加载模块列表(不论是自动生成的还是用户手动选择的),转换为各模块的 URL,依次加载对应的 JS 文件并调用其中的 install 方法(通过 Vue.use( )),一个「插」系统就实现了。

    这里唯一需要额外实现的是「拔」,因为并没有一个 Vue.unuse( ),Vue 也不识别 uninstall 方法。但既然模块本身已经在本地了,手动调用 uninstall 方法并没有什么障碍,实现相应的卸载逻辑即可。
    duanzs
        34
    duanzs  
    OP
       2019-07-10 16:20:54 +08:00
    @SilentDepth 关于纠结 纠结于「是否预先知晓模块信息」这一点:我觉得是我连子模块信息都不知道更谈不上引用了。
    关于整体回复:我觉得主项目使用 Vue.use( )之后不应该还得去把主项目打包发布吗?
    SilentDepth
        35
    SilentDepth  
       2019-07-10 16:30:12 +08:00
    @duanzs #34 你是不是误解了什么。

    function loadExternalModule (url) { document.createElement('script') /* ... */ }

    有了这样一个函数,只要能有一个 url,是不是就可以在运行时加载一个外部 JS 模块了呢?至于模块信息……你(的业务)总得设计一个机制来生成或获得模块信息啊,不然插件系统做出来要怎么用?

    function useExternalPlugin (plugin) { Vue.use(plugin) /* ... */ }

    有了这样一个函数,只要能有一个插件对象(通过 loadExternalModule( ) 获得),是不是就可以在运行时注册一个外部 Vue 插件呢?完全不需要重新编译主项目。
    sodatea
        36
    sodatea  
       2019-07-10 16:36:15 +08:00
    不知道你需要的是不是这种模式:
    https://medium.com/@cramforce/designing-very-large-Javascript-applications-6e013a3291a3

    enhance instead of import
    duanzs
        37
    duanzs  
    OP
       2019-07-10 16:46:42 +08:00
    @SilentDepth 感谢老哥指点,对这块确实基础薄弱
    这里我再 引申 /直白 一下问题。
    举一个例子,有一个子项目,比如说是用 vue 写的一个单页计算器
    子项目肯定是以单独项目的方式去打包,再以某种方式发布到主项目中。子项目( webpack )打包出来可能会有 index.html、bundle.js 、css、大图片等。这种怎么去 use。
    duanzs
        38
    duanzs  
    OP
       2019-07-10 16:47:45 +08:00
    @sodatea 打不开,尴尬
    sodatea
        39
    sodatea  
       2019-07-10 16:56:24 +08:00
    @duanzs medium 被墙了,自行翻出去吧……
    SilentDepth
        40
    SilentDepth  
       2019-07-10 17:09:01 +08:00
    @duanzs #37 这个要分情况了。

    如果子项目本身就可以独立运作并发挥业务价值(有自己的业务环境、root 实例、入口页、整体布局等),只是在特定情况下需要「嵌入」到一个更大的应用中,但内外并没有太多交互,那么你可以考虑 <iframe>这种情况,你只是希望两个应用显示在一个页面中。(这种情况讲道理不能算「插件」。)

    而,如果你不想用 <iframe>,你可以考虑 Vue-in-Vue (我瞎编的术语)。子项目的也是一个完整的 Vue 应用,其实只是需要一个 mountpoint 而已,那么主项目应用留出这么一个元素让子项目去 mount 即可。此时子项目的 install 方法就是一句 new Vue(...).$mount(...)。

    如果子项目是一个业务模块,需要依赖外部环境才能发挥业务价值,那么子项目的 index.html 应该只是为了方便独立开发调试而存在,真正 build 出来的应该是一个库(也就是 dev 和 build 是两套打包流程)。剩下的就跟 element-ui、vue-router 等没什么本质不同了。
    SilentDepth
        41
    SilentDepth  
       2019-07-10 17:15:58 +08:00
    @sodatea #36 (文章真长!)我觉得他想要的是这种模式。

    @duanzs #38 找到了个翻译版: https://hijiangtao.github.io/2018/04/20/Designing-Very-Large-Javascript-Applications/
    hailun3202475
        42
    hailun3202475  
       2019-07-18 10:29:06 +08:00
    @duanzs 你好,我们公司最近也是要做一个和你一样需求的项目,请问你现在进展如何,我自己找了找部分文档应该是比较符合我们的需求
    用微前端的方式搭建类单页应用: https://mp.weixin.qq.com/s/DpFXTrQ3_kBX4EB6or4Q8Q
    对应的 demo 开源项目: https://gitee.com/newgateway/xdh-web (不太确定算不算,还没仔细看)
    maxincai
        43
    maxincai  
       2020-07-23 23:59:04 +08:00
    @sohu022 是否可以留下联系方式,我这边也需要做一个类似的系统,愿意有偿请教。

    加我 VX maxincai
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     879 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 21:22 PVG 05:22 LAX 14:22 JFK 17:22
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86