qiankun 的使用

qiankun 的使用

1.前言

许多项目搭建的方法不一致,故qiankun改造的方式也不一致,但是从目前的情况看,基本的步骤和些思路还是大抵相同,在改造中遇到的最多问题就是 路径不一致、配置的代理不正确、跨域等,后面有问题也优先往这方面靠近。

参考文档:

乾坤官网:https://qiankun.umijs.org/zh

vite vue3配置:https://www.cnblogs.com/Mr-Rshare/p/16571760.html

angular 配置:https://www.cnblogs.com/wangyongping/p/16788537.html

2.主应用

vue2 (公司平台)

这里的配置完全可以参考乾坤官网上来配置,这里我用公司平台举例:

如果子应用存在angular那么首先要安装 zone.js,不用的话就不用安装。

npm i zong.js
npm i qiankun

修改路由 ,在路由表的根部添加要配置的子应用路径:如flink,后面的*号一定要有。

# src/common/router/index.js

export const constantRouterMap = [
  { path: '/license', component: LicenseComponent, hidden: true },
  +{
   + path: '/flink/*',
   + component: componentsObj.Layout
  + },
  { path: '*', component: NotFoundComponent, hidden: true },
  // ...
];
export default new Router({
  mode: 'history',
  routes: constantRouterMap
});

在要渲染子应用的地方添加id容器

# src/common/views/layout/components/AppMain.vue
<template>
  <section class="app-main" v-bind:style="{ height: screenHeight }">
    <div class="app-main-container">
      // ...
      <router-view  />
      + <div id="appContainer"></div>
      //  ...
    </div>
  </section>
</template>

设置qiankun的启动

# src/common/views/layout/Layout.vue

+ import { start } from 'qiankun';

export default {
  mounted() {
    + start();
  },
  methods: {
  }
};
</script>

设置qiankun的基础配置,如果有anglue项目需要引入zone.js

# src/common/config/sys.js

import 'zone.js';  // 如果有anglue项目,一定要在qiankun的前面引入

import {
  registerMicroApps,
} from 'qiankun';

...

registerMicroApps(
  [
    {
      name: 'flink_web',
      entry: '//localhost:4200',
      container: '#appContainer',
      activeRule: '/flink'
    },
    // {
    //   name: 'datainsight',
    //   entry: '//localhost:5002',
    //   container: '#appContainer',
    //   activeRule: '/datainsight'
    // }
  ],
  {
    beforeLoad: [
      app => {
        // console.log('before load', app);
      }
    ],
    beforeMount: [
      app => {
        // console.log('before mount', app);
      }
    ],
    afterUnmount: [
      app => {
        // console.log('after unload', app);
      }
    ]
  }
  // {
  //   fetch: request
  // }
);
...

配置代理 ,这里路径一定要正确,后面报错的话不好定位到这里

# setting.js

'/api/flinkdashbaord/*': {
      target: 'http://flink.com/api/flinkdashbaord', // 本地测试环境
      logLevel: 'debug',
      changeOrigin: true,
      autoRewrite: true,
      cookieDomainRewrite: true,
      pathRewrite: {
        '^/api/flinkdashbaord': ''
      }
 },

3.微应用

vue3 + vite

因为官网里面没有提供方法,且不支持vite, 所以需要在引入一个包,在子应用

npm i vite-plugin-qiankun

在改造后发现路由不匹配的问题,后经过测试发现是history没有在离开时销毁造成,所以要 history.destroy();

修改路由,这里简写,并把路由返回的方式

# src/router/index.ts

import { createRouter } from 'vue-router'
import routes from './routes'

...

let routerIn:any=null

function setRouter(history: any) {
  routerIn = createRouter({
    history,
    routes
  })
  ...
  return routerIn
}

export  {setRouter, routerIn as router}

修改主配置,引入插件 vite-plugin-qiankun

# src/main.ts
 
import {createApp} from 'vue'
import App from './App'
import {QiankunProps, qiankunWindow, renderWithQiankun} from 'vite-plugin-qiankun/dist/helper'
import {setRouter} from "@/router";
import {createWebHistory} from "vue-router";

let app:any = null;
let routerIn: any = null
let historyIn: any = null

