qiankun框架初探

# qiankun

**微前端架构图**

## qiankun 实现原理

`qiankun`是基于`single-spa`开发,它主要采用`HTML Entry`模式,直接将子应用打出来 HTML作为入口,通过 fetch html 的方式,解析子应用的html文件,然后获取子应用的静态资源,同时将 HTML document 作为子节点塞到主框架的容器中。

应用切出/卸载后,同时卸载掉其样式表即可,浏览器会对所有的样式表的插入、移除做整个 CSSOM 的重构,从而达到 插入、卸载 样式的目的。这样即能保证,在一个时间点里,只有一个应用的样式表是生效的。

`HTML Entry` 方案则天生具备样式隔离的特性,因为应用卸载后会直接移除去 HTML 结构,从而自动移除了其样式表。

子应用挂载时,会自动做一些特殊处理,可以确保子应用所有的资源dom(包括js添加的style标签等)都集中在子应用根节点dom下。子应用卸载时,对应的整个dom都移除了,这样也就避免了样式冲突。

提供了js沙箱,子应用挂载时,会对全局window对象代理、对全局事件监听进行劫持等,确保微应用之间 全局变量/事件 不冲突。

HTML Entry` 是由 `import-html-entry` 库实现的,通过 `http` 请求加载指定地址的首屏内容即 `html` 页面,然后解析这个 `html` 模版得到 `template`, `scripts` , `entry`, `styles,返回一个promise对象### 改造流程

具体而言,import-html-entry 实现了以下功能:

加载 HTML 入口文件:import-html-entry 会通过创建一个 <link> 标签来加载子应用的 HTML 入口文件。这样可以确保子应用的资源得到正确加载,并在加载完成后进行处理。
解析 HTML 入口文件:一旦 HTML 入口文件加载完成,import-html-entry 将解析该文件的内容,提取出子应用的 JavaScript 和 CSS 资源的 URL。
动态加载 JavaScript 和 CSS 资源:import-html-entry 使用动态创建 <script> 和 <link> 标签的方式,按照正确的顺序加载子应用的 JavaScript 和 CSS 资源。
创建沙箱环境:在加载子应用的 JavaScript 资源时,import-html-entry 会创建一个沙箱环境(sandbox),用于隔离子应用的全局变量和运行环境,防止子应用之间的冲突和污染。
返回子应用的入口模块:最后,import-html-entry 返回一个可以加载子应用的 JavaScript 模块。这个模块通常是一个包含子应用初始化代码的函数,可以在主应用中调用以加载和启动子应用。
通过使用 qiankun import-html-entry,开发者可以方便地将子应用的 HTML 入口文件作为模块加载,并获得一个可以加载和启动子应用的函数,简化了子应用的加载和集成过程。

## 改造流程

(主应用为vue2.X(history路由模式),子应用为react18(hash路由模式))

#### 1、主应用 

安装qiankun : npm i -S qainkun

将app.js和index.js文件放到qiankun文件夹(管理qiankun相关配置)里复制到项目中

```js
// app.js
import store from '@/store'
const apps = [
  {
    name: 'micro-app-react', // 导入的子应用名称,需要和子应用的package.json中的name保持一直 
    entry: '//localhost:8012',// 子应用的访问地址
    container: '#micro-app-react',// 子应用在主应用的挂在地址(id)
    activeRule: '/micro-app-react/react',
    props: { // 需要传递给子应用的数据
      store,
      msg: '我是主应用'
    }
  }
]

export default apps
```

