路由配置
路由模式为 hash
模式,自定义路由配置:
<Router> <Switch> <Route path="/" exact> <Home /> </Route> <Route path="/about" exact> <About /> </Route> </Switch> </Router> 复制代码
打包配置
对于 React
应用来讲,要么是一开始就是自定义 webpack
相关配置,要么是基于 crate-react-app
内置的 webpack
配置进行修改,由于上面是通过 crate-react-app
的方式创建的项目,因此可以基于其内置的配置文件进行修改:
不想通过
eject
的方式去修改配置文件,可通过react-app-rewired
进行重写
- 执行
npm run eject
将内置的webpack
配置像暴露出来,会生成一个scripts
目录 和config
目录
- 这里只需要关注
config
目录即可,因为webpack.config.js
在该目录下 - 为其中的
output
配置添加上如下两行配置项即可(需重新启动)
output: { library: 'singleReact', libraryTarget: 'umd', globalObject: 'window', .... } 复制代码
创建基座应用
基座应用的技术栈这里选择 Vue2
,同样可以通过 vue create vue2-main-app
的方式创建对应的基座应用:
路由配置
为了让 基座应用 的路由看起来更美观,这里选择 history
模式(也可选 hash
模式),接着配置具体路由:
- 基座应用的路由
- 配置对应的路由路径
- 指定对应组件作为路由渲染视图,如
HomeView
组件
- 微应用的路由
- 只需要设定对应的路由路径,不需要指定对应的具体组件
import Vue from 'vue' import VueRouter, { RouteConfig } from 'vue-router' import HomeView from '../views/HomeView.vue' Vue.use(VueRouter) const routes: Array<RouteConfig> = [ { path: '/', name: 'home', component: HomeView }, { path: '/vue3-micro-app', name: 'about', }, { path: '/react-micro-app', name: 'about', } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router 复制代码
子应用挂载容器
基座应用中的 <router-view />
是为了渲染基座应用自身的路由视图,而子应用是不能通过 <router-view />
来渲染,因此,我们需要在基座应用中设定一个 Dom
节点专门用于渲染子应用的视图,这里就将子应用的内容挂载在 <div id="micro-content"></div>
的节点中:
<!-- 主内容 --> <main class="content"> <!-- 基座应用路由视图渲染 --> <router-view /> <!-- 子应用挂载容器 --> <div id="micro-content"></div> </main> 复制代码
菜单配置
菜单配置实际就是指定路由的跳转,具体包含内容如下:
Home
菜单渲染的视图内容是 基座应用 中对应的HomeView
组件Vue3-micro-app
菜单渲染视图是名为vue3-micro-app
的 子应用Home
菜单渲染视图是基名为react-micro-app
的 子应用
基座应用注册子应用
在 基座应用 中尚未注册 子应用 时的页面效果如下:
为了切换菜单路由时,对应的子应用能够被正确的渲染在基座应用中,需要我们在基座应用中注册子应用:
- 通过
pnpm install single-spa -S
安装single-spa
- 通过
single-spa
中提供的registerApplication()
和start()
函数完成注册和启动,该逻辑可抽离到registerApplication.ts
中
// registerApplication.ts import { registerApplication, start } from 'single-spa'; // 子应用 export const applications = [{ name: 'singleVue3', async activeWhen() { await loadScript('http://localhost:5000/js/chunk-vendors.js'); await loadScript('http://localhost:5000/js/app.js'); return window.singleVue3 }, app(location: Location) { return location.pathname.startsWith('/vue3-micro-app') }, customProps: { container: '#micro-content' } }, { name: 'singleReact', async activeWhen() { await loadScript('http://localhost:3000/static/js/main.js'); return window.singleReact }, app(location: Location) { return location.pathname.startsWith('/react-micro-app') }, customProps: { container: '#micro-content' } }] // 加载子应用 script export const loadScript = async (url: string) => { await new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.onload = resolve; script.onerror = reject; document.head.appendChild(script) }); } // 注册子应用 export const registerApps = (apps: any[] = applications) => { apps.forEach(({ name, activeWhen, app, customProps = {} }) => registerApplication(name, activeWhen, app, customProps) ); start(); } 复制代码
- 在
main.ts
中导入并执行registerApplication()
即可
效果预览
其中包含了 子应用 在基座应用中 特定位置的渲染,以及 子应用 自身路由的切换时的效果,可以看出子应用路由和主应用路由互不影响。
qiankun 实践
有了前面 single-spa
的基础,通过 qiankun
来实现微前端更加简单了,因为 qiankun
本身就是基于 single-spa
实现的微前端架构系统,目的是提供更简单、简洁的方式接入。
下面我们还是使用上述的三个项目来通过 qiankun
的形式来实现微前端。
配置基座应用
在 vue2-main-app
的入口文件 registerApplication.ts
中使用 qiankun
进行简单配置即可,相比于上面 single-spa
的方式来说更简单:
// registerApplication.ts import { registerMicroApps, start } from 'qiankun'; // 默认子应用 export const applications = [ { name: 'singleVue3', // app name registered entry: 'http://localhost:5000', container: '#micro-content', activeRule: '/vue3-micro-app', }, { name: 'singleReact', // app name registered entry: 'http://localhost:3000', container: '#micro-content', activeRule: '/react-micro-app', }, ] // 注册子应用 export const registerApps = (apps: any[] = applications) => { registerMicroApps(applications); start(); } 复制代码
配置子应用
子应用 部分该导出的生命周期还是要导出,值得注意的是生命周期中的 container
已经是对应基座应用中的 真实 DOM
节点,而不是 CSS
选择器,因此只需要进行简单的修改即可,具体如下所示:
vue3-micro-app 子应用
// src/main.ts import { createApp } from 'vue' import type { App as AppType } from 'vue' import App from './App.vue' import router from './router' let instance: AppType function render(container?: string) { instance = createApp(App) // 这里的 container 已经是对应基座应用中的真实 DOM 节点,而不是 CSS 选择器 instance.use(router).mount(container || '#micro-vue-app') } // 当 window.singleVue3 不存在时,意味着是子应用单独运行 if (!window.singleVue3) { render(); } // 子应用必须导出 以下生命周期 bootstrap、mount、unmount export const bootstrap = () => { return Promise.resolve() }; export const mount = (props: any) => { render(props.container); return Promise.resolve() }; export const unmount = () => { instance.unmount(); return Promise.resolve() }; 复制代码
react-micro-app 子应用
// src/index.js import React from 'react' import ReactDOM from 'react-dom/client' import './index.css' import App from './App' let root = null function render(props = {}) { // 这里的 container 已经是对应基座应用中的真实 DOM 节点,而不是 CSS 选择器 const container = props.container || document.getElementById('root') if(!container) return root = ReactDOM.createRoot(container) root.render( <React.StrictMode> <App {...props} /> </React.StrictMode>, ) } // 当 window.singleReact 不存在时,意味着是子应用单独运行 if (!window.singleReact) { render() } // 子应用必须导出 以下生命周期 bootstrap、mount、unmount export const bootstrap = () => { return Promise.resolve() } export const mount = (props) => { render(props) return Promise.resolve() } export const unmount = () => { root.unmount() return Promise.resolve() } 复制代码
子应用配置 CORS
前面说过基座应用是需要将子应用的入口文件加载到当前应用下来执行的,这个过程第一步就是请求对应的入口文件,由于浏览器 同源策略 的限制,我们必须要在子应用中配置当前子应用的资源是允许被跨域请求的。
子应用没有配置 CORS 发生跨域
vue3-micro-app 配置 CORS
在 vue.config.js
中配置 devServer
既可,其中的 devServer
可以配置所有符合 webpack-dev-server
的选项:
module.exports = { publicPath: '//localhost:5000', configureWebpack: { output: { library: 'singleVue3', libraryTarget: 'umd', globalObject: 'window', }, devServer: { port: 5000, headers: { 'Access-Control-Allow-Origin': '*', }, }, }, } 复制代码
react-micro-app 配置 CORS
因为之前是通过 npm run eject
的方式暴露出来和 webpack
相关的配置,在查看对应的 config\webpackDevServer.config.js
配置发现其内部已经默认做了 CORS
配置
最后
经过以上的实践,下面简单地对微前端框架核心内容进行自己的理解:
- 技术栈无关
- 任何一个子应用不论使用什么技术栈,最终都会被编译为
JavaScript
代码,因此真正在执行时无论基座应用还是子应用都已经是同一种语言形式
- 独立开发、独立部署
- 子应用本质上就是普遍使用的
spa
单页面应用,因此当然可以拥有独立代码仓库进行关联,可独立发布运行,也可作为子应用运行,只需要做好不同环境的兼容即可
- 增量升级
- 子应用能够独立开发部署,自然支持自身应用的功能的独立扩展,又或者是接入新的子应用
- 独立运行时
- 保证多个子应用在基座应用中运行时,自身的状态不受其他子应用的影响
本篇文章就这里就结束了,下一篇文章再去聊聊微前端的实现原理,以及通过自己实现一个微前端的方式加深理解。
希望本篇文章能对你有所帮助!!!