微前端qiankun简易上手指南

标签: 前端 qiankun 简易 | 发表时间:2023-03-08 17:06 | 作者:OrzR3
出处:https://juejin.cn/frontend

theme: smartblue

前言

本文主要介绍了微前端  qiankun 环境的搭建,以及如何在主应用中挂载子应用,主应用和子应用之间通信,如何在子应用中接入路由。详细的整理,各种配置文件。分别介绍了 ReactVue 子应用的挂载方法。

如果,之前从未接触过微前端,这应该是个不错的上手项目。项目demo我已经放在 gitee 上面。

Gitee地址

那么,先从什么是微前端 qiankun 说起。

关于qiankun

微前端

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

微前端借鉴了微服务的架构理念,将一个庞大的前端应用拆分为多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用联合为一个完整的应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。

qiankun

qiankun 是一个基于  single-spa 的微前端实现库。

qiankun 官网地址

应用场景

1.项目的迁移,老项目的改造,更新主要的技术栈

2.公司的小伙伴儿比较多,用啥的都有

搭建环境

我使用的node版本:14.8.0

分别创建三个应用,将 qiankun-base 作为主应用

  npm create react-app qiankun-base --template typescript

npm create react-app qiankun-micro-app1 --template typescript

npm create react-app qiankun-micro-app2 --template typescript

相关配置,在每个应用的文件夹,根目录的中新建 .env 文件。

配置不同的端口号。

  // qiankun-base应用
PORT=3010
// qiankun-micro-app1应用
PORT=3011
//qiankun-micro-app2应用
PORT=3012

快速上手之前,先看一下官网的 快速上手

在主应用  qiankun-base 安装 qiankun

  
npm i qiankun -S

在主应用 qiankun-base 的入口文件 index.ts 中注册微应用

  
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'micro-app1', // app name registered
    entry: '//localhost:3011',
    container: '#micro-app1',
    activeRule: '/micro-app1',
  },
  {
    name: 'micro-app2',
    entry: '//localhost:3012',
    container: '#micro-app2',
    activeRule: '/micro-app2',
  },
]);

start();

当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑。

所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。

然后,在主应用中,修改App.tsx,加入 container 容器。

  
import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <div id="micro-app1"></div>
      <div id="micro-app2"></div>
    </div>
  );
}

export default App;

主应用中挂载子应用

微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。

React 微应用,相关配置:

1,在src文件夹下,新建 public-path.js 文件

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

2.修改webpack配置

①安装 react-app-rewired

  npm install react-app-rewired --save

②在 package.json 文件中,修改启动脚本。

  "scripts": {
    "start": "react-app-rewired start",
},

安装react-app-rewired 后,可以重写webpack的配置信息。

③在微应用根目录下,新建 config-overrides.js 文件。

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