```js
import { registerMicroApps, start, addGlobalUncaughtErrorHandler, initGlobalState } from 'qiankun/es'
import apps from './app'

// 微应用生命周期
const microAppLifCycles = {
  beforeLoad: [ // 全局的微应用生命周期钩子,子应用加载前
    app => {
      console.log('子应用加载-beforeLoad', app)
      return Promise.resolve()
    }
  ],
  beforeMount: [ // 全局的微应用生命周期钩子,子应用挂载前
    app => {
      console.log('2-beforeMount', app)
      return Promise.resolve()
    }
  ],
  afterMount: [ // 全局的微应用生命周期钩子,子应用挂载后
    app => {
      console.log('3-afterMount', app)
      return Promise.resolve()
    }
  ],
  beforeUnmount: [
    app => {
      console.log('4-beforeUnmount', app)
    }
  ]
}

console.log(registerMicroApps, 'registerMicroApps')
registerMicroApps(apps, microAppLifCycles)

// 添加全局的未捕获异常处理器
addGlobalUncaughtErrorHandler((event) => {
  const { message: msg } = event
  if (msg && msg.includes('died in status LOADING_SOURCE_CODE')) {
    console.error('微应用加载失败,请检查应用是否可运行', event)
  }
})

// 定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法
const { onGlobalStateChange, setGlobalState } = initGlobalState({
  user: 'qiankun'
})

// 在当前应用监听全局状态,有变更触发 callback
onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - main]:', value + prev))

// 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
setGlobalState({
  ignore: 'master',
  user: {
    name: 'master'
  }
})

export default start
```

配置vue.config.js

```js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    client: {
      overlay: false // 禁止:当出现编译错误或警告时,在浏览器中显示全屏覆盖
    }
  }
})
```

创建子应用占位组件

```vue
 // page/appReact/index.vue
<template>
  <div id="micro-app-react"></div>
</template>

<script>
import { start } from 'qiankun/es'
export default {
  name: 'microAppReact',
  components: {},
  mounted () {
    console.log('window.qiankunStarted', window.qiankunStarted)
    if (!window.qiankunStarted) {
      window.qiankunStarted = true
      start({
        prefetch: true, // 是否开启预加载
        sandbox: {
          experimentalStyleIsolation: true // 实验性的样式隔离
        }
      })
    }
  }
}
</script>
```

router.js 中配置字应用对应的路由

```js
 {
          name: 'microAppReact',
          path: '/micro-app-react/*',  // 匹配micro-app-react后的所有路由
          title: 'micro-app-react',
          icon: 'icon-instrum',
          component: () => import('@/page/appReact/index.vue')
        },
```

在main.js中启动qiankun

```
import start from '@/qiankun/index'

start({
  prefetch: true, // 是否开启预加载
  sandbox: {
    experimentalStyleIsolation: true // 实验性的样式隔离
 }
})
```

上述配置处理完毕就可以在通过改变路由进而访问子应用的页面了

```vue
          // this.$router.push({ path: '/micro-app-react/react#/' })
          window.history.pushState(null, '', '/micro-app-react/react#/')
          // this.$router.push({ path: '/micro-app-react/react#/demo' })
          window.history.pushState(null, '', '/micro-app-react/react#/demo')
          window.history.pushState(null, '', '/micro-app-react/react#/demo1')
          // this.$router.push({ path: '/micro-app-react/react#/demo1' })
          break
```

eg:由于子应用的接口访问是通过主应用发起的,所以子应用的代理配置需要在住应用中同步至proxy转发代理中。

#### 2、子应用

**子应用无需安装qiankun** 

1、修改package.json 的name为micro-app-react,和主应用需要的name保持一致。

2、处理webpack的打包输出,并且设置 "Access-Control-Allow-Origin": "*"来处理跨域问题(目前还未解决qiankun和webpack的模块联邦共存问题,只能二选一)

```js
const packageName = require('./package.json').name;

webpack: {
      config.output = {
        ...config.output,
         library : packageName,
    libraryTarget : "umd",
    globalObject : "window",
      };
      return config;
    },
  },
  devServer: {
    headers : {
      "Access-Control-Allow-Origin": "*",
    },
  }
```

3、**由于qiankun加载主应用时需要在入口文件的index.js就处理single-spa,所以现有项目的入口文件先加载bootstrap需改造成直接处理**

