微前端 从 0到 1搭建
微前端
微前端
Single-SPA
❝微服务是面向服务架构(SOA)的一种变体,把应用程序设计成一系列松耦合的细粒度服务,并通过轻量级的通信协议组织起来 具体地,将应用构建成一组小型服务。这些服务都能够独立部署、独立扩展,每个服务都具有稳固的模块边界,甚至允许使用不同的编程语言来编写不同服务,也可以由不同的团队来管理
❞
概念
官网
:
2018年 Single-SPA诞生了, single-spa是一个用于前端微服务化的JavaScript前端解决方案 (本身没有处理样式隔离、js执行隔离) 实现了路由劫持和应用加载;
Alibaba -
springboot
- sofaboot
Single-SPA 搞了个入口 --> qiankun
2019年 qiankun基于Single-SPA, 提供了更加开箱即用的 API (single-spa + sandbox + import-html-entry),它 做到了技术栈无关,并且接入简单(有多简单呢,像iframe一样简单)。
总结:子应用可以独立构建,运行时动态加载,主子应用完全解耦,并且技术栈无关,靠的是协议接入(这里提前强调一下:子应用必须导出 bootstrap、mount、unmount三个方法)。
micro front ends single spot
应用量庞大,
实现上,关键问题在于:
多个 Bundle 如何集成?
子应用之间怎样隔离影响?
公共资源如何复用?
子应用间怎样通信?
如何测试?
当然,这种架构模式并非百益而无一害,一些问题也随之而来:
导致依赖项冗余,增加用户的流量负担
团队自治程度的增加,可能会破坏协作
「.....」
简单来讲,微前端的理念类似于微服务:
❝In short, micro frontends are all about slicing up big and scary things into smaller, more manageable pieces, and then being explicit about the dependencies between them.
❞
将庞大的整体拆成可控的小块,并明确它们之间的依赖关系。关键优势在于:
代码库更小,更内聚、可维护性更高 松耦合、自治的团队可扩展性更好 「渐进地升级、更新甚至重写部分前端功能成为了可能」
「微前端
」
微前端
就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。
微前端的核心在于「拆」, 拆完后再「合」!
今天来一块聊聊微前端 技术
一门前端语言的基础 Vue React
SingleSpa 实战
构建子应用
首先创建一个vue
子应用,并通过single-spa-vue
来导出必要的生命周期:
vue create spa-vue
npm install single-spa-vue
import singleSpaVue from 'single-spa-vue';
const appOptions = {
el: '#vue',
router,
render: h => h(App)
}
// 在非子应用中正常挂载应用
if(!window.singleSpaNavigate){
delete appOptions.el;
new Vue(appOptions).$mount('#app');
}
const vueLifeCycle = singleSpaVue({
Vue,
appOptions
});
// 子应用必须导出以下生命周期:bootstrap、mount、unmount
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
export default vueLifeCycle;
const router = new VueRouter({
mode: 'history',
base: '/vue', //改变路径配置
routes
})
配置库打包
//vue.config.js
module.exports = {
configureWebpack: {
output: {
library: 'singleVue',
libraryTarget: 'umd'
},
devServer:{
port:10000
}
}
}主应用搭建
❝将子应用挂载到
❞id="vue"
标签中
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
const loadScript = async (url)=> {
await new Promise((resolve,reject)=>{
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script)
});
}
import { registerApplication, start } from 'single-spa';
registerApplication(
'singleVue',
async ()=>{
//这里通过协议来加载指定文件
await loadScript('http://localhost:10000/js/chunk-vendors.js');
await loadScript('http://localhost:10000/js/app.js');
return window.singleVue
},
location => location.pathname.startsWith('/vue')
)
start();
new Vue({
router,
render: h => h(App)
}).$mount('#app')
动态设置子应用
if(window.singleSpaNavigate){
__webpack_public_path__ = 'http://localhost:10000/'
}
前置条件
npm install -g yarn
yarn init
安装 官方 React
Create React App
是FaceBook的React团队官方出的一个构建React
单页面应用的脚手架工具。它本身集成了Webpack
,并配置了一系列内置的loader
和默认的npm的脚本,可以很轻松的实现零配置就可以快速开发React的应用。
# 全局安装
npm install -g create-react-app
# 构建一个my-app的项目
npx create-react-app my-app
cd my-app
# 启动编译当前的React项目,并自动打开 http://localhost:3000/
npm start
构建 React项目
npm
npm init react-app my-app
Yarn
# yarn create is available in Yarn 0.25+
yarn create react-app my-app
使用 qiankun 微前端构建
官方文档
: https://qiankun.umijs.org/zh
❝首先我们需要创建 3个 前端应用, 微前端 ,就是 代表 一个小型应用的独立部署,独立交互,需要 应用之间进行通信,这里我们使用qiankun来完成 微前端 应用
❞
创建 3 个 react app
yarn create react-app qiankun-base --template typescript
yarn create react-app qiankun-micro-app1 --template typescript
yarn create react-app qiankun-micro-app2 --template typescript
app2 app1 基座 在 react app 应用中 安装 qiankun 依赖
$ yarn add qiankun # or npm i qiankun -S
分别创建 .env 文件来指定 项目 运行的端口号
PORT=3010
PORT=3011
PORT=3012
在主应用中index.tsx 注册子应用
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react app one', // app name registered
entry: '//localhost:3011',
container: '#micro-app2',
activeRule: '/micro-app2',
props:{
nickname: "全栈小刘",
age:19
}
},
{
name: 'react app two', // app name registered
entry: '//localhost:3012',
container: '#micro-app1',
activeRule: '/micro-app1',
props:{
nickname: "全栈小刘",
age:18
}
},
]);
start();
ReactDOM.render(
,
document.getElementById('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();
❝❞
注
:子应用加载进来 ,需要有主应用进行挂载,现在我们已经将 子应用注册在 了 主应用当中
name 应用名称 entry 端口号 container 挂载容器 activeRule 激活的规则 props: 父子属性之间传参
api文档
: https://qiankun.umijs.org/zh/api
在 App.tsx 中创建挂载点
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
);
}
export default App;
在「所有」应用中 添加 public-path.js 用于 加载静态 资源
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
在子应用中 添加 webpack 重写项
添加
yarn add react-app-rewired -D
「设置 子应用」启动,在 scripts
"scripts": {
"start": "react-app-rewired start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
在webpack 中 进行 overrides 重写,重写的 目的是 允许跨域
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;
},
};
在不同的子应用当中 去加载 tsx 生命周期
app1 、app2 、 index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
export async function bootstrap() {
console.log('[react] react app bootstraped');
}
// @ts-ignore
export async function mount(props) {
console.log(props)
ReactDOM.render(
,
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
// @ts-ignore
export async function update(props){
console.log("update props",props)
}
// @ts-ignore
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
ReactDOM.render( ,document.getElementById("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();
在 index.tsx 引入 public-path.js 解决静态资源
import './public-path.js'
在 主 应用 添加 访问
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
);
}
export default App;
在 主 应用 index.tsx 中 传递 数据
props:{
nickname: "全栈小刘",
age:19
}
在 子应用 周期中进行打印
export async function mount(props) {
console.log(props)
ReactDOM.render(
,
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
在 主应用中 监听事件改变
import { initGlobalState, MicroAppStateActions } from 'qiankun';
const state ={
nickname: "全栈小刘"
}
// 初始化
const actions: MicroAppStateActions = initGlobalState(state);
actions.onGlobalStateChange((state,prev)=>{
console.log(state,prev)
})
// 2秒钟后 改变
setTimeout(()=>{
actions.setGlobalState({...state,age:19})
},2000)
在子应用中 监听改变
export async function mount(props) {
console.log(props)
// @ts-ignore
props.onGlobalStateChange((state,prev)=>{
console.log(state,prev)
setTimeout(()=>{
props.setGlobalState({ ...state, age:20 });
},2000)
})
// @ts-ignore
ReactDOM.render(
,
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
安装 NPM SCRIPT 插件 ,分别 启动 运行
接入 Vue3
❝通用 vue3
❞
安装最新的脚手架
npm install -g @vue/cli
创建 项目 es6 js 模块
vue create qiankun-vue-micro-app3
添加typescript ,转换 ts -Y yes
vue add typescript
依次加入 public-path.js
/* eslint-disable */
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
安装 qiankun
yarn add qiankun
参考qiankun官网的示例main.js ,完成生命周期的钩子函数
base 中 进行注册 index.tsx
vue-config.js 设置启动端口
vue-config.js
/* eslint-disable */
const { name } = require('./package');
module.exports = {
devServer:{
port: 3013,
headers:{
'Access-Control-Allow-Origin': '*',
}
},
configureWebpack:{
output: {
library: `${name}-[name]`,
libraryTarget: 'umd'
}
}
};
微前端项目 实战
https://github.com/a1029563229/micro-front-template
Reference Document : https://juejin.cn/post/6844903943873675271 https://zhuanlan.zhihu.com/p/96464401 https://single-spa.js.org/