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

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

  •  
  •   duanzs · 157 天前 · 2067 次点击
    这是一个创建于 157 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    42 回复  |  直到 2019-07-18 10:29:06 +08:00
        1
    SilentDepth   157 天前
    这不就是各种 Vue 组件库的效果?

    活用 Vue.use()、Vue.component()、<component :is="?">
        2
    duanzs   157 天前
    @SilentDepth 请注意审题哈 项目是分开的,而且小项目升级部署都不用发布大项目
        3
    SilentDepth   157 天前
    是啊,element-ui 和业务应用的项目也是分开的啊,如果 element-ui 的引用方式是 CDN ( HTTP 链接),人家更新也不需要你重新发布业务应用啊
        4
    mozhizhu   157 天前
    其实跟 webpack 的热更新是差不多的想法;
    比如服务器渲染的方法,例如 nuxt ;
        5
    duanzs   157 天前
    @SilentDepth 他就是个样式库,没可比性吧
        6
    SilentDepth   157 天前
    @duanzs #5 那好,Vue Router,这个不是样式库吧,要方法有方法,要组件有组件,你可以看看它是怎么做的。
        7
    SilentDepth   157 天前
    @duanzs #5 不要先入为主觉得 element-ui 就是个组件库所以没有参考性,其实大家的本质都是一样的,无非是动态注册的东西不一样而已。
        8
    duanzs   157 天前
    @SilentDepth 我觉的咱俩理解不一致,我的需求是能动态引入而无需重新打包发布的
        9
    wly19960911   157 天前
    首先理解路由懒加载原理,你就知道什么解决方案了。使用 requirejs 应该可以做到,但是现在的编译环境应该不行,因为项目的依赖已经完全被处理过了。
        10
    sohu022   157 天前
    目前我们这边也在基于 Vue 开发一个类似的项目管理平台, 主应用包含核心和通用的功能, 同时提供给用户开发插件的能力, 系统预埋一些扩展点, 用户可以使用插件来扩展系统的支持自定义扩展的块、 菜单、路由 等功能,
    主应用与所有的插件都是单独编译的, 插件编译好跟随插件的后台服务一起打包成一个插件的 Java jar 包, 上传到系统中, 在管理后台可以针对特定的项目启用该插件, 应该跟楼主说的这种比较类似了
        11
    sohu022   157 天前
    每一个插件也可以单独启用某一个版本的该插件
        12
    duanzs   157 天前
    @wly19960911 应该不满足我需求,因为我要求主项目不需要每次都重新构建发布
        13
    SilentDepth   157 天前
    @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 的例子,是因为它们也是这么做的。
        14
    duanzs   157 天前
    @sohu022 听起来别无二致,能普及一下技术栈吗
        15
    duanzs   157 天前
    @SilentDepth 我觉得还是有区别,你描述的引入 element-ui 和 vue-router,是因为你提前知道要引入这俩模块,但是你想一下 vscode,开发 vscode 的时候他们知道有多少插件吗?
        16
    SilentDepth   157 天前
    @duanzs #15 这个不重要呀,你需要的只是一个包的地址而已。我提 element-ui 和 vue-router 是为了引入具体场景来强调「异步加载」这个事儿,是不是 element-ui 不是问题的重点。
        17
    Rorysky   157 天前 via iPhone
    后端 动态链接库
        18
    SilentDepth   157 天前
    unpkg.com 上那么多包,你随便选一个得到地址,架设你刚好选的就是 element-ui,接下来不就和正常加载 element-ui 一个过程了?
        19
    learnshare   157 天前
    element 可不是个样式库,兄弟
    你需要解决的是按需加载、注册和执行
        20
    Rorysky   157 天前 via iPhone
    前端比后端设计模式落后一个世纪,这个问题上个世纪都解决的很好
        21
    doublleft   157 天前
    @duanzs 我也遇到并设计过这样的系统,并且走了很深(将近一年),分享下历程 希望对你有帮助吧。

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

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

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

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

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

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

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

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

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

    所有模块都可以转换成一个 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 方法并没有什么障碍,实现相应的卸载逻辑即可。
        34
    duanzs   157 天前
    @SilentDepth 关于纠结 纠结于「是否预先知晓模块信息」这一点:我觉得是我连子模块信息都不知道更谈不上引用了。
    关于整体回复:我觉得主项目使用 Vue.use( )之后不应该还得去把主项目打包发布吗?
        35
    SilentDepth   157 天前
    @duanzs #34 你是不是误解了什么。

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

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

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

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

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

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

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

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

    @duanzs #38 找到了一个翻译版: https://hijiangtao.github.io/2018/04/20/Designing-Very-Large-JavaScript-Applications/
        42
    hailun3202475   149 天前
    @duanzs 你好,我们公司最近也是要做一个和你一样需求的项目,请问你现在进展如何,我自己找了找部分文档应该是比较符合我们的需求
    用微前端的方式搭建类单页应用: https://mp.weixin.qq.com/s/DpFXTrQ3_kBX4EB6or4Q8Q
    对应的 demo 开源项目: https://gitee.com/newgateway/xdh-web (不太确定算不算,还没仔细看)
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1089 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 26ms · UTC 18:38 · PVG 02:38 · LAX 10:38 · JFK 13:38
    ♥ Do have faith in what you're doing.