微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
一、什么是微前端
“微前端”一词最早在2016年提出,它将后端微服务的概念扩展到前端世界。简单来说,就是将大型的wen渐进式项目拆分成一个个小型工程,即微应用。每个微应用都能独立开发、部署、运行,然后由主应用将所有微应用整合在一起,实现所有页面的展示和交互。
二、为什么用微前端
在开发toB项目尤其是中后台项目时,以往的趋势都是搭建一个功能强大且完善的单页应用程序。但是中后台项目生命周期较长(一般3年+),这期间由于参与的人员、团队的增多、变迁,单页应用就会从一个普通应用演变成一个巨石应用。随之而来就导致项目无法维护尾大不掉。这类问题在企业级 Web 应用中尤其常见。
巨石应用往往有以下几个弊端:
- 项目引用包太多,打包上线巨慢;
- 首屏请求的接口越来越多,页面资源繁杂,导致首屏加载缓慢;
- 项目初期野蛮生长过度,代码难以维护,产品迭代举步维艰;
- toB应用对于客户定制化需求很难满足;
巨石应用发展后期都逃不过重构这一条路,一旦重构导致整个项目功能停止更新迭代,对公司发展来说成本巨大。
微前端架构旨在解决单体应用长时间迭代后,导致不可维护的问题。其具备以下几个核心价值:
- 技术栈无关
- 主框架不限制接入应用的技术栈,微应用具备完全自主权
- 独立开发、独立部署
- 微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
- 增量升级
- 在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
- 独立运行时
- 每个微应用之间状态隔离,运行时状态不共享
这其中的技术难点就是如何将不同技术栈的微应用整合在一起,保持微应用独立运行的同时又能实现跨应用通信。qiankun微前端框架提供了一套简单、无痛的微前端架构解决方案。下面我们一步一步讲解使用qiankun如何从零开始搭建微前端项目。
三、主应用
主应用与技术栈无关,我们可以使用Vue、React、Angular、JQuery甚至ES5语法进行搭建。主应用的目的如下:
- 注册微应用;
- 为每个微应用提供dom容器;
- 启动主应用;
下面我们以vue为主应用一步一步进行讲解,先使用vue-cli3构建项目。
3.1 安装qiankun
$ yarn add qiankun
3.2 注册微应用
在入口文件main.js中添加如下代码:
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun' /** * step1 初始化应用 */ new Vue({ render: h => h(App), }).$mount('#mainapp-container') /** * step2 注册微应用 */ registerMicroApps( [ { name: 'vue-app', entry: '//localhost:8101', container: '#subapp-container', activeRule: '/vue', }, { name: 'react-app', entry: '//localhost:8102', container: '#subapp-container', activeRule: '/react17', }, ], { beforeMount: [ app => { console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name); }, ], afterUnmount: [ app => { console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name); }, ] } ) /** * step3 设置默认进入微应用 */ setDefaultMountApp('/vue') /** * step4 启动 */ start()
注意step1中我们将主应用挂载到自己的dom容器#mainapp-container中,step2中微应用运行的容器#subapp-container我们定义在主应用的根组件App.vue中。
3.3 启动配置
所有的微应用都保存在根目录projects中,之后会讲解如何配置不同技术栈的微应用。等所有微应用都配置好后,就需要一键启动。
$ yarn add npm-run-all
安装npm-run-all支持同时运行多个npm项目,然后修改package.json配置:
"scripts": { "mainapp:install": "npm-run-all --parallel install:*", "mainapp:start": "npm-run-all --parallel start:*", "install:main": "yarn", "start:main": "yarn start", "install:vue": "cd projects/vue && yarn", "start:vue": "cd projects/vue && yarn start", "install:react17": "cd projects/react17 && yarn", "start:react17": "cd projects/react17 && yarn start", "start": "vue-cli-service serve", "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" },
按照下面命令就能启动微前端项目:
$ yarn install
$ yarn mainapp:install
$ yarn mainapp:start
四、微应用
微应用不用额外安装qiankun即可接入主应用,大致分为下面几个步骤:
- 入口js文件平级目录下增加public-path.js文件;
- 入口js文件中引入public-path.js,修改并导出qiankun定义的三个钩子函数:bootstrap、mount、unmount;
- 使用history路由,并且路由base值要和activeRule匹配规则保持一致;
- 修改webpack配置,允许开发环境跨域及umd打包;
下面我们以react17+typescript作为技术栈构建微应用,一步一步进行讲解,首先使用create-react-app构建项目。
$ yarn create react-app react17 --template typescript
4.1 在src下增加public-path.js
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
注意注释掉的那一行代码必须加上,否则项目有eslint报错无法启动。
4.2 修改入口文件
修改src下index.tsx文件
import './public-path'; function render(props: any) { const { container } = props; ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root')); } // 单独启动 if (!(window as any).__POWERED_BY_QIANKUN__) { render({}); } // 修改、导出微应用钩子 export async function bootstrap() { console.log('[react16] react app bootstraped'); } export async function mount(props: any) { console.log('[react16] props from main framework', props); render(props); } export async function unmount(props: any) { const { container } = props; ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); }
4.3 路由配置
在组件内部配置路由时,使用history路由并且设置base值。
import React, { lazy, Suspense } from 'react' import { BrowserRouter as Router, Link, Route, Switch } from 'react-router-dom' const Home = lazy(() => import('../components/Home')) const About = lazy(() => import('../components/About')) const Layout: React.FC = () => { return ( <Router basename={(window as any).__POWERED_BY_QIANKUN__ ? '/react17' : '/'}> <nav> <Link to="/">Home</Link> <Link to="/about">About</Link> </nav> <Suspense fallback={null}> <Switch> <Route path="/" exact component={Home} /> <Route path="/about" component={About} /> </Switch> </Suspense> </Router> ) } export default Layout
4.4 配置webpack
安装插件@rescripts/cli
yarn add @rescripts/cli
在根目录下新增.rescriptsrc.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; }, };
修改package.json启动文件:
- "start": "react-scripts start", + "start": "rescripts start", - "build": "react-scripts build", + "build": "rescripts build", - "test": "react-scripts test", + "test": "rescripts test", - "eject": "react-scripts eject"
4.5 启动端口配置
根目录下增加.env配置端口信息,保证微应用启动端口和主应用中注册的该微应用的入口entry保持一致。
SKIP_PREFLIGHT_CHECK=true BROWSER=none PORT=8102 WDS_SOCKET_PORT=8102
到这里以React作为技术栈的微应用就配置成功了。Vue、Angular所需要的配置都一样,实现起来大同小异。
完整微前端项目代码在github上。
五、后记
当然一个优秀的项目最终是要服务于产品,为客户提供便利为公司带来价值,不能为了炫技而本末倒置。微前端项目启动初期,需要和后端协商好如何依据业务模块进行拆分确定接口url,后期上线部署也需要运维支持。这样才能保证项目能健壮发展下去。
系列相关文章: