前端微服务化进阶1 - 基于umi的子模块方案

个人博客: https://alili.tech/

距离第一篇聊前端微服务的文章已经时隔大半年,很多人对此感兴趣.

今天我们就聊一聊,我们如何基于umi来打造一个更完善的前端微服务的子模块.

如果你用的是react以外的前端技术栈,
我的很多处理做法也可以应用在其他技术栈上.

希望对你也有所帮助.

优秀的umi框架

在前端中后台项目上,前端微服务化的需求相对是比较旺盛一些的.

说到中后台,很多企业都是基于antd的组件来构建自己的项目.

自去年的see conf之后,蚂蚁的一款可插拔的企业级 react 应用框架 umi发布了.

这款框架与antd息息相关,antd结合umi使用那是相当的自然与流畅.

可以说,基于umi与antd构建的项目非常的漂亮.这么优秀的框架,如果让他适用于我们的前端微服务架构,岂不美哉?

umi也有相关的类似微服务方案: https://github.com/umijs/umi-example-monorepo

但是umi提供的方案,有很大的局限性.
如果可以接入single-spa的微服务方案,独立开发,独立部署等等的前端微服务化红利,
会让你的项目日后有更大的发展空间.

基于umi插件机制做到前端微服务化

umi 提供了非常强大的插件机制,正是由于这一点,我们才可以让umi也可以接入到微服务架构中来

umi插件介绍

umi插件的基本介绍:

https://umijs.org/zh/plugin/

umi插件开发

这里介绍了如何开发一个简单的umi插件:

https://umijs.org/zh/plugin/develop.html

接入single-spa的umi插件

export default (api, opts) => {
  // 以下的所有代码都写在这里面哦
};

渲染入口处理方法

定义一个动态的元素,当我们的base app 需要加载子模块的时候,会渲染出子模块需要渲染元素.

我们的子模块找到了自己模块需要渲染的节点的时候,就会渲染出来.

  const domElementGetterStr = `
      function domElementGetter() {
        let el = document.getElementById('submodule-page')
        if (!el) {
          el = document.createElement('div')
          el.id = 'submodule-page'
        }
        let timer = null
        timer = setInterval(() => {
          if (document.querySelector('#submoduleContent.submoduleContent')) {
                document.querySelector('#submoduleContent.submoduleContent').appendChild(el)
                clearInterval(timer)
                timer = null
          }
        }, 100)

        return el
    }`

使用single-spa-react

在umi的入口文件导入single-spa-react ,根据模块的属性来判断模块在运行时是否渲染在root节点上还是指定节点

// 生产环境使用
if (process.env.NODE_ENV === 'production') {
 api.addEntryCodeAhead(`
    import singleSpaReact from 'single-spa-react';
    let reactLifecycles;
    reactLifecycles =  singleSpaReact({
        React,
        ReactDOM,
        rootComponent: (customProps) => window.g_plugins.apply('rootContainer', {
        initialValue: React.createElement(require('./router').default,customProps),
        }),
        domElementGetter: ${options.base?`() => document.getElementById('root')`:domElementGetterStr}
    });
  `);
}

对外导出标准的生命周期

清空umi原来的渲染方法,并且对外导出single-spa需要的生命周期.

// 生产环境使用
if (process.env.NODE_ENV === 'production') {
api.modifyEntryRender(``)

api.addEntryCode(`
    export const bootstrap = [
    reactLifecycles.bootstrap,
    ];

    export const mount = [
    reactLifecycles.mount,
    ];

    export const unmount = [
    reactLifecycles.unmount,
    ];
    `)
}

这样我们就得到了一个兼容single-spa的umi子模块.

打包相关

    api.modifyWebpackConfig((config) => {
     // 打包的还是amd模块
      config.output.libraryTarget = 'amd'

      // 指定模块名称
      config.output.library = options.name;

      //根据自己部署情况来修改outputPath
      config.output.path = resolve(`./dist/${options.deployPath}/`);
      
      // 根据自己部署情况来修改publicPath
      config.output.publicPath = options.deployPath;
      return config;
    })
  }


  api.modifyDefaultConfig(memo => ({
      // webpack的配置修改,umi也提供了 chainWebpack
      ...memo,
      //指定路由模式
      history: 'hash',

      // 导出用于通信的store文件
      // 如果你不知道这个是用来干什么的,可以读一读以前的文章
      chainWebpack(config) {
        config
          .entry('store').add('./src/store.js')
          .end()
      }
    }));

umi的全局变量问题

umi对外提供了很多的全局变量,当我们的微前端架构中,只有一个模块是umi构建的话,不需要考虑这个问题,如果有多个模块使用了umi,将会出现全局变量冲突的问题.还好umi的全局变量是有规范的,我们可以针对性处理.

我给出以下解决方案,可能相对暴力,但是也能解决冲突的问题.如果你有更好更加优雅的办法,欢迎交流.

大致思路是,在项目打包完成后,把每个文件的全局变量全部替换成其他的名字.

// 打包后替换全局变量以免冲突
api.onBuildSuccess(() => {
    const outPath = resolve('.', `dist/${options.deployPath}`);
    readdir(outPath, 'utf8', (err, data) => {

      data.forEach((item) => {
        if (!lstatSync(resolve(outPath, item)).isDirectory()) {
          readFile(resolve(outPath, item), 'utf8', (error, files) => {
            if (error) {
              console.log(error);
              return
            }
            // 替换全局变量
            const result = files.replace(/window.g_/g, `window.g_${options.name}_`);

            writeFile(resolve(outPath, item), result, 'utf8', (err2) => {
              if (err2) console.log(err2);
            });

          })
        }

      });

    })
  })

尾巴

umi的插件机制真的很优秀,为了让umi也可以接入到single-spa的微服务化的方案中来,基本上umi源码都看了一遍.真的是受益匪浅~

以上就是让umi微服务化的方法,请根据自身的项目情况修改与使用.
后面我还会介绍一些前端项目微服务化之后,带来的很多意想不到的骚操作.

这种基础技术的进步,未来可以带来无法估量的收益与改变.

请关注后续的系列文章.

相关系列文章

https://alili.tech/tags/microfrontend/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336