一、微前端简介
微前端是一种相似于微服务的架构,它将微服务的理念利用于浏览器端,行将 Web 利用由繁多的单体利用转变为多个小型前端利用聚合为一的利用。各个前端利用能够独立运行、独立开发、独立部署。
微前端的益处
利用自治
。只须要遵循对立的接口标准或者框架,以便于系统集成到一起,相互之间是不存在依赖关系的。繁多职责
。每个前端利用能够只关注于本人所须要实现的性能。技术栈无关
。你能够应用 Angular 的同时,又能够应用 React 和 Vue。
微前端的毛病
- 利用的拆分根底依赖于基础设施的构建,一旦大量利用依赖于同一基础设施,那么保护变成了一个挑战。
- 拆分的粒度越小,便意味着架构变得复杂、保护老本变高。
- 技术栈一旦多样化,便意味着技术栈凌乱
微前端由哪些模块组成
当下微前端次要采纳的是组合式利用路由计划,该计划的外围是“主从”思维,即包含一个基座(MainApp)利用
和若干个微(MicroApp)利用
,基座利用大多数是一个前端SPA我的项目,次要负责利用注册,路由映射,音讯下发等,而微利用是独立前端我的项目,这些我的项目不限于采纳React,Vue,Angular或者JQuery开发,每个微利用注册到基座利用中,由基座进行治理,然而如果脱离基座也是能够独自拜访,根本的流程如下图所示
是否要用微前端
微前端最佳的应用场景是一些B端的管理系统,既能兼容集成历史零碎,也能够将新的系统集成进来,并且不影响原先的交互体验
二、微前端实战
微前端现有的落地计划能够分为三类,自组织模式、基座模式以及模块加载模式
2.1 SingleSpa实战
官网 https://zh-hans.single-spa.js...
实用场景:我的项目宏大,多个子项目整合在一个大的我的项目中。即便子项目的所用的技术栈不同,比方vue,react, angular有相应的single-spa的轮子,能够进行整合
1.构建子利用
首先创立一个vue子利用,并通过single-spa-vue
来导出必要的生命周
vue create spa-vue npm install single-spa-vue
// main.jsimport 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、unmountexport const bootstrap = vueLifeCycle.bootstrap;export const mount = vueLifeCycle.mount;export const unmount = vueLifeCycle.unmount;export default vueLifeCycle;
// router.js// 配置子路由根底门路const router = new VueRouter({ mode: 'history', base: '/vue', //扭转门路配置 routes})
2. 将子模块打包成类库
//vue.config.jsmodule.exports = { configureWebpack: { // 把属性挂载到window上不便父利用调用 window.singleVue.bootstrap/mount/unmount output: { library: 'singleVue', libraryTarget: 'umd' }, devServer:{ port:10000 } }}
3. 主利用搭建
<div id="nav"> <router-link to="/vue">vue我的项目<router-link> <!--将子利用挂载到id="vue"标签中--> <div id="vue">div>div>
import Vue from 'vue'import App from './App.vue'import router from './router'import {registerApplication,start} from 'single-spa'Vue.config.productionTip = falseasync function loadScript(url) { return new Promise((resolve,reject)=>{ let script = document.createElement('script') script.src = url script.onload = resolve script.onerror = reject document.head.appendChild(script) })}// 注册利用registerApplication('myVueApp', async ()=>{ console.info('load') // singlespa问题 // 加载文件须要本人构建script标签 然而不晓得利用有多少个文件 // 款式不隔离 // 全局对象没有js沙箱的机制 比方加载不同的利用 每个利用都用同一个环境 // 先加载公共的 await loadScript('http://localhost:10000/js/chunk-vendors.js') await loadScript('http://localhost:10000/js/app.js') return window.singleVue // bootstrap mount unmount }, // 用户切换到/vue下 咱们须要加载方才定义的子利用 location=>location.pathname.startsWith('/vue'),)start()new Vue({ router, render: h => h(App)}).$mount('#app')
4. 动静设置子利用publicPath
if(window.singleSpaNavigate){ __webpack_public_path__ = 'http://localhost:10000/'}
2.2 qiankun实战
文档 https://qiankun.umijs.org/zh/...
- qiankun 是一个基于 single-spa 的微前端实现库,旨在帮忙大家能更简略、无痛的构建一个生产可用微前端架构零碎。
- qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品对立接入平台,在通过一批线上利用的充沛测验及打磨后,咱们将其微前端内核抽取进去并开源,心愿能同时帮忙社区有相似需要的零碎更不便的构建本人的微前端零碎,同时也心愿通过社区的帮忙将 qiankun 打磨的更加成熟欠缺。
- 目前 qiankun 已在蚂蚁外部服务了超过 200+ 线上利用,在易用性及齐备性上,相对是值得信赖的。
1. 主利用搭建
<template> <!--留神这里不要写app 否则跟子利用的加载抵触 <div id="app">--> <div> <el-menu :router="true" mode="horizontal"> <!-- 基座中能够放本人的路由 --> <el-menu-item index="/">Home</el-menu-item> <!-- 援用其余子利用 --> <el-menu-item index="/vue">vue利用</el-menu-item> <el-menu-item index="/react">react利用</el-menu-item> </el-menu> <router-view /> <!-- 其余子利用的挂载节点 --> <div id="vue" /> <div id="react" /> </div></template>
2. 注册子利用
import { registerMicroApps,start } from 'qiankun'// 基座写法const apps = [ { name: 'vueApp', // 名字 // 默认会加载这个HTML,解析外面的js动静执行 (子利用必须反对跨域) entry: '//localhost:10000', container: '#vue', // 容器 activeRule: '/vue', // 激活的门路 拜访/vue把利用挂载到#vue上 props: { // 传递属性给子利用接管 a: 1, } }, { name: 'reactApp', // 默认会加载这个HTML,解析外面的js动静执行 (子利用必须反对跨域) entry: '//localhost:20000', container: '#react', activeRule: '/react' // 拜访/react把利用挂载到#react上 },]// 注册registerMicroApps(apps)// 开启start({ prefetch: false // 勾销预加载})
3. 子Vue利用
// src/router.jsconst router = new VueRouter({ mode: 'history', // base里主利用外面注册的保持一致 base: '/vue', routes})
不要遗记子利用的钩子导出。
// main.jsimport Vue from 'vue'import App from './App.vue'import router from './router'Vue.config.productionTip = falselet instance = nullfunction render() { instance = new Vue({ router, render: h => h(App) }).$mount('#app') // 这里是挂载到本人的HTML中 基座会拿到挂载后的HTML 将其插入进去}// 独立运行微利用// https://qiankun.umijs.org/zh/faq#%E5%A6%82%E4%BD%95%E7%8B%AC%E7%AB%8B%E8%BF%90%E8%A1%8C%E5%BE%AE%E5%BA%94%E7%94%A8%EF%BC%9Fif(!window.__POWERED_BY_QIANKUN__) { render()}// 如果被qiankun应用 会动静注入门路if(window.__POWERED_BY_QIANKUN__) { // qiankun 将会在微利用 bootstrap 之前注入一个运行时的 publicPath 变量,你须要做的是在微利用的 entry js 的顶部增加如下代码: __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}// 子利用的协定 导出供父利用调用 必须导出promiseexport async function bootstrap(props) {} // 启动能够不必写 须要导出办法export async function mount(props) { render()}export async function unmount(props) { instance.$destroy()}
4. 配置vue.config.js
// vue.config.jsmodule.exports = { devServer:{ port:10000, headers:{ 'Access-Control-Allow-Origin':'*' //容许拜访跨域 } }, configureWebpack:{ // 打umd包 output:{ library:'vueApp', libraryTarget:'umd' } }}
5.子React利用
应用react作为子利用
// app.jsimport logo from './logo.svg';import './App.css';import {BrowserRouter,Route,Link} from 'react-router-dom'function App() { return ( // /react跟主利用配置保持一致 <BrowserRouter basename="/react"> <Link to="/">首页</Link> <Link to="/about">对于</Link> <Route path="/" exact render={()=>( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> )} /> <Route path="/about" exact render={()=>( <h1>About Page</h1> )}></Route> </BrowserRouter> );}export default App;
// index.jsimport React from 'react';import ReactDOM from 'react-dom';import './index.css';import App from './App';import reportWebVitals from './reportWebVitals';function render() { ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, 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-vitalsreportWebVitals();// 独立运行if(!window.__POWERED_BY_QIANKUN__){ render()}// 子利用协定export async function bootstrap() {}export async function mount() { render()}export async function unmount() { ReactDOM.unmountComponentAtNode(document.getElementById("root"));}
重写react中的webpack配置文件 (config-overrides.js)
yarn add react-app-rewired --save-dev
批改package.json文件
// react-scripts 改成 react-app-rewired"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" },
在根目录新建配置文件
// 配置文件重写touch config-overrides.js
// config-overrides.jsmodule.exports = { webpack: (config) => { // 名字和基座配置的一样 config.output.library = 'reactApp'; config.output.libraryTarget = "umd"; config.output.publicPath = 'http://localhost:20000/' return config }, devServer: function (configFunction) { return function (proxy, allowedHost) { const config = configFunction(proxy, allowedHost); // 配置跨域 config.headers = { "Access-Control-Allow-Origin": "*", }; return config; }; },};
配置.env文件
根目录新建.env
PORT=30000# socket发送端口WDS_SOCKET_PORT=30000
路由配置
import { BrowserRouter, Route, Link } from "react-router-dom"const BASE_NAME = window.__POWERED_BY_QIANKUN__ ? "/react" : "";function App() { return ( <BrowserRouter basename={BASE_NAME}><Link to="/">首页Link><Link to="/about">对于Link><Route path="/" exact render={() => <h1>hello homeh1>}>Route><Route path="/about" render={() => <h1>hello abouth1>}>Route>BrowserRouter> );}
2.3 飞冰微前端实战
官网接入指南 https://micro-frontends.ice.w...
- icestark 是一个面向大型零碎的微前端解决方案,实用于以下业务场景:
- 后盾比拟扩散,体验差异大,因为要频繁跳转导致操作效率低,心愿能对立收口的一个零碎内
- 单页面利用十分宏大,多人合作老本高,开发/构建工夫长,依赖降级回归老本高
- 零碎有二方/三方接入的需要
icestark 在保障一个零碎的操作体验根底上,实现各个微利用的独立开发和发版,主利用通过 icestark 治理微利用的注册和渲染,将整个零碎彻底解耦。
1. react主利用编写
$ npm init ice icestark-layout @icedesign/stark-layout-scaffold$ cd icestark-layout$ npm install$ npm start
// src/app.jsx中退出const appConfig: IAppConfig = { ... icestark: { type: 'framework', Layout: FrameworkLayout, getApps: async () => { const apps = [ { path: '/vue', title: 'vue微利用测试', sandbox: false, url: [ // 测试环境 // 申请子利用端口下的服务,子利用的vue.config.js外面 须要配置headers跨域申请头 "http://localhost:3001/js/chunk-vendors.js", "http://localhost:3001/js/app.js", ], }, { path: '/react', title: 'react微利用测试', sandbox: true, url: [ // 测试环境 // 申请子利用端口下的服务,子利用的webpackDevServer.config.js外面 须要配置headers跨域申请头 "http://localhost:3000/static/js/bundle.js", ], } ]; return apps; }, appRouter: { LoadingComponent: PageLoading, }, },};
// 侧边栏菜单// src/layouts/menuConfig.ts 革新const asideMenuConfig = [ { name: 'vue微利用测试', icon: 'set', path: '/vue' }, { name: 'React微利用测试', icon: 'set', path: '/react' },]
2. vue子利用接入
# 创立一个子利用vue create vue-child
// 批改vue.config.jsmodule.exports = { devServer: { open: true, // 设置浏览器主动关上我的项目 port: 3001, // 设置端口 // 反对跨域 不便主利用申请子利用资源 headers: { 'Access-Control-Allow-Origin' : '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization', } }, configureWebpack: { // 打包成lib包 umd格局 output: { library: 'icestark-vue', libraryTarget: 'umd', }, }}
src/main.js
革新
import { createApp } from 'vue'import App from './App.vue'import router from './router'import store from './store'import { isInIcestark, getMountNode, registerAppEnter, registerAppLeave, setLibraryName} from '@ice/stark-app'let vue = createApp(App)vue.use(store)vue.use(router)// 留神:`setLibraryName` 的入参须要与 webpack 工程配置的 output.library 保持一致// 重要 不加不失效 和 vue.config.js中配置的一样setLibraryName('icestark-vue')export function mount({ container }) { //  console.log(container,'container') vue.mount(container);}export function unmount() { vue.unmount();} if (!isInIcestark()) { vue.mount('#app')}
router
革新
import { getBasename } from '@ice/stark-app';const router = createRouter({ // 重要 在主利用中的基准路由 base: getBasename(), routes})export default router
3. react子利用接入
create-react-app react-child
// src/app.jsimport { isInIcestark, getMountNode, registerAppEnter, registerAppLeave } from '@ice/stark-app';export function mount(props) { ReactDOM.render(<App />, props.container);}export function unmount(props) { ReactDOM.unmountComponentAtNode(props.container);}if (!isInIcestark()) { ReactDOM.render(<App />, document.getElementById('root'));}if (isInIcestark()) { registerAppEnter(() => { ReactDOM.render(<App />, getMountNode()); }) registerAppLeave(() => { ReactDOM.unmountComponentAtNode(getMountNode()); })} else { ReactDOM.render(<App />, document.getElementById('root'));}
npm run eject
后,革新config/webpackDevServer.config.js
hot: '',port: '',...// 反对跨域headers: { 'Access-Control-Allow-Origin' : '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',},
微前端部署
微前端部署实际总结
更多干货在公众号「前端进阶之旅」分享