美文网首页
前端微服务化进阶1 - 基于umi的子模块方案

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

作者: Hello丶Alili | 来源:发表于2020-06-20 11:56 被阅读0次

    个人博客: 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/

    相关文章

      网友评论

          本文标题:前端微服务化进阶1 - 基于umi的子模块方案

          本文链接:https://www.haomeiwen.com/subject/vsmwxktx.html