微前端介绍
什么是微前端?
微前端是一种多个团队通过独立发布功能的方式,来共同构建现代化 web 应用的技术手段及方法策略。
不同于单纯的前端框架/工具,微前端是一套架构体系,这个概念最早在2016年底由 ThoughtWorks 提出。 微前端是一种类似于后端微服务的架构,它将微服务的理念应用于浏览器端,将 Web 应用从整个的「单体应用」转变为多个小型前端应用的「聚合体」。
各个前端应用「原子化」,可以独立运行、开发、部署,从而满足业务的快速变化,以及分布式、多团队并行开发的需求。
微前端的特点
- 技术栈无关 主框架不限制接入应用的技术栈,子应用可自主选择技术栈
- 独立开发/部署 各个团队之间仓库独立,单独部署,互不依赖
- 增量升级 当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性
- 独立运行时 微应用之间运行时互不依赖,有独立的状态管理
- 提升效率 应用越庞大,越难以维护,协作效率越低下。微应用可以很好拆分,提升效率
解决问题的理念有了,那要通过怎样的技术去实现呢?
- iframe 最早也是最熟悉的解决方案就是通过iframe,因为它可以独立运行另一个项目,这种方案的优势:
- 非常简单,无需任何改造
- 完美隔离,JS、CSS 都是独立的运行环境
- 不限制使用,页面上可以放多个
iframe
来组合业务
当然也是逃不过事务的两面性,有优点就有缺点:
- 无法保持路由状态,刷新后路由状态就丢失(这点也不是完全不能解决,可以讲路由作为参数拼接在链接后,刷新时去参数进行页面跳转)
- 完全的隔离导致与子应用的交互变得极其困难
iframe
中的弹窗无法突破其本身
- 整个应用全量资源加载,加载太慢
既然有这么明显的问题,那就会有新的方案被创造出来
- 基于
single-spa
路由劫持方案
single-spa
通过劫持路由的方式来做子应用之间的切换,但接入方式需要融合自身的路由,有一定的局限性。
qiankun
孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台。它对 single-spa
做了一层封装。主要解决了 single-spa
的一些痛点和不足。通过 import-html-entry
包解析 HTML
获取资源路径,然后对资源进行解析、加载。
通过对执行环境的修改,它实现了 JS 沙箱
、样式隔离
等特性。
接下来我就好好的讲讲qiankun
是怎么落地到生产项目的,中间遇到过哪些坑。
微前端是一种设计模式,旨在将大型前端应用拆分为更小、更独立、可重用的部分,这些部分可以独立开发、测试和部署。主要的微前端实现方式包括使用iframe
、微前端框架(如qiankun
)等。下面将详细解释这些方法以及它们的区别和样式隔离的实现方式。
1. 使用 iframe
的微前端
iframe
是一种传统的方法,它可以将外部页面嵌入到当前页面中。每个iframe
是一个独立的文档,具有自己的全局作用域和样式。
优点:
- 隔离性强:
iframe
自然隔离了JS和CSS,不会互相影响。
- 技术栈无关:可以加载任何URL,不受技术栈限制。
缺点:
- 性能问题:
iframe
加载需要额外的资源和时间,可能影响性能。
- 交互复杂:父页面与
iframe
间的交互较为复杂,需要通过postMessage
等API。
2. 使用 qiankun
的微前端
qiankun
是基于single-spa
的微前端实现库。它利用JavaScript的运行时隔离,通过Proxy
来隔离全局变量,同时支持资源、样式的隔离和沙箱。
优点:
- 更好的性能:与
iframe
相比,加载速度更快,资源共享更高效。
- 易于交互:子应用可以轻松集成到主应用,组件和数据流通更为自然。
- 样式隔离:
qiankun
为每个子应用动态添加和移除样式标签,避免样式冲突。
缺点:
- 实现复杂度:需要对子应用的打包输出和运行时环境进行控制。
- 技术栈限制:虽然理论上支持所有前端框架,但实际集成时可能需要解决框架兼容问题。
样式隔离实现
iframe
: 自然提供完全的样式隔离。
qiankun
: 使用动态样式表,当子应用激活时添加,卸载时移除。另外,可以通过CSS命名空间(如BEM、CSS Modules等)强化样式隔离。
其他微前端实现方式
- **
single-spa
**:一个前端微服务解决方案,允许你将多个单页应用组合成一个整体。
- Web Components:利用原生Web组件实现微前端,每个组件封装自己的功能和样式。
- Module Federation(Webpack 5 新特性):允许从一个应用动态加载另一个应用的代码。非常适合微前端架构,支持不同团队独立部署和更新应用。
这些实现方式各有优缺点,选择合适的实现方案需要根据项目的具体需求、团队的技术栈以及维护策略来决定。微前端的目标是提升大型应用的可维护性、灵活性和可扩展性,选择适合自己团队和项目的实现方式至关重要。
qiankun
qiankun官网 qiankun.umijs.org/zh/guide
按照官方文档快速搞起来
主应用
- 安装
- 在主应用注册微应用 main.js
放在main.js的尾部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { registerMicroApps, start } from 'qiankun';
registerMicroApps([ { name: 'vue app', entry: '//localhost:7100', container: '#container-vue', activeRule: '/micro-vue', props: { routerBase: '/micro-vue', } }, { name: 'micro-clouds', entry: '//localhost:7000', activeRule: '/micro-clouds', container: '#subapp2', props: { routerBase: '/micro-clouds', } } ]);
start();
|
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
如果微应用不是直接跟路由关联的时候,你也可以选择手动加载微应用的方式:
1 2 3 4 5 6 7 8 9 10 11 12
| import { loadMicroApp } from 'qiankun';
loadMicroApp({ name: 'micro-clouds', entry: '//localhost:7000', activeRule: '/micro-clouds', container: '#subapp2', props: { routerBase: '/micro-clouds', } });
|
微应用
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import './public-path'; import { createApp } from 'vue' import App from '@/App.vue' import { createRouter, createWebHistory } from 'vue-router'; import routes from '@/router' import store from '@/store'
let instance = null async function render(props = {}) { const { container, routerBase } = props; instance = createApp(App); const router = createRouter({ history: createWebHistory(`${routerBase}`), routes }); instance.use(router); instance.use(store);
instance.mount(container ? container.querySelector("#app") : "#app"); }
if (!window.__POWERED_BY_QIANKUN__) { render({ container: "", routerBase: "/micro-clouds" }); }
export async function bootstrap() { console.log('[vue] vue app bootstraped'); }
export async function mount(props) { await render(props); }
export async function unmount() { instance.unmount(); }
|
public-path.js
1 2 3 4 5 6 7 8
| (function () { if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ } })()
|
到这里我们就把主应用和微应用融合起来了,涉及的代码量也就百八十行,至于跳转可以自己在主应用加几个按钮通过history.pushState(null, item.activeRule, item.activeRule)
![image.png](/../imgs/%E5%BE%AE%E5%89%8D%E7%AB%AF/d72b7c899e104645a0f372007e60cf92tplv-k3u1fbpfcp-zoom-in-crop-mark3024000.webp)
这个是我当时模仿的demo: github.com/fengxianqi/…
其实跑通以上demo还是挺简单的,我一开始也是看着qiankun官网,在网上找了个demo比划比划,很简单嘛!如果我这边文章只分享到这里那完全没有必要去分享了,往后看才是实际生产项目的搭法。
qiankun + vue3 搭建生产项目
通过上面的demo例子抛转引玉,应该对微前端是怎么跑起来的有了大致的认识。我当时也以为搞定了上面的demo就可以直接用在生产项目了。没料到实际用起来和demo完全是两回事,下面请听我一一解析这当中遇到的坑。
1. 路由
由于demo项目没有用vue-router
,直接是通过history.pushState
跳转,一切看来都是那么的没有,没有告警没有报红。但在实际的vue项目中,路由的改变,vue-router
是会自动去匹配路由的,当匹配到子路由在当前router中没有配置时就会报警报红,如果直接跳转又没法运用到vue-router
中的能力,怎么融合路由才能达到要求呢?
答案是通过一个component
组件来承载,它可以使qiankun
的代码与基座项目极大的降低耦合,代码可维护性增强,最重要的是可以统一将微应用路由归并到这个组件路由来,相当于接入微前端就像增加了一个普通组件这么简单。
主应用配置
1 2 3 4 5 6 7 8
| src/router/index.js { path: '/:micro(micro-vue|micro-clouds):endPath(.*)', name: 'MicroApp', meta: { title: '微前端应用' }, component: () => import(/* webpackChunkName: "qiankun" */'@/views/qiankun/MicroApp.vue') },
|
通过正则匹配路由,只要是微应用的路由都可以匹配进来,当然需要事先定义好微应用的路由前缀,比如micro-xxx
,增加微应用就在这里(micro-vue|micro-clouds)
加一个匹配前缀。
新建MicroApp.vue
组件
src/views/qiankun/MicroApp.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
| <template> <div class="layout-container micro-container" v-loading="$store.state.app.isLoadingMicro" element-loading-text="Loading..."> <div id="subapp1"></div> <div id="subapp2"></div> </div> </template>
<script> import { onMounted, watch, reactive, onUnmounted } from 'vue' import { loadMicroApp, addGlobalUncaughtErrorHandler } from 'qiankun' import { useRoute } from 'vue-router' import { microApps, registerApps } from '@/views/qiankun/micro-app'
export default { name: 'MicroApp', setup() { const microList = reactive({}) const route = useRoute() const state = reactive({ elementLink: null }); const activationHandleChange = async (path) => { const activeRules = microApps.map((app) => app.activeRule) const isMicro = activeRules.some((rule) => path.startsWith(rule)) if (!isMicro) return const microItem = microApps.find((app) => path.startsWith(app.activeRule.toString())) if (!microItem) return const current = microList[microItem.activeRule.toString()] if (current) return
const micro = loadMicroApp({ ...microItem }) microList[microItem.activeRule.toString()] = micro try { await micro.mountPromise } catch (e) { console.error('=======', e) } } addGlobalUncaughtErrorHandler((event) => console.log(event)) watch(() => route.path, async (newValue) => { activationHandleChange(newValue) }) onMounted(async () => { if (window.qiankunStarted) return window.qiankunStarted = true registerApps() activationHandleChange(route.path) }) onUnmounted(() => { window.qiankunStarted = false Object.values(microList).forEach((mic) => { mic.unmount() }) }) return { } } } </script> <style lang="scss" scoped> .micro-container{ background: var(--system-container-main-background); } </style>
src/views/qiankun/micro-app.js import { registerMicroApps, start } from 'qiankun' import store from '@/store' import utils from '@/assets/js/utils';
export const microApps = [ { name: 'micro-clouds', entry: process.env.VUE_APP_CLOUDS, activeRule: '/micro-clouds', container: '#subapp2', props: { routerBase: '/micro-clouds', mainStore: store, user: utils.getStorage('user') } } ]
export const registerApps = () => { registerMicroApps(microApps, { beforeLoad: (app) => { store.commit('app/loadingMicro', true) console.log('before load app.name====>>>>>', app.name) }, beforeMount: [ (app) => { console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name) } ], afterMount: [ (app) => { store.commit('app/loadingMicro', false) console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name) } ], afterUnmount: [ (app) => { console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name) } ] })
start({ sandbox: { prefetch: 'all', strictStyleIsolation: true, experimentalStyleIsolation: true } }) }
|
微应用配置
mian.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| import './public-path'; import { createApp } from 'vue' import ElementPlus from 'element-plus' import NProgress from '@/assets/js/nprogress'; import * as ELIcons from '@element-plus/icons-vue' import zhCn from 'element-plus/es/locale/lang/zh-cn' import draggable from 'vuedraggable' import 'element-plus/dist/index.css' import 'normalize.css' import '@/assets/style/common.scss' import '@/assets/style/iconfont.css' import App from '@/App.vue' import { createRouter, createWebHistory } from 'vue-router'; import routes from '@/router' import store from '@/store'
let instance = null async function render(props = {}) { const { container, routerBase } = props; instance = createApp(App); const router = createRouter({ history: createWebHistory(`${routerBase}`), routes }); instance.use(router); instance.use(ElementPlus, { locale: zhCn }); instance.use(store); instance.component('draggable', draggable) for (const [key, component] of Object.entries(ELIcons)) { instance.component(key, component); }
instance.mount(container ? container.querySelector("#app") : "#app");
if (window.__POWERED_BY_QIANKUN__) { router.afterEach((to) => { const matched = to.matched.map((item) => { return { ...item, path: `${routerBase}${item.path}`, redirect: `${routerBase}${item.path}`, }; }) props.mainStore.dispatch("app/getMicroBreadcrumb", [ ...matched ]); }); }
router.beforeEach(async (to, from, next) => { NProgress.start(); if (store.getters["user/isLogin"]) { next(); } else if (store.state.app.whiteList.includes(to.path)) { next(); } else { next("/login"); } });
router.afterEach((to) => { const keepAliveComponentsName = store.getters["keepAlive/keepAliveComponentsName"] || []; const { name } = to.matched[to.matched.length - 1].components.default; if (to.meta && to.meta.cache && name && !keepAliveComponentsName.includes(name)) { store.commit("keepAlive/addKeepAliveComponentsName", name); } NProgress.done(); }); }
if (!window.__POWERED_BY_QIANKUN__) { render({ container: "", routerBase: "/micro-clouds" }); }
export async function bootstrap() { console.log('[vue] vue app bootstraped'); }
export async function mount(props) { store.commit("app/microChange", true); await render(props); }
export async function unmount() { instance.unmount(); }
public-path.js (function () { if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } }());
vue.config.js const path = require('path')
module.exports = { publicPath: process.env.NODE_ENV === 'development' ? '/' : '/micro-clouds', configureWebpack: { output: { library: 'micro-clouds-[name]', libraryTarget: 'umd', jsonpFunction: 'webpackJsonp_[name]' }, }, devServer: { port: 4003, disableHostCheck: true, headers: { 'Access-Control-Allow-Origin': '*' }, proxy: { '/api': { target: process.env.VUE_APP_TEST, changeOrigin: true, pathRewrite: { '^/api': '' } } } } }
|
到这为止,一个完整的微前端架构就搭建好了,下面来介绍各个细节的交互是怎么处理的。
2. 父子应用耦合以及单独使用
简单讲就是为了满足更多更灵活的使用要求,咱们设计要能满足主应用与微应用耦合在一起使用,也可以子应用单独使用,不仅满足了用户的需求,还可以方便开发人员调试。(联调子应用时子应用一定是能独立启动的,并且有自己的登录、菜单功能,说白了就是一个完整的系统)
为了区分微前端运行还是单独运行,我们需要在子应用中插入微前端标识。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| # 单独启动子应用时会通过这里来启动项目
if (!window.__POWERED_BY_QIANKUN__) { render({ container: "", routerBase: "/micro-clouds" }); }
# 这是子应用中微前端的钩子函数,如果是通过微前端启动,就通过这里来启动项目
export async function mount(props) { store.commit("app/microChange", true); await render(props); }
|
区分好了之后,就需要将子应用中的菜单、头部状态栏隐藏,只留下内容区,连内容区周围的间隙都要去掉,因为主应用内容区也有间隙,避免样式冲突。
![截屏2022-06-29 下午12.19.13.png](/../imgs/%E5%BE%AE%E5%89%8D%E7%AB%AF/cd0fda56a473404986f7e1de0cc0e7f3tplv-k3u1fbpfcp-zoom-in-crop-mark3024000.webp)
3. 父子应用通信
原则上应该尽可能得降低父子应用之间的耦合,以免子应用单独使用时无法正常运行,也会增加维护成本。但是像登录信息的传递是有必要的,父应用登录了将登录信息传到子应用,避免子应用检测到无登录状态跳转到登录页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| # 主应用 export const microApps = [ { name: 'micro-clouds', entry: process.env.VUE_APP_CLOUDS, activeRule: '/micro-clouds', container: '#subapp2', props: { routerBase: '/micro-clouds', mainStore: store, user: utils.getStorage('user') } } ]
# 子应用接受父应用传过来的登录信息并写入storage中 export async function mount(props) { utils.setStorage("user", props.user); store.commit("app/microChange", true); await render(props); }
|
4. 面包屑怎么融合父子应用中的路由
现在的后台应用基本都有面包屑要么就是有一个tabs标签栏,在非微前端应用中,是一个非常简单的问题,直接to.matched
就能查找出当前的路由嵌套关系,面包屑遍历出来就OK。但是在微前端中怎么知道子应用中的路由嵌套关系,那么就涉及父子应用的通信,当子应用监听到路由变化时上报子应用的to.match
给父应用。由于这里涉及到主子应用的to.match
切换,当打开的是主应用的页面就用主应用的to.match
,打开的是子应用的页面就用子应用的to.match
,所以我这里统一用vuex
来处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| # 主应用vuex const actions = { getBreadcrumb({ commit }, matched) { const levelList = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false); commit('setBreadcrumb', JSON.parse(JSON.stringify(levelList))); }, getMicroBreadcrumb({ commit }, matchedList) { const microLevel = matchedList.filter((ele) => ele.meta && ele.meta.title && ele.meta.breadcrumb !== false);
commit('setMicroBreadcrumb', JSON.parse(JSON.stringify(microLevel))); } }
# 主应用面包屑组件中 const getBreadcrumb = async () => { if (route.path.startsWith('/micro')) { store.subscribe((mutation, state) => { if (mutation.type === 'app/setMicroBreadcrumb') { levelList.value = store.state.app.microBreadcrumbs } }) } else { await store.dispatch('app/getBreadcrumb', route.matched) levelList.value = store.state.app.breadcrumbs } }
# 子应用上报to.match给父应用 # 子应用main.js
# 好好回想一下,在父应用中传递给子应用的props中就有一个`mainStore`,这个就是父应用的store,可以直接在子应用中使用`props.mainStore.dispatch("app/getMicroBreadcrumb", [ ...matched ]);`
function render(props = {}) { ... if (window.__POWERED_BY_QIANKUN__) { router.afterEach((to) => { const matched = to.matched.map((item) => { return { ...item, path: `${routerBase}${item.path}`, redirect: `${routerBase}${item.path}`, }; }) props.mainStore.dispatch("app/getMicroBreadcrumb", [ ...matched ]); }); } ... }
|
到这里基本上配置完成了,后面是一些遇到的样式bug,一定要实践,光看可能效果不大
5. 父子应用中样式冲突的问题
![截屏2022-06-29 下午2.45.44.png](/../imgs/%E5%BE%AE%E5%89%8D%E7%AB%AF/523aefbbc2eb4185abacac1284dd32d3tplv-k3u1fbpfcp-zoom-in-crop-mark3024000.webp)
当在父应用打开子应用时出现样式问题,正常在vue + element-plus
的项目中,是这么引组件的样式
通过审查元素发现这个弹窗的dom节点的class样式少了一些属性,也就是光有class少了样式值,所以导致样式问题。目测是import引入样式的没法作用到子应用节点,改成在public/index.html
通过link的方式引用
![截屏2022-06-29 下午2.58.35.png](/../imgs/%E5%BE%AE%E5%89%8D%E7%AB%AF/231d5ee6b248446d80b01d06621c2bbetplv-k3u1fbpfcp-zoom-in-crop-mark3024000.webp)
成功解决样式问题,需要注意的是当遇到样式问题时可以从 引入方式、append-to-body属性(element-plus组件)着手排查。
配置完整个微前端架构,看似百八十行代码,其实遇到的问题还是蛮多的,跟撸个demo完全是两回事,网上可查的资料又少,官方文档只是介绍了那几个api的使用,干完这票确实提升不少,值得去尝试,我认为这是构架师必备技能。
6. qiankun加载子应用的协议会转成http的问题
前面由于都是在本地以及测试环境验证过,因为都是http协议所以没有暴露出这个问题。当到沙盒以及生产环境时,子应用页面加载不出来,我也没有改动代码,测试和生产是同一份代码,一直报这个错误
![截屏2022-07-19 下午6.18.03.png](/../imgs/%E5%BE%AE%E5%89%8D%E7%AB%AF/5fa57a448c1f4606af23b4514cecdee8tplv-k3u1fbpfcp-zoom-in-crop-mark3024000.webp)
也就是主应用是 https
协议,子应用是http
协议,导致了这个问题。现在问题清楚了,又有另外一个疑问,我的主应用指向子应用的路由是 https
啊,为什么被改成了 http
,这个问题我不太清楚是不是qiankun
这家伙干的,官方也没说。
解决办法:
public/index.html 头部加上这个
1
| <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
|
如果是统一加上这个,测试环境和本地环境又不行了,得区分开发,怎么办?
我们都知道,打包后生成的index.html
是由 html-webpack-plugin
插件生成的,沿着这条线索找,插件文档果然有meta
这个配置项github.com/jantimon/ht…
那么在vue-cli
生成的项目中没有单独使用html-webpack-plugin
,而是在 vue.config.js
里的pages入口这里配置,具体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| pages: { index: { entry: 'src/main.js', template: 'public/index.html', filename: 'index.html', title: '采贝中台', meta: process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test' ? {} : { 'Content-Security-Policy': { 'http-equiv': 'Content-Security-Policy', content: 'upgrade-insecure-requests' } } } },
|
完美解决!
项目部署
部署方式很多种,我这里只介绍我在项目中的实践。
![image.png](/../imgs/%E5%BE%AE%E5%89%8D%E7%AB%AF/3e9100e7d27846afbb68b7525f1cd979tplv-k3u1fbpfcp-zoom-in-crop-mark3024000.webp)
主应用:main.xxx.com/home
子应用A: clouds.xxx.com/projectA/ho…
子应用B: clouds.xxx.com/projectB/ho…
子应用C: clouds.xxx.com/projectC/ho…
现在的部署都流行CICD,docker + k8s,所以我采用的是每个项目都单独部署一个docker,最后通过 域名 + 项目前缀
进入项目,nginx配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }
location /micro-projectA { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
alias /usr/share/nginx/html; try_files $uri $uri/ /micro-projectA/index.html; }
location / { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
root /usr/share/nginx/html; try_files $uri $uri/ /index.html; }
|