10分钟弄懂微应用框架 | 您所在的位置:网站首页 › 乾坤谁在上 › 10分钟弄懂微应用框架 |
前言
今天刚刚学习了一个微前端框架——乾坤,正着热乎劲,写一篇入门博客。这篇文章不会讨论太多的原理和实现,只是一个入门写 Hello World 的教程。 文章的很多地方都参考官网,但是官网的教程太简洁了,个人觉得还是做不到无脑上手,希望这篇文章可以帮到正在入门微前端的伙伴。 想直接看代码的,我写了个比官网更简单的例子,点击即可学会。 什么是微前端首先,来了解一下微前端是个啥。 当我们写了一个又一个的 SPA 应用。突然有一天,老板说要将这些应用合并,前端工程们就头大了——每个应用的代码都是一座摇摇欲坠的💩山,别说合并了,就算动都不敢动呀。 虽然很麻烦,但是前端工程师还是把这个问题解决了,而这个将多个 SPA 合并成一个 Web App 的解决方案就是微前端。 为什么要微前端“多个 SPA 合并成一个 Web App?”,可能有人会想到用 也可以实现一个网页里内嵌多个网页呀。原因有: 不感知 url 状态,比如前进、后退没法玩 UI 不同步、DOM 不同享。 本质上是页面的硬隔离,所以如果你有个遮罩层,可能只能在那一小片区域才展示遮罩层 页面之间的通信很麻烦 每次都要加载子应用,速度很慢 而微前端正好可以补足上面的缺点。 微前端的优势除了解决了上面的问题,微前端还有如下的优点: 子应用技术栈无关,即类似上页说的页面硬隔离,但是是以 sandbox 的方式实现的 合并多个子应用,相对地,也可以将大应用拆解成多个应用,实现业务解耦 子应用高度自治,发布、报错、测试流程仅限于子应用,不会受别的业务影响,同时也不影响别的业务 乾坤由来最原始的微前端框架并不是乾坤,而是 single-spa。但是这个框架只提供最基本的功能,而且全是英文,文档写得也很繁琐,应该没人想去看。 阿里的乾坤则是基于 single-spa 开发的又一个微前端框架,提供了更多的功能,也解决一些坑,官网也很简洁。 不过,个人觉得有点太简洁了,写 Hello World 的时候还是遇到一些坑,只能看 Github 的 /examples 目录学习。 主应用 VS 子应用首先,要知道现在项目并不是只有一个了,而是区分出 主应用 和 子应用,关系如下: ![]() 两者区别: 主应用 概念:就是要统治各个子应用的应用,也即合并结果页面 负责子应用的注册、路由分发。可以简单理解为 React.js 和 Vue.js 里的 App 组件,主要做一些初始化、路由注册、全局状态注册、销毁时的动作 子应用 概念:各个 SPA 应用,可以理解为 SPA 里的页面组件 负责暴露一些函数,以此对接主应用,让主应用知道:哦,原来你是子应用,要和我对接。常见的对应函数有:bootstrap, mount, unmount 项目创建乾坤官网最推荐的做法是将主应用和子应用分成两个项目,各自管理。当然,也可以一个项目里分成不同的目录来存放。 ├── main # 主应用 ├── baidu # 子应用 └── taobao # 子应用如果你觉得 官方的例子 太复杂,也可以看我自己建的 qiankun-bigass-app,子应用只有两个用 React.js 的项目。我把很多无关的代码都删了。 实现主应用理清上面的关系后,我们直接干代码,先看主应用。 首先,我们弄一个 .html 文件出来,作为主页面的 HTML 模板: QianKun 淘宝 百度 function push(subapp) { history.pushState(null, subapp, subapp) }然后,使用 Webpack,指定为 template HTML,并配置 dev server,注意一定要配置 headers,不然会有跨域的问题,子应用同理: // webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './index.js', devtool: 'source-map', devServer: { open: true, port: '7099', clientLogLevel: 'warning', disableHostCheck: true, compress: true, headers: { 'Access-Control-Allow-Origin': '*', }, historyApiFallback: true, overlay: { warnings: false, errors: true }, }, output: { publicPath: '/', }, mode: 'development', resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'], }, module: { rules: [ { test: /\.jsx?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: ['@babel/plugin-transform-react-jsx'], }, }, }, { test: /\.(le|c)ss$/, use: ['style-loader', 'css-loader', 'less-loader'], }, ], }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: process.env.MODE === 'multiple' ? './multiple.html' : './index.html', minify: { removeComments: true, collapseWhitespace: true, }, }), ], };入口文件 index.js 就比较重要了,需要完成主应用的很多事情: import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from 'qiankun'; import './index.less'; /** * 主应用 **可以使用任意技术栈** * 以下分别是 React 和 Vue 的示例,可切换尝试 */ import render from './Render'; // import render from './render/VueRender'; /** * Step1 初始化应用(可选) */ render({ loading: true }); const loader = loading => render({ loading }); /** * Step2 注册子应用 */ registerMicroApps( [ { name: 'taobao', entry: '//localhost:7101', container: '#subapp-viewport', loader, activeRule: '/taobao', }, { name: 'baidu', entry: '//localhost:7102', container: '#subapp-viewport', loader, activeRule: '/baidu', }, ], { beforeLoad: [ app => { console.log('[LifeCycle] before load %c%s', 'color: green;', app.name); }, ], 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); }, ], }, ); const { onGlobalStateChange, setGlobalState } = initGlobalState({ user: 'qiankun', }); onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - master]:', value, prev), true); setGlobalState({ ignore: 'master', user: { name: 'master', }, }); /** * Step3 设置默认进入的子应用 */ setDefaultMountApp('/taobao'); /** * Step4 启动应用 */ start(); runAfterFirstMounted(() => { console.log('[MainApp] first app mounted'); });上面主要完成:初始化、注册子应用、设置配置全局状态、设置默认进入子应用、启动应用。 至于初始渲染函数,可以这么写: import React from 'react'; import ReactDOM from 'react-dom'; /** * 渲染子应用 */ function Render(props) { const { loading } = props; return ( {loading && Loading...} ); } export default function render({ loading }) { const container = document.getElementById('subapp-container'); ReactDOM.render(, container); } 实现子应用子应用其实和官网的差不多,这里以 React.js 子应用举例。首先用 create-react-app 来创建子应用: create-react-app baidu在 src 目录下新增 public-path.js: if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }设置 history 模式路由的 base: const RouteExample = () => { return ( Home About ); }; export default function App() { return ( 淘宝Taobao ); }__POWERED_BY_QIANKUN__ 用于判断现在是否作为子应用被访问,其它地方与普通 React.js App 没差别。 去掉一些无用的文件后,在入口配置子应用: function render(props) { const { container } = props; ReactDOM.render(, container ? container.querySelector('#root') : document.querySelector('#root')); } if (!window.__POWERED_BY_QIANKUN__) { render({}); } // 监听全局状态 function storeTest(props) { props.onGlobalStateChange((value, prev) => console.log('淘宝', `[onGlobalStateChange - ${props.name}]:`, value, prev), true); props.setGlobalState({ ignore: props.name, user: { name: props.name, }, }); } export async function bootstrap() { console.log('[淘宝] react app bootstraped'); } export async function mount(props) { console.log('[淘宝] props from main framework', props); storeTest(props); render(props); } export async function unmount(props) { const { container } = props; ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); }注意上面的 bootstrap, mount 和 unmount 一定要 export 出去,不然没人知道这个是子应用。 下一步,是修改 Webpack 的配置。但是 creat-react-app 造出来的 React App 不 eject 出来就改不了,这里官网推荐使用 @rescripts/cli 来修改: yarn add -D @rescript/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; }, };Webpack 配置同样很重要,一个是配置 historyApiFallback 处理单页的 404 问题,另一个是通过 Access-Control-Allow-Origin 解决主应用访问子应用的跨域问题。 在上面的主应用里看到我们是要访问不同的端口的,那端口要怎么配置呢?可以通过 .env 来配置: SKIP_PREFLIGHT_CHECK=true BROWSER=none PORT=7101 WDS_SOCKET_PORT=7102更多框架的配置可见这里。 API 粗讲乾坤的 API 也不是很多,详见这里。简单讲一下用处: API用处类比registerMicroApps主应用用来注册多个子应用的函数类似于 Vue 和 React 的路由start启动主应用类似于 React.js 的 render 函数和 Vue.js 的 new Vue()loadMicroApp手动加载子应用也类似于 React.js 的 render 函数和 Vue.js 的 new Vue(),只不过更自由了prefetchApps预加载子应用类似于 Webpack 的 prefetch 功能addGlobalUncaughtErrorHandler页面报错时可以用于上报和兜底-removeGlobalUncaughtErrorHandler都懂的-initGlobalState初始化全局状态类似于 Redux 的 createStore 和 Vue 的 new Vue.Store() 最后查看:2021年总结记录 关注前端开发博客,在后台回复以下关键字可以获取资源。 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF 回复「Vue脑图」获取 Vue 相关脑图 回复「思维图」获取 JavaScript 相关思维图 回复「简历」获取简历制作建议 回复「简历模板」获取精选的简历模板 回复「加群」进入500人前端精英群 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。 回复「知识点」下载高清JavaScript知识点图谱 点赞和在看就是最大的支持❤️ |
CopyRight 2018-2019 实验室设备网 版权所有 |