混合技术栈下的微模块和微容器融合

1,158 阅读12分钟

技术栈

micro-app、Hel-micro、vue2、pnpm

关于hel-micro与micro-app

hel-micro是业内里首个模块联邦 sdk 化,免构建、热更新、工具链无关的微模块方案,让模块联邦技术从构建工具插件层面提升到 sdk 层面,使用更灵活,模块流通性更好(工具链无关)。

micro-app 微前端是一款基于 Web Components 微前端框架,具备成本低、速度快、原生隔离、功能强等一系列优点。

微容器对比

主要技术栈就是vue,选择wujie就是看中了它的简洁和侵入性弱,这样接入成本就低了很多,更喜欢它基于WebCompoent的实现原理。推荐文章

微模块对比

相比传统的npm共享方式,hel-micro也具有更高效的代码共享能力(运行时共享)

Webpack 模块联邦改造 (文档)

  • vue-cli 改造webpack5
  • 对模块联邦接入有学习时间成本

Hel-micro 实现原理

特点

  • 单一技术栈应用无限拆分
  • 多技术栈下微容器与微模块混合
  • 模块治理&管控、工具与生态建设

sdk化后,任何技术栈、任何工具链均可无损、无痛接入模块联邦技术。

运行时的模块消费关系

从工具链回归到js语言本身,意味着模块消费关系从编译时提升到运行时,将极大提高动态载入远程模块的灵活性,为更复杂的业务赋能。

双构建机制

hel-micro使用rollup打包本地可静态导入的代理文件,使用webpack打包远程注入的实际运行代码,来达成可以本地静态导入node_modules里的代理模块对象得到完整的类型提示,让用户能得到像使用本地模块一样地使用远程模块的极致开发体验 (如果不需要发类型或把文件发布到unpkg 服务,不用关注rollup)

内定了4个目录hel_dist,hel_proxy,hel_proxy_es,hel_bundle来承载不同的产物,供package.json配置不同的入口。

其中hel_proxy,hel_proxy_es目录下的文件是就是我们说到的模块代理对象的入口文件,我们可以看到该文件近乎一个空壳,所以它对模块使用方的打包体积大小影响几乎可以省略不计。

hel-micro-vue 只是一个规划中的工具,辅助加载远程vue组件,提供shadow-dom样式隔离

微前端关系图

平台自己的组成(平台本身就是多个模块组合起来的一个平台)
平台和模块之间的关系(具像化到技术层面为:加载模块,管控权限,运行时通信,卸载)
模块和模块之间的关系(具像化到技术层面为:加载模块,运行时通信,卸载)

注意:hel只是一种远程模块加载方案,可以用在任何框架里的

单一技术栈

每个项目都是基座 + 子应用(一堆页面集合)+ 项目间共享组件,子应用上线后自带的基座不执行,被线上基座激活

然后慢慢的把一些上下文轻量的组件抽到xccomps里独立维护

比如访问 xxx.com/app1 激活 app1应用,app1下有一堆页面 app1/page1 app2/page2 ,同时 app1 可以引入 app2 里的某个弹窗层或者大列表(大粒度业务组件)

app2 也可以引入 app1里的相关需要复用组件

应用间互引,同时也可以引公共层的xccomps

hel模块如何接入场景方?

1、旧场景 使用 micro-app 接入场景url

2、新的场景按照新模式开发hel模块

项目改造成hel模块 (vue-admin-template-with-hel)

基于 vue-admin-template 改造为远程模块,详细改造说明见 HEL_DESC.md

改造说明(from HEL_DESC)

提供方改造

安装相关包

npm i hel-lib-proxy hel-dev-utils typescript@4.8  rollup@2 rollup-plugin-typescript rollup-plugin-terser shx cross-env

typescript 4.9有个破坏性变更,所以必须要<=4.8

github.com/microsoft/T…

下沉入口文件

将原来的 src/main.js 内容复制到 src/loadApp.js

新增模块暴露目录

新增 src/entrance 目录作为统一暴露模块的出口目录

注意:导入方式需要对齐,不对齐在使用的时候,对不齐的方式编译层面会人为注入一个default,Ts 有个这个配置干这个事allowSyntheticDefaultImports,默认是true

1、components/index.js

export { default as SvgIcon } from './SvgIcon'
export { default as Hamburger } from './Hamburger'
export { default as TestTitle } from './TestTitle'

entrance/libProperties.js

import * as comps from '../components'

export default comps

2、components/index.js

import TestTitle from './TestTitle/index.vue'

export default {
  TestTitle
}

entrance/libProperties.js

import comps from '../components'