```tsx
/* eslint-disable @typescript-eslint/no-empty-function */
import './public-path.js'; // 首先加载
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.less';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { HashRouter } from 'react-router-dom';
import { NPLProvider } from '@LU30/NPLWeblib';
import { getBrowserVersion, loadStyles } from './utils/VersionCompatible';
import AuthorityPrompt from './pages/AuthorityPrompt/index';
import ErrorBoundary from './utils/CmbTrack';

// 如果是360或者版本低于90都做样式兼容i
if (getBrowserVersion() < 90) {
  // 加载ant4最新版样式,兼容低版本浏览器
  // loadStyles('/antd.css');
  // 覆盖局部未兼容样式
  loadStyles('/compayible.css');
}

console.log('window.__POWERED_BY_QIANKUN__', window.__POWERED_BY_QIANKUN__);

let myRoot = null;

function render(props) {
  const { container } = props;
  const dom = container ? container.querySelector('#root') : document.querySelector('#root');
  myRoot = ReactDOM.createRoot(dom as HTMLElement);
    myRoot.render(
    <NPLProvider>
      {getBrowserVersion() < 86 && getBrowserVersion() !== 0 && <AuthorityPrompt />}
      {(getBrowserVersion() >= 86 || getBrowserVersion() === 0) && (
        <HashRouter>
          <ErrorBoundary>
            <App />
          </ErrorBoundary>
        </HashRouter>
      )}
    </NPLProvider>,
  );
}
 // qiankun生命周期钩子 ,必须抛出
export async function bootstrap() {
  console.log('bootstrap');
}
export async function mount(props: any) {
  console.log('激活');
  // 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
  console.log('mount', props); // props是主应用传递过来的值
  render(props);
}
export async function unmount(props: any) {
  console.log('unmount', props);
  // const { container } = props;
  // const dom = container ? container.querySelector('#root') : document.querySelector('#root');
  // const root = ReactDOM.createRoot(dom as HTMLElement);
  // 应用每次 切出/卸载 会调用的unmount方法,通常在这里我们会卸载微应用的应用实例
  myRoot.unmount();
}

// 如果不是qiankun就执行,以支持独立开发
if (!window.__POWERED_BY_QIANKUN__) {
  const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
  root.render(
    <NPLProvider>
      {getBrowserVersion() < 86 && getBrowserVersion() !== 0 && <AuthorityPrompt />}
      {(getBrowserVersion() >= 86 || getBrowserVersion() === 0) && (
        <HashRouter>
          <ErrorBoundary>
            <App />
                </ErrorBoundary>
        </HashRouter>
      )}
    </NPLProvider>,
  );
}

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

// const bootstrap = import('./bootstrap');

// export default bootstrap;
```

public-path.js

```js
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
```

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
qiankun框架可以通过将子应用的vuex实例作为主应用vuex实例的一个模块来实现共用。具体步骤如下: 1. 在主应用的vuex实例中,使用`registerModule`方法注册子应用的vuex实例为一个模块,可以指定模块名称和命名空间。 ```javascript import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start } from 'qiankun'; import store from './store'; registerMicroApps([ { name: 'sub-app', entry: '//localhost:8080', container: '#subapp-container', activeRule: '/sub-app', }, ]); store.registerModule('subAppStore', { state: {}, mutations: {}, actions: {}, getters: {}, }); ``` 2. 在子应用的vuex实例中,导出vuex实例。 ```javascript import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, getters: {}, }); ``` 3. 在子应用的入口文件中,将子应用的vuex实例作为参数传递给子应用的渲染函数。 ```javascript import Vue from 'vue'; import App from './App.vue'; import store from './store'; Vue.config.productionTip = false; function render() { new Vue({ store, render: h => h(App), }).$mount('#app'); } if (window.__POWERED_BY_QIANKUN__) { // 动态添加publicPath,使子应用可以正确加载静态资源 __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } else { render(); } ``` 这样,主应用和子应用之间就可以共用一个vuex实例了。在主应用中使用`store.state.subAppStore`访问子应用的vuex状态,在子应用中使用`this.$store`访问自己的vuex状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值