V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
duanzs
V2EX  ›  问与答

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

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

    简单来说:写一个大项目,然后有很多小项目,大项目可以动态引用小项目(页面或组件)而无须重新打包发布 举个例子:大项目就好比是 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   ·   实用小工具   ·   5950 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 02:25 · PVG 10:25 · LAX 18:25 · JFK 21:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.