微前端学习总结
文章描述
微前端学习,微前端入门,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仓库:链接