官网:qiankun - qiankun

 

 1  简介

        1.简单:任意js框架都可以使用;

        2.完备:样式隔离,js沙箱,预加载等;

        3.生成可用;

2  案例

1.创建4个应用,分别是:base项目(可以是任何框架搭建的),微前端的主应用,react创建的;my-react,一个子应用;my-vue,vue3创建的一个子应用;vue-lib,vue2创建的一个组件库。vue-lib是一个公共库,需要在react-project和my-vue中使用。

2.在base项目的App.js中,导入路由,可以实现my-react和my-vue两个子应用的却换;

3.在base项目中的src下新建一个resgisterApp.js的文件,用于注册子应用。用qiankun来注册。然后在main.ts中导入该文件。

import cons from "consolidate";
import { registerMicroApps, start } from "qiankun";
// qiankun底层是基于single-spa实现的

// 给m-vue应用传一个loader
const loader = (loading) => {
  console.log(loading);
};

/**
 * 注册应用  registerMicroApps(apps,lifeCycles?)
 * apps Array<RegistrableApp> , 必填,微应用的一些注册信息
 * lifeCycles 可选,全局的微应用生命周期钩子
 */
registerMicroApps(
  [
    {
      name: "m-vue", // 注册应用名
      entry: "//localhost:20000", // 访问资源的入口
      container: "#container", // 挂载容器
      activeRule: "/vue", // 激活条件,当选中/vue路由时才加载当前应用
      loader, //  加载一个loading
    },
    {
      name: "m-react",
      entry: "//localhost:30000",
      container: "#container",
      activeRule: "/react",
      loader,
    },
  ],
  {
    beforeLoad: (app) => {
      console.log("加载前");
    },
    beforeMount: (app) => {
      console.log("挂载前");
    },
    afterMount: (app) => {
      console.log("挂载后");
    },
    beforeUnmount: (app) => {
      console.log("销毁前");
    },
    afterUnmount: (app) => {
      console.log("销毁后");
    },
  }
);

// 开始预加载所有微应用静态资源
start();

4.进入my-vue项目,改造一下main.js;需要暴露接入协议,修改一下配置文件。

在vue.config.js文件中,具体代码如下:

module.exports = {
  publicPath: "//localhost:20000", // 保证子应用静态资源都是像20000端口上发送的
  devServer: {
    port: 20000,
    headers:{
        "Access-Control-Allow-Origin": "*",
    }
  },
  /**
   * 别人怎样访问到?
   * 别人会用到fetch请求资源
   * fetch默认不允许跨域
   */
  configureWebpack: {
    // 需要获取我打包的内容,打包输出格式为umd格式
    output:{
        libraryTarget: "umd", // 打包格式
        library: "m-vue", // 挂载名
    }
  },
};

// 3000  -->   20000基座会去找20000端口中的资源,publicPath /

5.修改my-vue中的main.js,直接在main.js后面追加如下代码:

// 不能直接挂载,需要切换的时候,调用mount方法时再去挂载
// createApp(App).use(router).mount("#app");
let history; // 每次退出时需要清空,所以用一个history记录
let router;
let app;
function render(props = {}) {
  // 每次挂载和卸载都需要产生一个全新的实例
  history = historyWebHistory("/vue"); // 传入主应用中的base地址
  //   创建路由
  router = createRouter({
    history,
    routes,
  });
  app = createApp(App);
  let { container } = props;
  app.use(router).mount(container ? container.querySelector("#app") : "#app");
}

// qiankun在渲染前给我们提供了一个变量window.__POWERED_BY_QIANKUN__
if (!window.__POWERED_BY_QIANKUN__) {
  // 用于独立运行
  render();
}

// 需要暴露接入协议
export async function bootstrap() {
  console.log("vue3  app 启动成功  bootstrap");
}
export async function mount(props) {
  console.log("vue3  app 启动成功  mount");
  //   props 传入需要挂载的容器过来,用于指定渲染时需要挂载的容器
  render(props);
}
export async function unmount() {
  console.log("vue3  app 启动成功  unmount");
  history = null; // 卸载时就清空history
  router = null;
  app = null;
}

6.修改route/index.js文件:

删除里面的const router=createRouter({ ....})

7.修改my-react项目,想要修改配置文件,需要先下载@rescript/cli插件,表示重写react本身的配置文件,在项目根目录下新建一个.rescriptsrc.js文件,具体代码如下:

module.exports = {
  webpack: (config) => {
    config.output.library = "m-react";
    // 为什么一定得用umd模式的,因为只有umd模式的才会把打包后的资源挂载在window上,挂载的属性名为library名
    config.output.libraryTarget = "umd";
    config.output.publicPath = "//localhost:30000/"; // 最后的/必须写,不写的话会找不到下面的资源
  },
  devServer: (config) => {
    config.headers = {
      "Access-Control-Allow-Origin": "*",
    };
    return config;
  },
};

8.在my-react项目中,根目录下新建一个.env文件,具体代码如下:

PORT = 30000;
WDS_SOCKET_PORT = 30000;

9.想要my-react中的自定义配置文件生效,则需要修改package.json文件中的sxripts属性中的值,将之前的react-scripts替换为自定义的插件名rescripts;

10.my-react项目中导出生命周期函数,进去src下面的index.js文件,添加如下代码:

function render(props = {}) {
  // 把之前的渲染挪在这个函数里面
  let { container } = props;
  ReactDom.render(
    <App />,
    container
      ? container.querySelector("#root")
      : document.getElementById("#root")
  );
}

if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {}
export async function mount(props) {
  render(props);
}
export async function unmount(props) {
  let { container } = props;
  // 卸载ReactDOM
  ReactDom.unmountComponentAtNode(
    container
      ? container.querySelector("#root")
      : document.getElementById("#root")
  );
}

11.样式隔离
默认情况下切换应用,它会采用动态样式表,加载的时候添加样式,删除的时候卸载样式。

主要是隔离主应用和子应用如何隔离:

        1.可以通过BEM规范来进行避免;

        2.css-modules每次加载模块都动态生成一个前缀,并不是完全隔离。在主应用的registerApps.js中修改:

// ...
start({
  // 添加沙箱
  sandbox: {
    experimentalStyleIsolation: true,
  },
});

        3.shadowDOM才是真正意义上的隔离,还有添加全局样式就会有问题:

// ...
start({
  // 严格样式隔离
  sandbox: {
    strictStyleIsolation: true,
  },
});

jeecg前端乾坤微服务 微前端 乾坤 实例_前端

 沙箱是使用案例代码如下:
 

<body>
    <div>hello world</div>
    <script>
      // 通过fatch拿到内容
      let appContent = `
            <div id="qiankun">
                <div>hellow aa</div>
                <style>div{color:red;}</style>
            </div>
        `;
      const containerElement = document.createElement("div");
      containerElement.innerHTML = appContent;
      const appElement = containerElement.firstChild;
      let html = appElement.innerHTML;
      appElement.innerHTML = "";
      let shadow = appElement.attachShadow({ mode: "closed" });
      shadow.innerHTML = html;
      document.body.appendChild(appElement);
    </script>
  </body>