module.exports = {
  webpack: (config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    config.output.jsonpFunction = `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;
  },
}

④修改微应用的入口文件 index.tsx

  // @ts-ignore
function render(props) {
  const { container } = props;
// @ts-ignore
  ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}
// @ts-ignore
export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}
// @ts-ignore
export async function mount(props) {
  console.log('[react16] props from main framework', props);
  render(props);
}
// @ts-ignore
export async function unmount(props) {
  const { container } = props;
// @ts-ignore
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}

⑤为了方便区分,修改 qiankun-micro-app1 微应用中的 App.tsx 文件。

  
import React from "react";
import "./App.css";

function App() {
  return <div className="App">qiankun-micro-app1</div>;
}

export default App;

⑥启动主应用,和两个子应用。

通过主应用配置的 activeRule 匹配到指定的 container 中,成功展示出子应用。

http://localhost:3010/micro-app1

页面展示:

qiankun-micro-app1

http://localhost:3010/micro-app2

页面有个图片没有展示出来,接下来就解决这个问题。

4.解决静态资源不显示的问题

在微应用src文件夹的 index.tsx 入口文件中,导入之前配置的 public-path.js 文件

  import './public-path';

刷新页面,成功展示图片

http://localhost:3010/micro-app2

打开控制台在 Elements 元素,可以看到:

  <img src="http://localhost:3012/static/media/logo.svg" class="App-logo" alt="logo">

导入 public-path.js 文件之后,图片路径变成了完整的url路径:

http://localhost:3012/static/media/logo.svg

主应用和子应用之间通信

主应用和子应用之间,可能公用一些参数,如何实现传参呢?

修改主应用 qiankun-base 的配置,在 index.tsx 文件中,加入 props 参数。

  registerMicroApps([
  {
    name: "micro-app1", // app name registered
    entry: "//localhost:3011",
    container: "#micro-app1",
    activeRule: "/micro-app1",
    props: {
      niceBody: "malena",
      age: 32
    }
  },
  {
    name: "micro-app2",
    entry: "//localhost:3012",
    container: "#micro-app2",
    activeRule: "/micro-app2",
    props: {
      niceBody: "malena",
      age: 32
    }
  }
]);

子应用 qiankun-micro-app1 的入口文件 index.tsx 中,在 mount 方法中,可以获取的主应用传递的props 参数。

  
// @ts-ignore
export async function mount(props) {
  console.log('[react16] props from main framework', props);
  // render(props);
  // @ts-ignore
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
  });
  // @ts-ignore
  // props.setGlobalState(state);
}

如果主应用中参数,发生改变。微应用中,也能接收到改变。

譬如,设置一个定时器,2秒后将state数据中的age从32改为34

主应用 入口文件 index.tsx

  import { initGlobalState, MicroAppStateActions } from 'qiankun';

const state = {
  name: 'malena morgan'
}

// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);

actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});

setTimeout(() => {
  actions.setGlobalState({ ...state, age: 34});
}, 2000);
actions.offGlobalStateChange();

微应用 入口文件 index.tsx

  // @ts-ignore
export async function mount(props) {
  console.log('[react16] props from main framework', props);
  // render(props);
  // @ts-ignore
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
  });
}

如果在微应用中,改变 state 的值,主应用中也能拿到

  // @ts-ignore
export async function mount(props) {
  console.log('[react16] props from main framework', props);
  // render(props);
  // @ts-ignore
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
      // @ts-ignore
    setTimeout(() => {
      props.setGlobalState({...state, age: 36})
    }, 2000);
  });
}

qiankun接入vue3

首先,新建一个vue项目

安装脚手架

  npm install -g @vue/cli 

创建vue3项目

  vue create qiankun-micro-vue3-app3

安装typescript

  cd qiankun-micro-vue3-app3
vue add typescript

进行配置:

修改 vue.config.js 文件

  // @ts-nocheck
const { name } = require('./package.json');
module.exports = {
  devServer:{
    port: 3013,
    headers:{
      'Access-Control-Allow-Origin': '*',
    }
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
    },
  },
}

在 src 文件夹下,新建 public-path.js 文件

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

修改 main.ts 入口文件

  // @ts-nocheck
import "./public-path";
import { createApp } from "vue";
import Vue from "vue";
import App from "./App.vue";
let instance = null;

function render(props = {}) {
  const { container } = props;
  instance = createApp(App);
  instance.mount(container ? container.querySelector("#app") : "#app");
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log("[vue] vue app bootstraped");
}

export async function mount(props) {
  console.log('vue3-app')
  console.log(props)
  render(props);
  instance.config.globalProperties.$onGlobalStateChange =
    props.onGlobalStateChange;
  instance.config.globalProperties.$setGlobalState = props.setGlobalState;
}

export async function unmount() {
  instance.unmount();
  instance._container.innerHTML = "";
  instance = null;
}

在 qiankun-base 主应用中,加载微应用

  registerMicroApps([
  {
    name: "micro-app1", // app name registered
    entry: "//localhost:3011",
    container: "#micro-app1",
    activeRule: "/micro-app1",
    props: {
      niceBody: "malena",
      age: 32
    }
  },
  {
    name: "micro-app2",
    entry: "//localhost:3012",
    container: "#micro-app2",
    activeRule: "/micro-app2",
    props: {
      niceBody: "malena",
      age: 32
    }
  },
  {
    name: "micro-vue3-app3",
    entry: "//localhost:3013",
    container: "#micro-vue3-app3",
    activeRule: "/micro-vue3-app3",
    props: {
      niceBody: "malena",
      age: 32
    }
  }
]);

如你所愿,如下所示vue3应用

到此为止,完成了各个子应用之间的切换,那么子应用中各个页面的切换,又该怎么做呢?这就是下面要讲到的。

react子应用接入路由

首先,当然要安装路由 react-router-dom

  npm install react-router-dom --save

在子应用 index.tsx 入口文件中,引入路由

  import { BrowserRouter } from "react-router-dom";
  import "./public-path";
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <BrowserRouter>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </BrowserRouter>
);

// @ts-ignore
function render(props) {
  const { container } = props;
  // @ts-ignore
  ReactDOM.render(
    <BrowserRouter
      basename={window.__POWERED_BY_QIANKUN__ ? "/micro-app2" : "/"}
    >
      <App />
    </BrowserRouter>,
    container
      ? container.querySelector("#root")
      : document.querySelector("#root")
  );
}
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
  // render({});
}
// @ts-ignore
export async function bootstrap() {
  console.log("[react16] react app bootstraped");
}
// @ts-ignore
export async function mount(props) {
  console.log("[react16] props from main framework", props);
  render(props);
}
// @ts-ignore
export async function unmount(props) {
  const { container } = props;
  // @ts-ignore
  ReactDOM.unmountComponentAtNode(
    container
      ? container.querySelector("#root")
      : document.querySelector("#root")
  );
}

// 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();

在 /src/pages 文件夹下,新建两个页面,命名随意。

修改 App.tsx 文件,配置这两个页面的路由。

  import React from 'react';
import logo from './logo.svg';
import './App.css';
import { Routes, Route, Link } from "react-router-dom";
import Mila from './pages/Mila';
import Malena from './pages/Malena';

function App() {
  return (
    <div className="App">
      <Link to={"/"}>home</Link> | 
      <Link to={"/mila"}>micro-app2 mila</Link> | 
      <Link to={"/malena"}>micro-app2 malena</Link>
      <Routes>
        <Route path="/mila" element={<Mila/>} />
        <Route path="/malena" element={<Malena/>} />
      </Routes>
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

正如你所看到的,成功接入路由。

vue3子应用接入路由

第一步,当然也是安装路由

  npm install vue-router --save

新建 src\router\index.ts 文件,新建两个页面,在路由文件中配置。

  
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Mila from '../pages/Mila.vue';
import Malena from '../pages/Malena.vue';

const routes: RouteRecordRaw[] = [
  {
    path: '/mila',
    component: Mila
  },
  {
    path: '/malena',
    component: Malena
  }
];
const router = createRouter({
  history: createWebHistory(
    window.__POWERED_BY_QIANKUN__ ? "/micro-vue3-app3" : "/"
  ),
  routes
});
export default router;

然后,在 main.ts 文件中使用

  import router from './router/index';

function render(props = {}) {
  const { container } = props;
  instance = createApp(App);
  instance.use(router)
  instance.mount(container ? container.querySelector("#app") : "#app");
}

入口页面 App.vue 中,加入 router-view

  <template>
  <router-link to="/mila">mila</router-link> |
  <router-link to="/malena">malena</router-link>
  <router-view />
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import HelloWorld from './components/HelloWorld.vue';

@Options({
  components: {
    HelloWorld,
  },
})
export default class App extends Vue {}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

vue3子应用,也成功接入路由。

http://localhost:3010/micro-vue3-app3/malena

最后的话

以上,如果对你有用的话,不妨点赞收藏关注一下,谢谢

微信公众号: OrzR3

不定期更新一些技术类,生活类,读书类的文章。

相关 [前端 qiankun 简易] 推荐:

微前端qiankun简易上手指南

- - 掘金 前端
本文主要介绍了微前端  qiankun 环境的搭建,以及如何在主应用中挂载子应用,主应用和子应用之间通信,如何在子应用中接入路由. 分别介绍了 React 和 Vue 子应用的挂载方法. 如果,之前从未接触过微前端,这应该是个不错的上手项目. 项目demo我已经放在 gitee 上面.

微前端及Vue + qiankun 实现案例 - 简书

- -
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略. 微前端的概念出现于2016年末,其将微服务的概念引入前端世界. 用以解决在需求、人员、技术栈等因素不断更迭下前端工程演变成巨石应用(Frontend Monolith)**而不可维护的问题. 这类问题尤其常见于企业级Web项目中.

微前端qiankun从搭建到部署的实践 - SegmentFault 思否

- -
qiankun,写篇文章分享下实战中遇到的一些问题和思考. 项目有个功能需求是需要内嵌公司内部的一个现有工具,该工具是独立部署的且是用. React写的,而我们的项目主要技术选型是. vue,因此需要考虑嵌入页面的方案. 两种方案都能满足我们的需求且是可行的. iframe方案虽然普通但很实用且成本也低,.

除了 Qiankun, 这些微前端框架或许更适合你「建议收藏」

- - 掘金 架构
大家好,我是 易师傅,上篇文章给大家主要讲解到了 《微前端的入门》,这篇文章给大家收集归纳了微前端常用的框架,以及他们优缺点;. 如果你正在做微前端,但是苦于不知使用哪一个微前端框架而苦恼时,我相信这篇文章能够给到你答案. 如果您对微前端感兴趣可以关注我的专栏 《微前端从入门到进阶》;. 细想一下:通过微前端常见的解决方案,其实我们是可以归纳一些微前端存在的问题,比如 样式冲突、消息通信、脚本互斥、公共依赖加载等问题;所以 微前端框架 就因为这些问题应运而生了.

基于qiankun框架的微前端实战使用_DJYanggggg的博客-CSDN博客_qiankun框架使用教程

- -
   最近公司要整合目前所有的前端项目,希望放到同一个项目里面进行管理,但是项目使用的技术栈大体不相同,有原生js的,有用jq的也有用Vue的,整合的话要么重构,统一技术栈,不过这是不现实的,成本高,时间长,要么使用iframe,但是iframe也有很多缺点,通信不方便,刷新页面会导致路由丢失,这都是很不好的体验,于是我想起了最近很火的微前端概念,打算用微前端的技术来整合已有项目.

前端技术

- - CSDN博客综合推荐文章
随着互联网产业的爆炸式增长,与之伴生的Web前端技术也在历经洗礼和蜕变. 尤其是近几年随着移动终端的发展,越来越多的人开始投身或转行至新领域,这更为当今的IT产业注入了新的活力. 尽管Web前端技术诞生至今时日并不长,但随着Web技术的逐渐深入,今后将会在以下几方面发力. JavaScript的兄弟们.

简易榨汁器

- Richard - 设计|生活|发现新鲜
现在各种各样的自动化家电,绝对让你成为懒人中的懒人. 其实,有时候自己亲自动手别有一番趣味,试试这款简易榨汁器吧. 看看你制作出来的口感和机器制作出来的是否一样呢. 「设计,生活,发现新鲜」在新浪微博,更即时地获读更新,更直接地交流沟通. © 设计|生活|发现新鲜 | 原文链接 | 投稿 ! | 新浪微博 | 逛逛我们的在线商店.

简易垃圾桶

- Zoe - 玩意儿
Jonliow 设计了一款简易的垃圾桶,外形就像是一个倒立的凳子,不过它却又很强的灵活性,可以放置各种大小的垃圾袋,比一般的垃圾桶更容易清洗,不过不能倒汤汁类的,否则垃圾袋有破洞就麻烦了. 本文原始链接:http://www.cngadget.cn/simple-trash.html. 让办公室的垃圾也为你增加乐趣.

Web前端优化

- - JavaScript - Web前端 - ITeye博客
优点:直接使用浏览器内存的缓存数据,减少网站后台压力,用户体验(速度)好. 缺点:对于时时变化的动态页面,这种情况就不能容忍了,因为每次访问的都是第一次访问的内容,这样即使所请求的页面已经变化了,用户也不可能知道,所以此场景必须要消除这种缓存的影响. 延迟加载,将资源延迟到需要的时候的加载,例如detail页面,相关产品推荐,当用户浏览更多的信息往下拉动滚动时,才进行加载,异步加载可以大幅减少对后端资源的使用,在需要的时候加载,是资源合理使用常用的方式,但是也带来一个问题,当往下拉才去加载,如果性能不够好,用户的体验其实是不好的,“菊花”转动的时间会比较长,同时异步加载对前端性能的作用也是非常明显的,渲染的节点数量大幅减少.

Web 前端测试

- - Web前端 - ITeye博客
Web 网站测试流程和方法(转载). 进行正式测试之前,应先确定如何开展测试,不可盲目的测试. 一般网站的测试,应按以下流程来进行:. 1)使用HTML Link Validator将网站中的错误链接找出来;. 2)测试的顺序为:自顶向下、从左到右;. 3)查看页面title是否正确. (不只首页,所有页面都要查看);.