微前端qiankun的实践总结

237 阅读2分钟

最近搭了一个qiankun的微前端项目,总结一下整个搭建过程; 我是基于webpack5.0, reactv18.2.0

  1. 微前端项目文件布局如下,qiankun-base是主应用,qiankun-react,qiankun-vue是子应用; image.png
  2. 主应用
npx create-react-app qiankun-main

我是用craco来配置webpack的,故需要引入@craco/craco和craco-less 所以package.json修改为:

"scripts": {
    "mock": "cross-env REACT_APP_MOCK=true craco start",
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
},

将缺的依赖补齐 qiankun、react-dom、babel-plugin-import等等 App.js 修改为

import React, { useEffect } from 'react';
import { HashRouter as Router, Link } from 'react-router-dom';
import { registerMicroApps, start, initGlobalState } from 'qiankun';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/es/locale/zh_CN';
import './App.less';

const App = () => {
  const apps = [{
    // 微应用名称,在微应用的打包配置文件种library的名称,微应用之间必须确保唯一
    name: 'react', 
    // 微应用地址,子应用必须支持跨域 fetch
    entry: '//localhost:8081/#/',
    // 微应用挂载的容器节点
    container: '#appContainer',
    // 微应用的激活规则,访问到react的时候跳转子应用
    activeRule: '#/react',
    // 主应用需要传递给微应用的数据
    props: { token: 'gaiery-token-xxx'}
  }]

  useEffect(() => {
    // 注册app
    registerMicroApps(apps);
    // 开启
    start();
  }, [])

  return (
  <ConfigProvider locale={zhCN}>
    <Router>
      <div>
        <div className='app-menu'>
          <Link to='/react'>react</Link>
          <Link to='/vue'>vue</Link>
        </div>
        <div id="appContainer"></div>
      </div>
    </Router>
  </ConfigProvider>
  );
};

export default App;

到此为止,主应用算配置基本配置完成

  1. react子应用的配置 先在src里新建一个public-path.js文件
if(window.__POWERED_BY_QIANKUN__) {
  // 动态设置 webpack publicPath,防止资源加载出错
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

index.js修改为如下

import './public-path';
import { createRoot } from 'react-dom/client';
import App from './App';

let root;

// 将render方法用函数定义,供后续主应用与独立运行调用
function render (props) {
  const { container } = props; console.log('container---', container);
  const dom = container ? container.querySelector('#appRoot') : document.getElementById('appRoot');
  root = createRoot(dom);
  root.render(
    <App />
  )
}

// 判断是否在qiankun环境下,非qiankun环境下独立运行
if(!(window).__POWERED_BY_QIANKUN__){
  render({});
}

// 各个生命周期
// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
export async function bootstrap() {
  console.log('react app bootstraped');
}

// 应用每次进入都会调 mount 方法,通常我们在这里触发应用的渲染方法
export async function mount(props) {
  console.log('mount----');
  render(props);
}

// 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
export async function unmount(props) {
  console.log('unmount----');
  root.unmount();
}

App.js 将HashRouter的basename改为basename={window.POWERED_BY_QIANKUN? '/react' : '/'},如下

import React from 'react';
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
// import AntdPresetsConfigProvider from '@components/AntdPresetsConfigProvider';
import { reactLoadable } from 'utils';
import { ConfigProvider } from 'antd';
import 'antd/lib/style';
// import zhCN from 'antd/es/locale/zh_CN';
import './App.less';
import packageJson from './../package.json';

const prefixCls = packageJson.antdConfig.prefixCls;

// import Home from './routes/home';
const Home = reactLoadable(() => import('./routes/home'));
const Login = reactLoadable(() => import('./routes/login'));

const App = () => (
	<ConfigProvider prefixCls={prefixCls}>
		<Router basename={window.__POWERED_BY_QIANKUN__? '/react' : '/'}>
			<div>
				<Switch>
					<Route exact path="/login" component={Login} />
					<Route path="/home" component={Home} />
					<Route path="/" component={Home} />
				</Switch>
			</div>
		</Router>
	</ConfigProvider>
);

export default App;

为了让主应用识别到子应用,子应用需要暴露出来一下信息,webpack需要改动一下output

const packageName = require('./package.json').name;
configure: (webpackConfig, {env, path}) => {
        console.log('env', env);
        console.log('path', path);
        console.log('webpackConfig--', webpackConfig);
        webpackConfig.output = {
                ...webpackConfig.output,
                library: `${packageName}-[name]`,
                libraryTarget: 'umd',
                chunkLoadingGlobal: `webpackJsonp_${packageName}`
        }
        return webpackConfig;
}

这里的packageName是对应主应用里的这个name image.png

最终的效果如图

image.png

以下是qiankun的开发指南,链接如下 qiankun.umijs.org/zh/guide