微前端学习总结

文章描述

微前端学习,微前端入门,qiankun入门;微前端借鉴了微服务的架构理念,将一个庞大的前端应用拆分为多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用联合为一个完整的应用

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

 

目前市场主流的一些微前端:

  • 基于 iframe 完全隔离的方案
    • 优点
      • 非常简单,无需任何改造
      • 完美隔离,JS、CSS 都是独立的运行环境
      • 不限制使用,页面上可以放多个 iframe 来组合业务
    • 缺点
      • 无法保持路由状态,刷新后路由状态就丢失
      • 完全的隔离导致与子应用的交互变得极其困难
      • iframe 中的弹窗无法突破其本身
      • 整个应用全量资源加载,加载太慢

 

  • 基于 single-spa 路由劫持方案
    • single-spa 通过劫持路由的方式来做子应用之间的切换,但接入方式需要融合自身的路由,有一定的局限性
    • qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台。它对 single-spa 做了一层封装。主要解决了 single-spa 的一些痛点和不足。通过 import-html-entry 包解析 HTML 获取资源路径,然后对资源进行解析、加载

 

  • 京东 micro-app 方案
    • 京东 micro-app 并没有沿袭 single-spa 的思路,而是借鉴了 WebComponent 的思想,通过 CustomElement 结合自定义的 ShadowDom,将微前端封装成一个类 webComponents 组件,从而实现微前端的组件化渲染

微前端改造例子:

前置的一些工作

mkdir qiankun-demo  创建一个目录
然后创建四个应用
create-react-app main-app  作为项目的基座
create-react-app react-app  子应用
vue create vue-app 用vue3   子应用(手动模式,安装上vue-router)
vue create common-app 用vue2 作为公用项目

当前目录结构

修改main-app的主要逻辑

安装依赖
yarn add react-router-dom qiankun
修改index.js文件
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App />
);
修改App.js文件
import { BrowserRouter as Router, Link } from 'react-router-dom';
function App() {
  return (
    <div className="App">
        <Router>
            <Link to="/react">react应用</Link>
            <Link to="/vue">vue应用</Link>
        </Router>
        {/* 切换子应用,将子应用渲染到该容器中 */}
         <div id="container"></div>
    </div>
   );
}
export default App;

src下创建registryApp.js文件

/**
 * 负责注册微应用(子应用)
 */
import { registerMicroApps, start } from 'qiankun';

const loader = (loading) => {
 console.log(loading);
} 

// 注册
registerMicroApps([
  {
     name: 'vue-app',
     entry: '//localhost: 9000', // 子应用的运行端口
     container: '#container', // 挂载容器
     activeRule: '/vue', // 激活条件
     loader,
  },
  {
     name: 'react-app',
     entry: '//localhost: 9001',
     container: '#container',
     activeRule: '/vue', 
     loader,
  },
], {
 // 生命周期
   beforeLoad() {
     console.log('加载前')
   },
   beforeMount() {
     console.log('挂载前')
  },
   afterMount() {
     console.log('挂载后')
  },
   beforeUnmount() {
     console.log('销毁后')
  },
   afterUnmount() {
     console.log('销毁后')
  },
})

// 开启应用
start();

在src/index.js引入

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './registryApp';

...

 

修改vue-app的主要逻辑

修改项目中vue.config.js ,没有则手动创建一个

 

vue.config.js 

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
 transpileDependencies: true,
 publicPath: '//localhost:9000', // 保证子应用静态资源都是向9000端口上发送请求
 devServer: {
     port: 9000,
     headers: {
         'Access-Control-Allow-Origin': '*',
     }
  },
 configureWebpack: {
     // 父应用需要获取子应用的打包内容,设置成UMD格式即可
     output: {
         libraryTarget: 'umd',
         library: 'vue-app',
     }
  }
})

修改main.js文件

import { createApp } from 'vue'
import App from './App.vue'
import routes from './router'
import { createRouter, createWebHistory } from 'vue-router'

let myHistory, myRouter, app;

// 不能直接挂载,调用mount方法时再去挂载
// createApp(App).use(store).use(router).mount('#app')

function render(props) {
     myHistory = createWebHistory('/vue');
     let { container } = props || {};
     myRouter = createRouter({ history: myHistory, routes });
     app = createApp(App);
     app.use(myRouter).mount(container ? container.querySelector('#app') : '#app')
}

// qiankun 在渲染时,会在window对象上挂载一个 __POWERED_BY_QIANKUN__  如果不存在,就是自运行,不是在基座上切换的
if (!window.__POWERED_BY_QIANKUN__) {
     render();
}

export async function bootstrap() {
     console.log('vue-app bootstraped')
}
 
export async function mount(props) {
     console.log('vue-app mount')
     render(props);
}
 
export async function unmount() {
     console.log('vue-app unmount')
     // 清空
     myHistory = null;
     myRouter = null;
     app = null;
}

修改router/index.js文件

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {
     path: '/',
     name: 'home',
     component: HomeView
  },
  {
     path: '/about',
     name: 'about',
     // route level code-splitting
     // this generates a separate chunk (about.[hash].js) for this route
     // which is lazy-loaded when the route is visited.
     component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  }
]

// 这里不需要了
// const router = createRouter({
//   history: createWebHistory(process.env.BASE_URL),
//   routes
// })

export default routes

现在我们就可以看到效果了

启动基座 yarn start
启动vue项目 yarn serve

继续React-app项目的修改

安装插件 yarn add @rescripts/cli -D (用来修改配置文件的一个插件)
在根目录下创建.rescriptsrc.js
// .rescriptsrc.js

module.exports = {
     webpack(config) {
       config.output.libraryTarget = 'umd';
       config.output.library = 'react-app';
       config.output.publicPath = '//localhost:9001/';
       return config;
     },
     devServer(config) {
         config.headers = {
             'Access-Control-Allow-Origin': '*',
         }
       return config;
     }
}

修改package.json

...
"scripts": {
 "start": "rescripts start",
 "build": "rescripts build",
 "test": "rescripts test",
 "eject": "rescripts eject"
},
...

根目录再创建一个.env文件

PORT=9001
WDS_SOCKET_PORT=9001

修改src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import RDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';


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

function render(props) {
     let { container } = props || {};
     const root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.getElementById('root'));

     root.render(
         <App />
      );
}

// qiankun 在渲染时,会在window对象上挂载一个 __POWERED_BY_QIANKUN__  如果不存在,就是自运行,不是在基座上切换的
if (!window.__POWERED_BY_QIANKUN__) {
     render();
}

export async function bootstrap() {
     console.log('react-app bootstraped')
}
 
export async function mount(props) {
     console.log('react-app mount')
     render(props);
}
 
export async function unmount(props) {
     let { container } = props || {};
     console.log('react-app unmount')
     // 卸载 unmountComponentAtNode
     RDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.getElementById('root'))
}

样式隔离

// 开启应用
start({
    sandbox: {
        strictStyleIsolation: true,
    }
});

gitee仓库:链接

 

评论(共0条)