qiankun + oss + turbo(monorepo)+ 阿里云效cicd 实现微前端

357 阅读4分钟

“简简单单”实现微前端

  • 怎么实现,在官方网站介绍的是怎么接入一个个系统整合,不过实际有时候由于产品或者业务一开始根本就没有想好怎么架构业务系统,基于自己的经验想到,如果做成一个菜单一个模块是不是方便点,基于这个想法我们是不是可以想到怎么加载每个菜单? 菜单.png

    1、怎么加载每个菜单?

    古老的方式就是iframe 链接每个url,达到加载每个项目,但是弊端也很明显,通信,共享方法,都是比较局限,主题是也很难统一, 乾坤在这个方面挺方便的,如果是自动加载用当前方式 registerMicroApps ,我们这次用的方法是 loadMicroApp 手动加载。

   import { loadMicroApp } from 'qiankun';
   import React from 'react';

   class App extends React.Component {
     containerRef = React.createRef();
     microApp = null;

     componentDidMount() {
       this.microApp = loadMicroApp({
         name: 'app1',
         entry: '//localhost:1234',
         container: this.containerRef.current,
         props: { brand: 'qiankun' },
       });
     }

     componentWillUnmount() {
       this.microApp.unmount();
     }

     componentDidUpdate() {
       this.microApp.update({ name: 'kuitos' });
     }

     render() {
       return <div ref={this.containerRef}></div>;
     }
   }

第一个技术问题解决

2、每个菜单项目从哪里来?

  • 这个就会想到如果每个菜单都是一个工程那就会很麻烦,出现多个git,这里我们借助turbo 管理工具实现,当然也有其他实现方式,比如leran+yarn,我们这里选择turbo! image.png

image.png apps 里面我们可以放入多个菜单项目,这个,packages放入公共模块包,文件已经整理好,下面就是怎么实现打包了?

3、如何实现打包发布?

到这个时候就需要配合乾坤设置webpack打包配置和oss配置了,上代码!

// 需要在根目录业务代码里面引入public-path.js 文件
if (window.__POWERED_BY_QIANKUN__) {
   // eslint-disable-next-line
   __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
   //__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

 import "./public-path";
 import React from "react";
 import ReactDOM, { render } from "react-dom";
 import { ConfigProvider } from "antd";
 import zhCN from "antd/es/locale/zh_CN";
 import Page from "./Page";

 const App: React.FC = () => {
   return (
     <ConfigProvider locale={zhCN}>
       <Page />
     </ConfigProvider>
   );
 };

 function renders() {
   render(<App />, document.getElementById("app"));
 }

 if (!window.__POWERED_BY_QIANKUN__) {
   renders();
 }
 /**
  * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
  * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
  */
 export async function bootstrap() {
   console.log("react app bootstraped");
 }
 /**
  * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
  */
 export async function mount(props: any) {
   console.log(props);
   renders();
 }
 /**
  * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
  */
 export async function unmount() {
   ReactDOM.unmountComponentAtNode(document.getElementById("app") as any);
 }
 /**
  * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
  */
 export async function update(props: any) {
   console.log("update props", props);
 }

上面是业务代码配置,下面就是wbepack配置,我这个是webpack5的配置,webpack4有差异的

output: {
     clean: true, // Clean the output directory before emit.
     publicPath: isDev ? `/` : `demo/201412121`, //demo/20141212是后端对应的oss url项目路径
     library: {
       name: `[name]`,
       type: "umd",
     },
     filename: "js/[name]_[fullhash:8].js",
     chunkFilename: "js/[name]_[fullhash:8].js",
     chunkLoadingGlobal: `webpackJsonp`,
     globalObject: "window"
}

配置做完之后怎么去实现上传oss,首先阿里云提供了很完善的oss存储对象服务

image.png

上传之前还要实现如何打包cicd,这里我选择了阿里的云效,非常的方便

image.png 在上面创建了自己的git,然后把自己的代码上传,点击代码里面创建自己的流水线,

image.png 只要自己的代码推送就会自动触发cicd打包,首先是进行代码检查,然后打包,(里面的流程可以自己配置) 这个时候打包环境也具备了,下面就是打包成功之后上传oss。 代码如下:

const OSS = require("ali-oss");
const path = require("path");
const fs = require("fs");
const pkg = require(path.join("../", "package.json"));
const version = require("./version.json");
const time = version.time;
const client = new OSS({
  region: '<oss region>',
  accessKeyId: '<Your accessKeyId>',
  accessKeySecret: '<Your accessKeySecret>',
  bucket: '<Your bucket name>'
});
async function uploadDir(dirPath) {
  const files = await fs.promises.readdir(dirPath);

  for (const file of files) {
    const filePath = path.join(dirPath, file);
    const stats = await fs.promises.stat(filePath);
    if (stats.isDirectory()) {
      await uploadDir(filePath);
    } else {
      const line = `${pkg.name}/${time}${filePath.replace(
        path.join(__dirname, "../dist"),
        ""
      )}`;
      console.log(line);

      const result = await client.put(line, filePath);
      console.log(`Uploaded ${filePath} to OSS as ${result.name}`);
    }
  }
}

uploadDir(path.join(__dirname, "../dist"));

做完这些就差菜单显示了

4、如何整理每个菜单项目?

 <template>
  <div>
    <a-tabs default-active-key="1" @change="callback">
      <a-tab-pane key="1" tab="Tab 1"> </a-tab-pane>
      <a-tab-pane key="2" tab="Tab 2"> </a-tab-pane>
    </a-tabs>
    <div id="subapp-viewport"></div>
  </div>
</template>

<script>
var microApp = null;
import { loadMicroApp } from "qiankun";
const obj = {
  1: {
    name: "demo",
    url: "https://xxx", //oss url
  },
  2: {
    name: "demo1",
    url: "https://xxx",//oss url
  },
};
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {};
  },
  methods: {
    callback(key) {
      microApp.unmount();
      microApp = null;
      setTimeout(() => {
        microApp = loadMicroApp({
          name: obj[key].name,
          entry: obj[key].url,
          container: "#subapp-viewport",
          props: { brand: "qiankun" },
        });
      });
    },
  },
  mounted() {
    // this.listenRouterChange();
    microApp = loadMicroApp({
      name: "demo",
      entry:
        "https://xxxxx",
      container: "#subapp-viewport",
      props: { brand: "qiankun" },
    });
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

以上实现最终效果是

image.png

image.png

以上是我对qiankun实现的微前端简单方案,目前发现的弊端就是无法使用路由。