内容简介:微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。微前端的核心在于拆, 拆完后在合!
一、为什么要学习微前端
什么是微前端
微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。
微前端的核心在于拆, 拆完后在合!
为什么去使用微前端
- 不同团队间开发同一个应用技术栈不同怎么破?
- 希望每个团队都可以独立开发,独立部署怎么破?
- 项目中还需要老的应用代码怎么破?
我们是不是可以将一个应用划分成若干个子应用,将子应用打包成一个个的lib。当路径切换时加载同的子应用。这样每个子应用都是独立的,技术栈也不用做限制了!从而解决了前端协同开发问题
怎样落地微前端
微前端的灵感来源于,计算机上的应用,每一次用户打开一个应用,就相当于打开了一个新的页面
- 2018年 Single-SPA诞生了, single-spa 是一个用于前端微服务化的 JavaScript 前端解决方案 (本身没有处理样式隔离, js 执行隔离) 实现了路由劫持和应用加载
- 2019年 qiankun 基于Single-SPA, 提供了更加开箱即用的 API ( single-spa + sandbox + import-html-entry ) 做到了,技术栈无关、并且接入简单(像iframe 一样简单)
总结:子应用可以独立构建,运行时动态加载,主子应用完全解耦,技术栈无关,靠的是协议接入(子应用必须导出 bootstrap、mount、unmount方法)
这里先回答下大家的问题:
-
这不是iframe吗?
如果使用 iframe , iframe 中的子应用切换路由时用户刷新页面就尴尬了
更多参考 Why Not Iframe
-
应用之间怎么通信
- 基于URL来进行数据传递,但是传递消息能力弱
- 基于 CustomEvent 实现通信
- 基于props主子应用间通信
- 使用全局变量、 Redux 进行通信
-
公共依赖
- CDN - externals
- webpack 联邦模块
微前端架构具备以下几个核心价值:
-
技术栈无关
主框架不限制接入应用的技术栈,微应用具备完全自主权
-
独立开发、独立部署
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
-
增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
-
独立运行时
每个微应用之间状态隔离,运行时状态不共享
二、SingleSpa 实战
Single-Apa完整的项目请参考 gitHub
构建子应用
我们需要父应用加载子应用,需要暴露三个方法:
1. bootstrap
2. mount
3. unmount
1. 构建子应用
// 启动项目安卓依赖 vue create single-child npm i --save single-spa-vue // main.js中导入依赖 import singleSpaVue from 'single-spa-vue' const appOptions = { el: '#vue', // 挂载到父应用中的 id 为 vue 的标签中 router, render: h => h(App) } const vueLifeCycle = singleSpaVue({ // 返回single-spa 的生命周期也就是 bootstrap/mount/unmount Vue, appOptions }); // single规定的协议,父应用会调用这些方法 export const bootstrap = vueLifeCycle.bootstrap; export const mount = vueLifeCycle.mount; export const unmount = vueLifeCycle.unmount; // 这样做还有一个严重的问题,子应用无法启动了??
2. 配置子应用中的打包路径
// 配置vue.config.js module.exports = { configureWebpack: { output: { library: 'singleVue', libraryTarg: 'umd' }, devServer: { port: 10000 } } };
3. 配置子应用的路由
const router = new VueRouter({ mode: 'history', base: '/vue', // 配置路由的基础路径 routes })
4. 父应用搭建
vue create single-parent npm i --save single-spa // 注意这里是single-spa
5. 将子应用挂载到 id="vue"
的容器中
<div id="app"> <!-- 当路由切换到 /vue 时加载子应用 --> <router-link to="/vue">加载vue引用</router-link> <router-view/> <!-- 子应用加载的位置 --> <div id="vue"></div> </div>
6. 配置父应用加载子应用
import { registerApplication, start } from 'single-spa' async 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.log('加载模块'); // 加载子应用中的脚本 await loadScript(`http://localhost:10000/js/chunk-vendors.js`) await loadScript(`http://localhost:10000/js/app.js`) // 这里需要要返回 bootstrap/mount/unmount return window.singleVue }, location => location.pathname.startsWith('/vue'), // 此路径用来判断当前路由切换到 /vue 的路径下,需要加载我们定义的子应用 { a: 1 } // 选传,传给子应用 props 的参数,可以是对象或值 ); start(); // 启动应用
7. 配置子应用的路径
// 设置路径 if (window.singleSpaNavigate) { // 如果是父应用去应用,那会自动挂载一个属性为true __webpack_public_path__ = 'http://localhost:10000/' }
8. 希望子应用可以独立运行,在子应用中添加一个配置
if(!window.singleSpaNavigate){ delete appOptions.el; // 子应用中没有#vue,所以需要手动删除,挂载到 #app 中 new Vue(appOptions).$mount('#app'); }
singleSpa 缺陷
- 不能动态加载JS文件
- 样式不隔离
- 全局对象,没有JS沙箱的机制
三、qiankun 实战
乾坤完整的demo请参考: gitHub
特点
- 简单:任意 js 框架均可使用。微应用接入像使用接入一个 iframe 系统一样简单,但实际不是 iframe。
- 完备:几乎包含所有构建微前端系统时所需要的基本能力,如 样式隔离、js 沙箱、预加载等。
- 生产可用:已在蚂蚁内外经受过足够大量的线上系统的考验及打磨,健壮性值得信赖。
项目构建
1. 主应用搭建 qiankun-base
// 构建项目,下载依赖,只需要在主项目中安装 qiankun 即可 vue create qiankun-base npm i --save qiankun // 配置主项目的加载 main.js 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'; import {registerMicroApps, start} from 'qiankun'; Vue.config.productionTip = false Vue.use(ElementUI); const apps = [ { name: 'vueApp', // 应用的名字 entry: 'http://localhost:10000/', // 默认加载这个html,解析里面的js动态的执行(子应用必须支持跨域,内部使用的是 fetch) container: '#vue', // 要渲染到的容器名id activeRule: '/vue' // 通过哪一个路由来激活 }, { name: 'reactApp', entry: 'http://localhost:20000/', container: '#react', activeRule: '/react' } ]; registerMicroApps(apps); // 注册应用 start(); // 开启应用 new Vue({ router, render: h => h(App) }).$mount('#app')
<!-- 设置容器 --> <template> <div> <el-menu :router="true" mode="horizontal"> <!-- 主应用中也可以放自己的路由 --> <el-menu-item index="/">首页</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 v-show="$route.name"></router-view> <div id="vue"></div> <div id="react"></div> </div> </template>
2. 搭建Vue子项目
vue create qiankun-vue // 子项目中不需要安装任何依赖,父组件会给window设置一些环境变量 // mian.js import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false /* new Vue({ router, render: h => h(App) }).$mount('#app') */ let instance = null; function render(props) { // props 组件通信 instance = new Vue({ router, render: h => h(App) }).$mount('#app') // 这里是挂载到自己的HTML中,基座会拿到这个挂载后的HTML,将其插入进去 } if (!window.__POWERED_BY_QIANKUN__) { // 如果是独立运行,则手动调用渲染 render(); } if(window.__POWERED_BY_QIANKUN__){ // 如果是qiankun使用到了,则会动态注入路径 __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } // 根据 qiankun 的协议需要导出 bootstrap/mount/unmount export async function bootstrap(props) { }; export async function mount(props) { render(props); }; export async function unmount(props) { instance.$destroy(); };
// 设置router路径 const router = new VueRouter({ mode: 'history', base: '/vue', routes })
// 配置打包 vue.config.js module.exports = { devServer: { port: 10000, headers:{ 'Access-Control-Allow-Origin': '*' // 允许跨域 } }, configureWebpack: { output: { library: 'vueApp', libraryTarget: 'umd' } } };
3. 搭建React项目
npx create-react-app qiankun-react npm i --save-dev react-app-rewired // 入口配置 /src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; function render(){ ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); } if(!window.__POWERED_BY_QIANKUN__){ render(); } export async function bootstrap(){ } export async function mount() { render() } export async function unmount(){ ReactDOM.unmountComponentAtNode( document.getElementById('root')); }
// 配置启动 config-overrides.js module.exports = { webpack:(config)=>{ config.output.library = 'reactApp'; config.output.libraryTarget = 'umd'; config.output.publicPath = 'http://localhost:20000/'; return config; }, devServer:(configFunction)=>{ return function (proxy,allowedHost){ const config = configFunction(proxy,allowedHost); config.headers = { "Access-Control-Allow-Origin":'*' } return config } } }
添加react环境变量 .env
PORT=20000 WDS_SOCKET_PORT=20000
// 配置react路由 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 home</h1>}></Route> <Route path="/about" render={() => <h1>hello about</h1>}></Route> </BrowserRouter> ); }
完整的项目请参考 gitHub
以上所述就是小编给大家介绍的《微前端介绍》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 前端框架 Vue.js 全面介绍
- 前端交互式图表绘制库GoJS介绍
- git rebase/submodule/flow介绍 - 前端
- 【前端面试系列1】介绍redux,主要解决什么问题
- 《从零构建前后分离的web项目》:前端了解过关了吗?前端基础架构和硬核介绍
- 透视前端工程化之 Webpack 基本介绍【文末有彩蛋~】
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web Development Recipes
Brian P. Hogan、Chris Warren、Mike Weber、Chris Johnson、Aaron Godin / Pragmatic Bookshelf / 2012-1-22 / USD 35.00
You'll see a full spectrum of cutting-edge web development techniques, from UI and eye candy recipes to solutions for data analysis, testing, and web hosting. Make buttons and content stand out with s......一起来看看 《Web Development Recipes》 这本书的介绍吧!