qiankun.js配置流程及使用过程中遇到的问题分享

806 阅读4分钟

# 为什么使用qiankun框架?

我们项目使用的初衷是为了解决一个后台项目的重构问题,因为项目太过庞大,vue2升级vue3选择了重构,希望可以两个项目并行操作,这也就是我们使用qiankun的理由吧。我想很大原因还是iframe有很大的局限性,导致很多人选择qiankun这种微前端框架吧。

主项目配置

这是我自己根据qiankun框架重新搭建了一个项目,公司的项目不适合放到这里。主项目和子项目都是根据react和webpack进行构建的
import {} from "react-router-dom";
import {Outlet} from "react-router-dom";
import LeftMenu from './components/layout/LeftMenu';
import { Layout } from 'antd';
import { registerMicroApps, start } from 'qiankun';
import "./index.less";
const { Header, Footer, Sider, Content } = Layout;
export default () => {
    // qiankun框架初始化
  const initQiankunHandler = () => {
    registerMicroApps([
      {
        name: "op-first", // 名字
        entry: "//localhost:3000/", // 需要加载的本地地址,如果放到线上这块需要修改为子项目线上地址
        container: "#firstApp", // 子项目的容器,主项目这个id一定要定义好,不然子项目会没有容器而不显示
        activeRule: "/firstApp" // 这是子项目的路由,访问这种路由开头的时候会访问子项目
        /**
        activeRule: (location: Location) => {
              这里也可以放回一个布尔值告诉qiankun这是主项目路由还是子项目路由
            },
        */ 
      }
    ])
    start()
  }
  React.useEffect(() => {
    initQiankunHandler()
  }, [])
  return (
    <>
    <Layout>
        <LeftMenu />
      <Layout>
        <Header>Header</Header>
        <Content>
          <Outlet />
          <div id='firstApp'></div>
        </Content>
        <Footer>Footer</Footer>
      </Layout>
    </Layout>
    </>
  );
}

子项目配置

webpack配置
const path = require('path');
const HtmlWebapckPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: {
    main: './src/index.js'
  },
  output: {
    // asyncChunks: true,
    // filename: '[name].[contenthash].bundle.js',
    // path: path.resolve(__dirname, 'dist'),
    // 这里也要有这三个配置项
    library: `test-[name]`,
    libraryTarget: 'umd',
    // webpack5将qiankun官网上的jsonpFunction改为了chunkLoadingGlobal
    chunkLoadingGlobal: `webpackJsonp_test`
  },
  mode: 'development',
  devtool: 'source-map',
  devServer: {
    static: {
      directory: path.join(__dirname, 'public'),
    },
    compress: true,
    port: 3000,
    historyApiFallback: true,
    hot: true,
    // 这里一定要允许跨域,不然会导致主项目加载子项目过程中产生跨域问题,加载不进去
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    // proxy: {
    //   '/api': {
    //     target: '',
    //     pathRewrite: { '^/api': '' },
    //     changeOrigin: true
    //   }
    // }
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'scss-loader']
      },
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.tsx?$/,
        use: ['babel-loader','ts-loader'],
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebapckPlugin({
      title: 'react-project',
      template: './public/index.html'
    })
  ]
}
项目根目录下添加一个public-path.js 文件 添加下边这行代码
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}



然后在项目初始化文件中抛出qiankun的生命周期函数

import ReactDom from 'react-dom';
import RenderRouters from './routers/index.tsx';
import '../public-path';

// ReactDom.render(RenderRouters(), document.getElementById('App'));

function render(props) {
  const { container } = props;
  console.log(ReactDom, container, '>><><<aaa')
  ReactDom.render(RenderRouters(), container ? container.querySelector('#firstApp') : document.querySelector('#firstApp'));
}

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

export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}

export async function mount(props) {
  console.log('[react16] props from main framework', props);
  render(props);
}

export async function unmount(props) {
  const { container } = props;
  ReactDom.unmountComponentAtNode(container ? container.querySelector('#firstApp') : document.querySelector('#firstApp'));
}

这样子项目的配置也就好了

线上发布

线上发布的话可以在qiankun加载子项目的entry根据env配置不同的路径,我们是主项目和子项目部署到一个服务器上,通过ng配置不同的location进行区分,主项目不配置location这样取得就是ng上默认访问的html资源路径,然后在配置一个serve配置location这样的话访问这个服务器的域名再加一个location就能访问到子项目资源了。

// 主项目
server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
}
子项目
server {
 ...
 location /child {
            root   html;
            index  index.html index.htm;
  }
}

这样配置的话子项目资源访问就会有一点的问题,因为子项目的资源不能直接通过域名进行访问了,需要域名/child进行访问,这样的话子项目打包的时候需要在,这是以webpack打包示例,output这个属性下,添加一个publicPath: /child属性这样的话,子项目加载资源的时候会默认添加一个/child,这样就可以调通了,剩下的就可以交给运维了。

iconfont样式问题

如果主子项目icon的前缀名一样会造成icon混乱问题,我的解决思路是修改fontClss的前缀

image.png

主子项目可以进行命名区分,这样就可以避免icon混乱问题。

[class^="op-main-"], [class*=" op-main-"] {
  /* use !important to prevent issues with browser extensions that change fonts */
  font-family: 'op-main-iconfont' !important;
  font-style: normal;
  font-weight: normal;
  font-variant: normal;
  text-transform: none;
  line-height: 1;

  /* Better Font Rendering =========== */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

主子项目UI框架的样式隔离问题

这个下篇文章再说下吧,解决思路的话是通过babel来进行修改子项目中UI框架的class名来进行的样式隔离。

项目gitee地址:gitee.com/xuxuqingson…