export default comps

新增模块名描述文件

新增 src/configs/subApp.js 文件描述模块名

改造 main.js

引入 hel-lib-proxy 包,做分流控制

构建层引入模块描述文件

根目录引入 subApp.js 文件,对接 hel-dev-utils,供构建工具的vue.config.js文件使用,主要改动有

  • vue.config.js里修改 publicPath、distDir、output.jsonpFunction、externals
  • public/index.html 引入cdn vue
  • package.json 启动命令里声明 PORT,对齐 publicPath 里的端口号
  • 复制 github.com/hel-eco/hel… 三个文件到 scripts 目录下
  • package.json 新增命令 build用于构建hel包,可参考 github.com/hel-eco/hel… 直接复制
  • package.json 新增 files 表示npm发布要提交的文件范围

模块发布

注意:模块发布前,请先看 推荐文章

先修改版本号,再执行打包与发布命令(这点很重要,顺序不能弄反为:先构建再修改版本号)

发布CDN

发布NPM

如果第一次上传 npm

  • npm config set registry http:/xxx/repository/xxx/
  • 在命令行中登录 npm login
    1. name: xxx
    2. password: xxx
    3. email: 随意(建议用公司邮箱)
npm run build
npm publish

使用方使用

入口文件先绑定vue运行时

// 主项目可以 bindVueRuntime透传vue下来
import { bindVueRuntime } from 'hel-micro';
import Vue from 'vue';
bindVueRuntime({Vue});

懒加载

export default {
  components: {
    // vue3
    Hamburger: defineAsyncComponent(async () => {
      const comps = await preFetchLib("lib-zhangbb");
      return comps.Hamburger;
    }),
    // vue2
    TestTitle: async() => {
      const comps = await preFetchLib('lib-zhangbb-test')
      return comps.TestTitle
    },
  },
};

预加载

原入口文件下沉,然后加载远程模块,参考 github.com/hel-eco/hel…

await preFetchLib('lib-zhangbb')

然后安装lib-zhangbb, 其他地方可import 静态导入模块

import { Hamburger } from 'lib-zhangbb';

hel-micro在大型前端应用里的实践

单一技术栈下的无线应用拆分

项目现状

旧版架构

新版架构

CDN规划

模块管控平台(热更新组件管理平台)

用户可将hel-meta.json保存到后台数据库(可结合 devops 流水线),以便搭一个中心化的模块管控平台,对模块实施版本预览、灰度放量、秒级回滚等工作(做到秒回滚秒更新,分块编译,细粒度更新)

模块如何划分及管理

管理方式:npm/cdn

组件我们分 基础组件 业务组件 场景组件

common模块:utils的公共方法、样式、基础组件

思考

1、微前端主要解决的不就是独立开发,独立部署,降低业务模块之间的耦合,用monorepo架构会不会加强了子应用的耦合,比如http模块都用公共的,子应用一因为业务然后对http模块进行了扩展,然后应用二没有进行对应修改,然后应用二就报错了。

全部独立开发,带来的工作量也很大,考虑开发和维护成本,始终还是要面对组件共享的问题,解决组件共享有几种方案:npm 包,git submodule,webpack5 模块联邦,monorepo,我们选择了monorepo(npm包,git sub 都使用繁琐,模块联邦才出不久,对于大的项目不想踩坑)。对于你提到的示例,我们在封装的时候,要做到高内聚,低耦合,高可扩展,对于不同子应用的特定需求,放到子应用内去实现。对于http模块,最佳实践就是写一个http 类,每个子应用自己去实例化,并扩展自身的需求

2、hel 微模块是怎么定义划分的,如果是模块联邦的话,项目一个详情页面也是可以分享出去,是否可以把项目当成既是容器也是微模块?

模块可大可小哇,大到一个完整的页面(通过路由激活挂载),小到一个函数,开发者自己发挥,一个项目可以既是应用,也可以对接hel sdk暴露成微模块

3、组件在运行时远程加载渲染 性能怎么样?

首次下载资源有点性能损耗,后续长效缓存后就很快了

4、我们是云桌面开发的,有npm私服,这块对hel接入没有影响?

1、完全没影响啊,hel允许对接npm私服的,已经有实践上生产了,
2、不过如果你们要走unpkg私服的话,建议走 可负载均衡的cdn服务
3、我们一开始走私有unpkg 在一台服务器上跑,但是为了规避风险,我们又搭建了minio 用十台机器做负载均衡
4、也可以用unpkg部署多台机器

5、Vue的有计划样式隔离?