function render(props:any) {
    historyIn = createWebHistory(
        qiankunWindow.__POWERED_BY_QIANKUN__? '/dolphinscheduler/':(import.meta.env.MODE === 'production' ? '/dolphinscheduler/' : '/')
    )
    routerIn = setRouter(historyIn)
    const { container } = props;
    app = createApp(App);
    app.use(routerIn);
    app.mount(container ? container.querySelector('#dolphinschedulerApp'): '#dolphinschedulerApp');
}

renderWithQiankun({
    mount(_props) {
        render(_props);
    },
    bootstrap() {
        console.log('bootstrap');
    },
    unmount(_props: any) {
        app.unmount();
        app = null;
        routerIn= null;
        historyIn.destroy();
    },
    update: function (props: QiankunProps): void | Promise<void> {
        console.log('update');
    }
});

if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
    render({container:''});
}



修改 vite配置 ,因为vite 不支持qiankun 使用插件后,打包时候要提前配置好 base

# vite.config.ts
 
import {defineConfig,  } from 'vite'
+ import qiankun from 'vite-plugin-qiankun'

+ const { name } = require('./package.json');

export default defineConfig({
  base: 'http://dolphinscheduler.com/',
  // base: process.env.NODE_ENV === 'production' ? '/dolphinscheduler/' : '/',
  plugins: [
    + qiankun(name, {
     +  useDevMode: true,
    +  }),
    ...
  ],
  ...
  server: {
    + headers: {
      + 'Access-Control-Allow-Origin': '*',
    + },
    proxy: {
      '^/api/inner/dolphinscheduler': {
        target: loadEnv('development', './').VITE_APP_DEV_WEB_URL,
        changeOrigin: true
      }
    }
  }
})

angular12 + single-spa

angular整体结构有所不同,这里的项目有用到 single-spa 才可以,执行命令

npm i single-spa -S
ng add single-spa-angular

执行完后,项目的一部分路径文件也会有所变化,创建 public-path.js 文件,在 main.single-spa.ts 内最上面引入

# public-path.js 

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

修改入口文件

# main.single-spa.ts

import './public-path';
import { enableProdMode, NgZone } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Router } from '@angular/router';

import { singleSpaAngular, getSingleSpaExtraProviders } from 'single-spa-angular';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { singleSpaPropsSubject } from './single-spa/single-spa-props';

if (environment.production) {
  enableProdMode();
}
// enableProdMode();
// 微应用单独启动时运行
if (!(window as any).__POWERED_BY_QIANKUN__) {
  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.error(err));
}

const lifecycles = singleSpaAngular({
  bootstrapFunction: singleSpaProps => {
    singleSpaPropsSubject.next(singleSpaProps);
    return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule);
  },
  template: '<flink-root id="flinkid" />',
  Router,
  NgZone: NgZone
});

export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;

控制zone.js 的引入,确保本地和qiankun都能引入

# index.html

head标签添加

<script src="https://unpkg.com/zone.js" ignore></script>
# polyfills.ts

// import 'zone.js  把这里引入注释掉

修改路由的配置

# app-routing.module.ts

+ import { APP_BASE_HREF } from '@angular/common';

@NgModule({
  exports: [RouterModule],
  imports: [RouterModule.forRoot(routes)],
  + providers: [{ provide: APP_BASE_HREF, useValue: (window as any).__POWERED_BY_QIANKUN__ ? '/flink/' : '/' }]
})

修改angular配置文件

# extra-webpack.config.js

const singleSpaAngularWebpack = require('single-spa-angular/lib/webpack').default;
const {merge } = require("webpack-merge");
const appName = require('./package.json').name;

module.exports = (config, options) => {
  const singleSpaWebpackConfig = singleSpaAngularWebpack(config, options);

  const singleSpaConfig = {
    devServer: {
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
    },
    output: {
      library: `${appName}-[name]`,
      libraryTarget: "umd",
    },
    externals: {
      "zone.js": "Zone",
    },
  };
  const mergedConfig = merge (
    singleSpaWebpackConfig,
    singleSpaConfig
  );
  return mergedConfig;

};

如果还有其它报错,基本上是路径匹配,端口NaN等造成

react16.8

一部分的引入的方式可参考官网,可能项目不同,webpack的引入方式不正确,需要单独处理。

修改打包配置文件

# internals/webpack/webpack.prod.babel.js

