微前端学习分享1 通过single-spa 实现vue 微服务

single-spa 实现技术特点

1.项目独立部署 

2.兼容技术栈不同多语言 vue react 

3.新旧代码版本并行

single-SPA

实现原理 :路有劫持 和应用加载 

没有处理css样式和js 会导致样式重叠 和脏数据

下一期 会出qiankun的教程 其实乾坤是基于single-SPA进一步完善的前端微服务架构 使用也很简单 开箱即用的API (single-spa+sandbox+import-html-entry)父子应用协议接入(bootstrap mount unmount)

ifream 嵌套 其实也可以实现前端 的 微服务逻辑 但是最终还是被废弃掉了因为是有很多很多本质上的问题 比如

页面刷新状态丢失、url通信方式传递消息功能性较弱等问题

其实通信方式有很多比如:

利用浏览器默认方法 customevent

基于props 主子应用通信

全局变量通信 redux

CDN-externals

webpack 联邦模块等等  闲话少说  接下来我们开始我们的微前端的single-spa之旅

开始

创建父子应用parent-vue 和child-vue (最简单的包含router的 vue应用即可 )

父子应用分别安装 single-spa-vue

child-vue main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import singleSpaVue from 'single-spa-vue'

Vue.config.productionTip = false

const appOptions = {
  el: '#vue',
  router,
  render: h => h(App)
}
const vueLifeCycle = singleSpaVue({
  Vue,
  appOptions
})
// 判断是否是父应用引用我
if (window.singleSpaNavigate) {
  __webpack_public_path__ = 'http://localhost:10001/'
}
if (!window.singleSpaNavigate) {
  delete appOptions.el
  new Vue(appOptions).$mount('#app')
}

// 协议接入 我订好了协议 父应用会调用这些方法

export const bootstrap = vueLifeCycle.bootstrap
export const mount = vueLifeCycle.mount
export const unmount = vueLifeCycle.unmount

// new Vue({
//   router,
//   render: h => h(App)
// }).$mount('#app')

// 父应用加载子应用 将子应用打包成一个个lib 去给父应用使用
// 没有new vue 无法启动 所以 需要新建一个 vue.config.js 配置启动
// bootstrap  mount unmount
// single-spa  /single-spa-vue  single-spa-react

vue.config.js

module.exports = {
  configureWebpack: {
    output: {
      library: 'singleVue',
      libraryTarget: 'umd'
    },
    devServer: {
      port: 10000
    }
  }
}

讲解一下这里的配置是为了打包成umd模块  做过vue 组件开发的小伙伴应该熟悉 最后最后会把注册的几个bootstrap mount unmout 挂载在到window.singleVue下

parent-vue main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { registerApplication, start } from 'single-spa'

Vue.config.productionTip = false
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)
  })

}
// singlespa 缺陷 不够灵活 不能动态加载js文件 
// 样式不隔离 没有js沙箱的机制



registerApplication('myVueApp',
  async () => {
    console.log('加载子模块')
    await loadScript('http://localhost:10001/js/chunk-vendors.js')
    await loadScript('http://localhost:10001/js/app.js')
    return window.singleVue

  },
  location => location.pathname.startsWith('/vue'), // 检测用户切换到 /vue  路径下 需要实现刚刚定义的加载事件
  { //可以是方法或者是函数  传递给子应用实现父子的通信 mount unmout方法  CustomEvent 全局  url
    a: '1',
    b: '2'
  }
)
start()

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

这样基本上所有的业务逻辑都在这里了 接下里看一下效果

上面说到的样式问题通过一些技术方案都是可以解决的例如 脏数据

如果 应用加载 刚开始我加载A应用 window.a B应用window.b

但应用切换 沙箱 创造一个干净的 环境给这个子应用 使用 当切换时 可以丢弃 属性和恢复属性

JS沙箱  proxy

快照沙箱 一年前拍一张照 现在再拍一张  (区别保存起来) 在回到一年前    ?进行差异对比? 不确定恢复

实现案例

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
	</head>
	<body>
		<p>hello  world</p>
		<div id="shadow"></div>
		<script>
			/* 
			如果 应用加载 刚开始我加载A应用 window.a B应用window.b
			但应用切换 沙箱 创造一个干净的 环境给这个子应用 使用 当切换时 可以丢弃 属性和恢复属性
			
			JS沙箱  proxy
			
			快照沙箱 一年前拍一张照 现在再拍一张  (区别保存起来) 在回到一年前    ?进行差异对比? 不确定恢复
			
			 */
			
			class SnapshotSandbox{
				constructor() {
					this.proxy=window;//window属性
					this.modifyPropsMap={} // 记录在window上的修改
					this.active();
				}
				active(){
					this.windowSnapshot={} //拍照
					for(const prop in window){
						if(window.hasOwnProperty(prop)){
							this.windowSnapshot[prop]=window[prop]
						}
					}
					Object.keys(this.modifyPropsMap).forEach(p=>{
						window[p]=this.modifyPropsMap[p]
					})
				}
				inactive(){
					for(const prop in window){
						if(window.hasOwnProperty(prop)){
							if(window[prop]!==this.windowSnapshot[prop]){
								this.modifyPropsMap[prop]=window[prop]
								window[prop]=this.windowSnapshot[prop]
							}
						}
					}
				}
			}
			let sandbox=new SnapshotSandbox();
			((window)=>{
				window.a=1
				window.b=2
				window.e=function (){
					alert(1)
				}
				console.log(window.a,window.b,window.c,window.e)
				sandbox.inactive()
				console.log(window.a,window.b,window.c,window.e)
				window.c=3
				sandbox.active()
				console.log(window.a,window.b,window.c,window.e)
				window.b=4
				sandbox.inactive()
				window.b=4
				console.log(window.a,window.b,window.c,window.e)
				sandbox.active()
				console.log(window.a,window.b,window.c,window.e)
			})(sandbox.proxy) //sandbox.proxy 就是window
			// 如果是多个子应用的就不能这种方式 es6的proxy
			// 代理沙箱介意实现多应用沙箱 吧不同的应用用不同的代理来处理
			
			
		</script>
	</body>
</html>

子应用之间的样式的隔离 

1.Dynamic Stylesheet 动态样式表 根据当前的应用切换各自的样式 移除就样式

父子应用之间的样式隔离

BEM 预定项目前缀

Css-modules 打包时候生成不冲突的选择器名

shadow DOM 真正意义上的隔离 影子DOM video 按钮

CSS-in-js

实现案例

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
	</head>
	<body>
		<p>hello  world</p>
		<div id="shadow"></div>
		<script>
			let shadowDom =shadow.attachShadow({mode:'closed'}) //外界无法访问 shadow dom
			let pElm=document.createElement('p')
			pElm.innerHTML='hello zf'
			let styleElm=document.createElement('style')
			styleElm.textContent='p{color:red}'
			shadowDom.appendChild(styleElm)
			shadowDom.appendChild(pElm)
			// document.body.appendChild(pElm) // react 项目 弹窗 
		</script>
	</body>
</html>

如果自己的实现效果不理想 这个是我的源代码 大家有兴趣的可以参考一下 由于是学习 记录我相信里面的额注释一定会对你很有帮助https://download.csdn.net/download/qq_35128576/12821683

猜你喜欢

转载自blog.csdn.net/qq_35128576/article/details/108462283