qiankun搭建项目

什么是微前端

微前端是指存在于浏览器中的微服务,其借鉴了微服务的架构理念,将微服务的概念扩展到了前端。

如果对微服务的概念比较陌生的话,可以简单的理解为微前端就是将一个大型的前端应用拆分成多个模块,每个微前端模块可以由不同的团队进行管理,并可以自主选择框架,并且有自己的仓库,可以独立部署上线。

基于qiankun的微前端实战

这里准备三个项目,基座使用react项目,两个子应用一个使用react18,一个使用vue3。

├── base-mrc     // 基座
├── mrc-react       // react子应用,create-react-app创建的react应用,使用webpack打包
├── mrc-vue  // vue子应用,vite创建的子应用

基座配置,这里用react项目配置基座

主要负责集成所有的子应用,提供一个入口能够访问你所需要的子应用的展示,尽量不写复杂的业务逻辑 - 子应用:根据不同业务划分的模块,每个子应用都打包成umd模块的形式供基座(主应用)来加载。

  1. 安装qiankun
npm i qiankun // 或者 yarn add qiankun

2、入口文件配置(index.js)

import { start, registerMicroApps } from 'qiankun';
const apps = [
  {
    name: "mrcReact", // 子应用的名称
    entry: 'http://localhost:8081/mrcReact/', // 默认会加载这个路径下的html,解析里面的js
    activeRule: "/mrcReact", // 匹配的路由
    container: "#container" // 加载的容器
  },
  {
    name: "mrcVue", // 子应用的名称
    entry: 'http://localhost:8082/mrcVue/', // 默认会加载这个路径下的html,解析里面的js
    activeRule: "/mrcVue", // 匹配的路由
    container: "#container" // 加载的容器
  },
]

// 2. 注册子应用
setTimeout(() => {
  registerMicroApps(apps, {
    beforeLoad: [async app => console.log('before load', app.name)],
    beforeMount: [async app => console.log('before mount', app.name)],
    afterMount: [async app => console.log('after mount', app.name)],
  })
  
  start();
})

3、在页面上添加id为container的占位符,用来加载子应用

    <div className="root">
      <div className="menu">
        <div className="menu-item" onClick={()=>nav('/mrcReact')}>首页</div>
        <div className="menu-item" onClick={()=>nav('/mrcVue')}>新闻</div>
      </div>
      <div className="cont"><Outlet /><div id='container'></div></div>
    </div>

react子应用配置

使用create-react-app脚手架创建,webpack进行配置,为了不eject所有的webpack配置,我们选择用react-app-rewired工具来改造webpack配置。

1入口文件配置(index.js)

// 防止资源加载错位
import './public-path.js';

//qiankun环境下应用挂在到基座的root元素下
let root;
function render(props) {
  const { container } = props
  const dom = container ? container.querySelector('#root') : document.getElementById('root')
  root = ReactDOM.createRoot(dom)
  root.render(
    <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/mrcReact' : '/'}>
      <App />
    </BrowserRouter>
  )
}

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

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

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

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

2、资源加载(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__
}

3、配置webpack改造打包方式(config-overrides.js)
子应用打包方式改成umd方式并在请求头添加跨域设置

const { name } = require("./package");

process.env.PORT = 8081;
module.exports = {
  webpack: (config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    // If you are using webpack 5, please replace jsonpFunction with chunkLoadingGlobal
    config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';
    return config;
  },
  devServer: (_) => {
    const config = _;

    config.headers = {
      'Access-Control-Allow-Origin': '*',
    };
    config.historyApiFallback = true;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;

    return config;
  },
};

vue子应用配置

1入口文件配置(main.js)

import './public-path'

let app;
if (!window.__POWERED_BY_QIANKUN__) {
    createApp(App).mount('#app');
}
  
export async function bootstrap() {
    console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
    app = createApp(App);
    console.log("props.container.querySelector('#app'):", props.container.querySelector('#app'));
    app.mount(props.container.querySelector('#app'));
}
export async function unmount() {
    app?.unmount();
}

2、资源加载(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__
}

3、配置webpack改造打包方式(vue.config.js)

const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package');
module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: '/mrcVue',
  devServer: {
    port: 8082,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // bundle the micro app into umd library format
      chunkLoadingGlobal: `webpackJsonp_${name}`, // // If you are using webpack 5, please replace jsonpFunction with chunkLoadingGlobal
    },
  },
})

问题汇总

1、在基座中刷新某个子应用时而能出来时而加载不出来(子应用加载快于基座,导致找不到根节点),解决方案:延迟加载子应用

setTimeout(() => {
  registerMicroApps(apps, {
    beforeLoad: [async app => console.log('before load', app.name)],
    beforeMount: [async app => console.log('before mount', app.name)],
    afterMount: [async app => console.log('after mount', app.name)],
  })
  start();
})

2、qiankun实现了各个子应用之间的样式隔离,但是基座和子应用之间的样式隔离没有实现,所以基座和子应用之前的样式还会有冲突和覆盖的情况,解决方法:1、每个应用的样式使用固定的格式 2、通过css-module的方式给每个应用自动加上前缀

//  子应用配置css module
css: {
    loaderOptions: {
      css: {
        modules: {
          auto: () => true   /* 样式会被编译独一无二的字段,需要通过引用变量的形式加载样式 */
        }
      }
    }
  }

// 加载样式
import styles from "./Header.module.css";
export default function Header() {
  return <h2 className={styles.title}>Header 组件</h2>;
}

3、父子应用的通信
基座引用qiankun框架的initGlobalState属性注册全局状态,子应用通过生命周期props可以发送和接收数据。

// 基座通过onGlobalStateChange监听数据
let state = {msg: ''}
const actions = initGlobalState(state);
// 主项目项目监听和修改
actions.onGlobalStateChange((state, prev) => {
  console.log("父应用接受到数据:", state, prev);
});

// 子应用通过setGlobalState发送数据
export async function mount(props) {
    setInterval(() => {
        props.setGlobalState({msg: 666});
    }, 5000);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容