module.exports = require('./webpack.base.babel')({
  mode: 'production',
  output: {
    // filename: '[name].[chunkhash].js',
    // chunkFilename: '[name].[chunkhash].chunk.js'
   + library: `datainsight`,
   +  filename: `[name].${timeStamp}.js`,
    + libraryTarget: 'umd',
     jsonpFunction: `webpackJsonp_${name}`
  },
  ...
})

修改引入

# app/app.tsx

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
import React from 'react'
import ReactDOM from 'react-dom'
import { translationMessages } from './i18n'
...

const MOUNT_NODE = document.getElementById('datainsightApp')

const render = (messages, props) => {
  const { container } = props
  ReactDOM.render(
    <Provider store={store}>
      <LanguageProvider messages={messages}>
        <ConfigProvider locale={zh_CN}>
          <ConnectedRouter history={history}>
            <App />
          </ConnectedRouter>
        </ConfigProvider>
      </LanguageProvider>
    </Provider>,
    container ? container.querySelector('#datainsightApp') : MOUNT_NODE
  )
}

if (!window.__POWERED_BY_QIANKUN__) {
  if (module.hot) {
    module.hot.accept(['./i18n', 'containers/App'], () => {
      ReactDOM.unmountComponentAtNode(MOUNT_NODE)
      render(translationMessages, {})
    })
  } else {
    render(translationMessages, {})
  }
}

export async function bootstrap() {
  console.log('react app bootstraped')
}

export async function mount(props) {
  await render(translationMessages, props)
}

export async function unmount(props) {
  const { container } = props;
  await ReactDOM.unmountComponentAtNode(container ? container.querySelector('#datainsightApp') : MOUNT_NODE)
}

if (process.env.NODE_ENV === 'production') {
  // disable react developer tools in production
  if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
    window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = () => void 0
  }
}

修改路由

# app/containers/App/index.tsx

...

  public render() {

  
    const __qiankun__ = window.__POWERED_BY_QIANKUN__

    return (
      <div>
        {/*basename={'/datainsight'}  basename={__qiankun__ ? '/datainsight' : '/'}*/}
        <Router  basename={__qiankun__ ? '/datainsight/' : '/'}>
          <Switch>
            <Route path="/activate" component={Activate} />
            <Route path="/joinOrganization" exact component={Background} />
            <Route path="/findPassword" component={FindPassword} />
            <Route path="/" exact render={this.renderRoute} />
            <Route path="/" component={logged ? Main : Background} />
          </Switch>
        </Router>
      </div>
    )
  }
...

vue2

这里不做详细介绍,参考官网

https://qiankun.umijs.org/zh/guide/tutorial#vue-%E5%BE%AE%E5%BA%94%E7%94%A8

4.nginx配置

qiankun 官网:

https://qiankun.umijs.org/zh/cookbook#%E5%9C%BA%E6%99%AF-2%E4%B8%BB%E5%BA%94%E7%94%A8%E5%92%8C%E5%BE%AE%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2%E5%9C%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BD%BF%E7%94%A8-nginx-%E4%BB%A3%E7%90%86%E8%AE%BF%E9%97%AE

通用方式

线上采用同一个服务器,不同的端口实现