1、vue的需自己去实现,目前react提供 hel-micro-react
2、如果主和子能够协调用一套ui,建议关闭shadow,渲染shadow-dom有额外的性能损耗

6、父子应用之间都是统一技术栈和UI库,是不是其实可不考虑样式隔离这样的问题?

1、是的,完全不用考虑样式隔离
2、比如用antd,统一抽到externals里,这样主 子用同一套ui,很方便

7、应用拆分 是按照什么规则来的

我们按实体拆,例如 话题 短内容 作者 视频等,视频里有很多功能,例如视频列表,视频更新弹窗,其他页面比如作者底层页里需要用到视频列表,直接载入视频子应用里的列表组件复用,例如视频里某个列表的作者名称需要悬浮提示作者详情,直接载入作者子应用里的作者提示组件(这个组件本身自带很多功能,例如关注,收藏等),而各个应用本身也携带自己路由系统,供基座载入去激活,形成了一种网状关系,这个架构后续会开源出来,代号是hel-react-app

8、权限控制

基座拉到权限列表存到global store里,每个子模块自己去读,子模块本地运行时会启动自己的基座(很薄的一层,带排版,带登录逻辑),发布到线上复用真实的基座逻辑(自带的基座不启动)

9、vue2、vue3 并存的是不是就要考虑微容器呢

1、不用也行,需要各自的运行时自己去驱动,不能复用运行时,同时通信也会麻烦点
2、微容器的意义在于运行隔离,屏蔽window污染,全局函数污染等副作用、以及样式污染
3、能够解决或约定规避这些问题就不需要套容器

10、目前外源包的模块部署方式?

目前外源包的模块部署方式,可以部署 npm + unpkg 私服来解决模块代码私有化的问题

11、运行时加载模块关系会不会导致js文件过多影响性能, 另外通过这种方式将项目大部分依赖远程加载来减少构建时间可行不,还是说仍要权衡前端性能

1,因为运行时代码由webpack构建,可以控制产物尽可能少的生成chunk,最多不超过4个是最好的( 包含css,见这个仓库的解释 github.com ),按一个模块有4个文件需要加载,之前看到过文章说chrome目前加载上1000个js文件时会出现一定的性能瓶颈,换算出来的话就是预加载250个以上的远程模块才能出现瓶颈(按优先走本地缓存元数据来算,不发起元数据获取的网络请求),基本上大部分项目规模还达不到这个程度,真的到了瓶颈,可以一定程度上做模块合并+懒加载一部分首屏不需要的模块来缓解。

2 因为本地参与的代理对象非常薄(几乎一个空文件),对本地开发和构建速度是有实打实的提高的(我们内部测试过),欢迎做测试哈,其实模块联邦技术都能够提高构建速度的(无论是webpack 5 mf 还是 hel-micro),通过模块联邦提速构建是真实可行的,可以参考umi4搞的那个mfsu介绍(zhuanlan.zhihu.com),也是有相关数据支撑的。

其实hel-micro 的也是模块联邦的一种实现,只不过从更高的维度去做,彻底脱离工具链,回归到js语法本身,大大提高了远程模块的流通性(基于不同工具链搭建的应用可以模块互认),并降低模块联邦的接入门槛(纯sdk,无需任何改造)。

12、模块联邦(mf),它是可以在项目中单独分享某个组件或者某个方法,hel 它需要将分享组件、方法单独项目维护的?

和项目一起维护,也可以抽离出去独立放另一个项目,取决于你的设计

13、为什么直接在项目里导出了微模块 ? 不直接抽出来 单独做呢

可以项目里直接导出,也可以单独做,取决于你的架构设计哈,例如我们现在的微前端架构如下

14、cdn 规划

index.html 引cdn建议再头部head里早早引入
如果联邦组件对这些cdn模块有依赖,肯定是需要先加载完这些cdn模块才能加载联邦组件的

两种方式讲react运行时传给子模块:
1,上面那个例子( 配置externals,react来着cdn https://github.com/hel-eco/hel-tpl-remote-react-comp-ts/blob/master/public/index.html)
2,  主项目入口文件调用 bindReactRuntime
import { bindReactRuntime } from '@tencent/hel-micro';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactReconciler from 'react-reconciler';
import ReactIs from 'react-is';

bindReactRuntime({React, ReactDOM, ReactReconciler, ReactIs})

1 宿主cdn引入vue并结合externals
2 宿主调用 bindVueRuntime 给子模块 (bindVueRuntime 透传的)

业务逻辑都下沉到loadApp里加载, 入口main.js 不能有其他业务逻辑

模块联邦组件 里面不会打包Vue的,配置排除打包,模板已经设置

LEAH_Vue