实战微前端,从使用qiankun到自写微前端

我本地写的文档,大部分内容我直截了当的截图了。

image.png

image.png

image.png

image.png

image.png

image.png

我根据报错提示进行了调整:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { isInIcestark, setLibraryName } from '@ice/stark-app';

import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

let w_root;
export function mount(props) {
  w_root = ReactDOM.createRoot(props.container);
  w_root.render(<App {...props.customProps} />);
}
export function unmount(props) {
  w_root.unmount();
}
setLibraryName('microApp');

const root = ReactDOM.createRoot(document.getElementById('root'));
if (!isInIcestark()) {
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
}

// 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();
import { useEffect } from 'react';

import './App.css';

function App() {
  useEffect(() => {
    console.log('useEffect');
    return () => {
      console.log('unmout');
    }
  })
  return (
    <div className="App">
      我是create-react-app的独立项目
    </div>
  );
}

export default App;

子应用的生命周期感知:顺利在进入的时候打印到“useEffect”, 离开的时候打印了"unmout", 完成将普通的create-react-app 项目成为子应用。

性能优化 性能优化 | icestark
image.png

image.png

image.png
第二步:主应用接入
  1. yarn add qiankun

  2. index.js 文件 (配置路由)

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { BrowserRouter } from "react-router-dom";
    
    import './index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    
    const root = ReactDOM.createRoot(document.getElementById('root_main'));
    root.render(
      <React.StrictMode>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </React.StrictMode>
    );
    
    // 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();
    
    
  1. 主应用App.js文件(入口文件):

    import { registerMicroApps, start } from 'qiankun';
    import { Routes, Route, Link } from "react-router-dom";
    import { useEffect } from 'react';
    
    import './App.css';
    
    
    function App() {
      useEffect(() => {
        registerMicroApps([
          {
            name: 'child', // app name registered
            entry: '//localhost:7001',
            container: '#Child',
            activeRule: 'child'
          }, {
            name: 'about', // app name registered
            entry: '//localhost:7001',
            container: '#about',
            activeRule: 'about'
          },
        ]);
        start();
      }, [])
      return (
        <div className="App">
          <div style={{ background: '#577dc3', padding: 10, color: '#fff' }}>
            headers
          </div>
          <div style={{ display: 'flex' }}>
            <div style={{ padding: 10, borderRight: '1px solid #eee' }}>
              <Link to="/main">main</Link>
              <br /><br />
              <Link to="/child">child</Link>
              <br /><br />
              <Link to="/about">about</Link>
              <br /><br />
              <Link to="/">/</Link>
            </div>
            <div style={{ flex: 1 }}>
              <Routes>
                <Route path="/" element={<div>enter</div>} />
                <Route path="main" element={<div>main</div>} />
                <Route path="child" element={<div id="Child"></div>} />
                <Route path="about" element={<div id="about"></div>} />
              </Routes>
            </div>
          </div>
        </div>
      );
    }
    
    export default App;
    App.css  样式文件
    
    .App {
      width: 100vw;
      height: 100vh;
      overflow: hidden;
    }
    
  2. 上一步中,只有1,3是跟逻辑相关的,2. 4都是强调了第一步的改动,到此主应用就完成了。

第三步:子应用接入
  1. yarn eject

  2. 修改配置:webpack.config.js文件


    image.png

    image.png
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from "react-router-dom";

import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';


/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {
  console.log('react app bootstraped');
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
let root_child
export async function mount(props) {
  root_child = ReactDOM.createRoot(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
  root_child.render(
    <React.StrictMode>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </React.StrictMode>
  );
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount(props) {
  console.log(props.container.querySelector('#root'))
    // unmountComponentAtNode会报错,注释掉后不影响使用
  // ReactDOM.unmountComponentAtNode(
  //   props.container ? props.container.querySelector('#root') : document.getElementById('root'),
  // );
}

/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
  console.log('update props', props);
}

if (!window.__POWERED_BY_QIANKUN__) {
  // 不是qiankun过来的就正常展示
  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(
    <React.StrictMode>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </React.StrictMode>
  );
}

// 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();
  • 这里重点注意:官网给的是

    export async function mount(props) {
      ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
    }
    

这个是旧版本的写法,新版本的react需要ReactDOM.createRoo,然后render来作为渲染方式,不然会报错。


image.png

image.png

image.png

image.png

image.png

image.png

这里我只是简单实现了在主应用中插入子应用,效果如上图。

思路
  1. 请求子应用url地址,得到入口js文件,这里也需要配置跨域哦
  2. 子应用向window注册wei_mount事件(我目前没有想到其他更好的办法)
  3. 主应用修改window._IS_WS_WEI,表示当前为微服务
  4. 主应用写上根模块id="child"
主应用实现代码

这里注意bundle.js的入口路径不同项目也许有差别,可以做个微调。

app.js文件

import { useState } from 'react';
import { useEffect } from 'react';
import './App.css';

window._IS_WS_WEI_ = 'wangshi的demo';
let a;
const runScript = async (url) => {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = url;
    script.onload = resolve;
    script.onerror = reject;
    const firstScript = document.getElementsByTagName('script')[0];
    firstScript.parentNode.insertBefore(script, firstScript);
  });
};

function App() {
  useEffect(() => {
    asyncCall();
  }, [])
  async function asyncCall() {
    if (a) {
      return;
    }
    a = true;
    await runScript('http://localhost:3001/static/js/bundle.js')
    window.wei_mount({ container: document.getElementById('child') })
  }
  return (
    <div className="main_App">
      我是主
      <div id="child"></div>
    </div>
  );
}

export default App;

入口文件路径查找: 我是这样确认我的入口位置的


image.png

image.png
image.png

Why Not Iframe

为什么不用 iframe,这几乎是所有微前端方案第一个会被 challenge 的问题。但是大部分微前端方案又不约而同放弃了 iframe 方案,自然是有原因的,并不是为了 "炫技" 或者刻意追求 "特立独行"。

如果不考虑体验问题,iframe 几乎是最完美的微前端解决方案了。

iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。

1url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
2UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
3全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
4慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

其中有的问题比较好解决(问题1),有的问题我们可以睁一只眼闭一只眼(问题4),但有的问题我们则很难解决(问题3)甚至无法解决(问题2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题, 最终导致我们舍弃了 iframe 方案。

-----摘抄于qiankun

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

推荐阅读更多精彩内容

  • 什么是微前端 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略. 微前...
    lean_阅读 2,563评论 0 7
  • 示例代码仓库:yl-qiankun-base:https://gitee.com/dongche/yl-qiank...
    东扯葫芦西扯瓜阅读 5,502评论 0 5
  • 一. 纯原生 redux 的使用 Redux 是常用的状态管理容器,能够帮助我们对一些全局变量进行有效管理。首先,...
    林木木road阅读 1,988评论 0 2
  • 1.发现问题 在生产大型前端项目时候,随着需求、模块的不断增加。项目的打包维护效率越来越低。 项目中部分稳定的模块...
    歆一阅读 10,577评论 1 5
  • 1. React简介 React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScrip...
    王蕾_fd49阅读 411评论 0 0