server {
        listen       86;
        server_name  localhost;

        location / {
            root   /aTinyApp/appbase;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
        location /applims {
            proxy_pass http://localhost:88/;
            proxy_set_header Host $host:$server_port;
        }
        location /appcost {
            proxy_pass http://localhost:89/;
            proxy_set_header Host $host:$server_port;
        }
        location /api/ {
            proxy_pass http://gateway/;
            proxy_set_header X-Real-IP  $remote_addr;
            proxy_read_timeout 300;
            client_max_body_size 1024m;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen  88;
        server_name  localhost;

        location / {
            root   /aTinyApp/applims;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
        location /api/ {
            proxy_pass http://gateway/;
            proxy_set_header X-Real-IP  $remote_addr;
            proxy_read_timeout 300;
            client_max_body_size 1024m;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    server {
        listen  89;
        server_name  localhost;

        location / {
            root   /aTinyApp/appcost;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
        location /api/ {
            proxy_pass http://gateway/;
            proxy_set_header X-Real-IP  $remote_addr;
            proxy_read_timeout 300;
            client_max_body_size 1024m;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

本地需要考虑到跨域等因素,做相应处理

server {
        listen       80;
        server_name  flink.com;

        #access_log  logs/host.access.log  main;

        location / {
            root D:/baseServer/app/flink;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
            # 允许 所有头部 所有方法 域 
            add_header 'Access-Control-Allow-Headers' '*';
            add_header 'Access-Control-Allow-Methods' '*';
            add_header 'Access-Control-Allow-Origin' "$http_origin";
            add_header 'Access-Control-Allow-Credentials' 'true';
        }
        
        location /api/ {
		    proxy_pass http://172.18.50.187;
	    }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
       
    }
    

同一域名下部署

多数也是参照乾坤的例子

主应用配置

egisterMicroApps(
  [
    {
      name: 'flink-dashboard',
      entry: '/child/flinkdashboard/',    // 重点
      container: '#appContainer',
      activeRule: '/flinkdashboard'      // 重点
    },
    {
      name: 'dolphinscheduler',
      entry: '/child/dolphinscheduler/',  // 重点
      container: '#appContainer',
      activeRule: '/dolphinscheduler'     // 重点
    },
  ],

子应用history配置

historyIn = createWebHistory(
          qiankunWindow.__POWERED_BY_QIANKUN__? '/dolphinscheduler/':'/child/dolphinscheduler/'
)

子应用跟路径 配置

base:'/child/dolphinscheduler/'

服务器目录

└── app/                     # 根文件夹
    |
    ├── child/                # 存放所有微应用的文件夹
    |   ├── flinkdashboard/         // flinkdashboard
    |   ├── dolphinscheduler/       // dolphinscheduler
    ├── index.html            # 主应用的index.html
    ├── css/                  # 主应用的css文件夹
    ├── js/                   # 主应用的js文件夹

nginx配置

server {
        listen       80;
        server_name  app.com;

        #access_log  logs/host.access.log  main;

        location / {
            root D:/baseServer/app;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
        location /child/flinkdashboard {
            root   D:/baseServer/app;
            index  index.html index.htm;
            try_files $uri $uri/ /child/flinkdashboard/index.html;
        }
        location /child/dolphinscheduler {
            root   D:/baseServer/app;
            index  index.html index.htm;
            try_files $uri $uri/ /child/dolphinscheduler/index.html;
        }

        
        location /api/ {
		    proxy_pass http://172.18.50.187;
	    }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
       
    }
    
     

配置成功后,主应用访问地址不变,单独访问子应用只需要加上 http://xxx.com/child/zi/ 即可。

5.常见问题

多数问题qiankun已经整合,可以从中作为参考:

https://qiankun.umijs.org/zh/faq

a. 如果安装了zone.js 在项目运行中,可能有不相关问题也会发出报错提示,不是angular项目时候可以先关掉引用,方便快速定位问题

b. 项目部署后,vite项目与其他项目切换中,发现路径回退,可能是vite项目的history没有销毁,在乾坤生命周期中销毁。

c. angular项目在执行完 ng add single-spa-angular 后,可能会遇到,端口变成NaN问题,路径不匹配的问题

// 报路径错的时候修改这里
# tsconfig.app.json

"files":[
"main.single-spa.ts"
]
// 端口要一致
# angular.json

"projects"->"flink"->"architect"->"build"->"options"->"deployUrl":'/'
"projects"->"flink"->"architect"->"serve"->"options"->"deployUrl":'http://localhost:4200/'

d. 任何项目部署后运行,刷新后子应用变成主应用消失,可以先检查主应用的entry和activeRule是不是配置成一样的了

e. angular项目有一个跟路径:

<base href="/" />

当出现白屏,路径不匹配,配置了好几种方案都不成功的时候,可以尝试将其注释掉。

f. 目前在vite 和vue3的项目上遇到一个问题,配置都正确,但是访问二级微应用的子路由的时候,将整个页面都渲染成白屏,后改路径等匹配规则都不成功,最后使用的hash 路由模式得以解决,如果要继续使用history 模式,可以尝试从子应用上检查,现在已经定位到是子应用路由渲染上有影响,具体原因还在查找。

。。。

后续待补充

posted @ 2023-03-08 15:52  星涑  阅读(553)  评论(0编辑  收藏  举报