一、CSS 隔离方案
子应用之间样式隔离:
Dynamic Stylesheet
动态样式表,当应用切换时移除老应用样式,添加新应用样式
主应用和子应用之间的样式隔离:
BEM
(Block Element Modifier)
约定项目前缀
CSS-Modules
打包时生成不冲突的选择器名
Shadow DOM
真正意义上的隔离
css-in-js
let shadowDom = shadow.attachShadow({ mode: 'open' });
let pElement = document.createElement('p');
pElement.innerHTML = 'hello world';
let styleElement = document.createElement('style');
styleElement.textContent = `
p{color:red}
`
shadowDom.appendChild(pElement);
shadowDom.appendChild(styleElement)
shadow DOM
可以实现真正的隔离机制
二、JS 沙箱机制
当运行子应用时应该跑在内部沙箱环境中
快照沙箱,在应用沙箱挂载或卸载时记录快照,在切换时依据快照恢复环境
(
无法支持多实
例
)
Proxy
代理沙箱
,
不影响全局环。
1).
快照沙箱
1.
激活时将当前
window
属性进行快照处理
2.
失活时用快照中的内容和当前
window
属性比对
3.
如果属性发生变化保存到
modifyPropsMap
中,并用快照还原
window
属性
4.
在次激活时,再次进行快照,并用上次修改的结果还原
window
快照沙箱只能针对单实例应用场景
,
如果是多个实例同时挂载的情况则无法解决,只能通过
proxy
代理沙箱来实现
2).Proxy
代理沙箱
每个应用都创建一个
proxy
来代理
window
,好处是每个应用都是相对独立,不需要直接更
改全局
window
属性!
class ProxySandbox {
constructor() {
const rawWindow = window;
const fakeWindow = {}
const proxy = new Proxy(fakeWindow, {
set(target, p, value) {
target[p] = value;
return true
},
get(target, p) {
return target[p] || rawWindow[p];
}
});
this.proxy = proxy
}
}
let sandbox1 = new ProxySandbox();
let sandbox2 = new ProxySandbox();
window.a = 1;
((window) => {
window.a = 'hello';
console.log(window.a)
})(sandbox1.proxy);
((window) => {
window.a = 'world';
console.log(window.a)
})(sandbox2.proxy);
每个应用都创建一个
proxy
来代理
window
,好处是每个应用都是相对独立,不需要直接更
改全局
window
属性!
三、主应用编写
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
import {registerMicroApps,start} from 'qiankun';
const apps = [
{
name:'vueApp', // 应用的名字
entry:'//localhost:10000', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
container:'#vue', // 容器名
activeRule:'/vue', // 激活的路径
props:{a:1}
},
{
name:'reactApp',
entry:'//localhost:20000', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
container:'#react',
activeRule:'/react',
}
]
registerMicroApps(apps); // 注册应用
start({
prefetch:false // 取消预加载
});// 开启
new Vue({
router,
render: h => h(App)
}).$mount('#app')
App.vue
<template>
<div>
<el-menu :router="true" mode="horizontal">
<!--基座中可以放自己的路由-->
<el-menu-item index="/">Home</el-menu-item>
<!--引用其他子应用-->
<el-menu-item index="/vue">vue应用</el-menu-item>
<el-menu-item index="/react">react应用</el-menu-item>
</el-menu>
<router-view ></router-view>
<div id="vue"></div>
<div id="react"></div>
</div>
</template>
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
四、子应用
vue子项目
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// Vue.config.productionTip = false
let instance = null
function render(props) {
instance = new Vue({
router,
render: h => h(App)
}).$mount('#app'); // 这里是挂载到自己的html中 基座会拿到这个挂载后的html 将其插入进去
}
if (window.__POWERED_BY_QIANKUN__) { // 动态添加publicPath
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if (!window.__POWERED_BY_QIANKUN__) { // 默认独立运行
render();
}
// 子组件的协议就ok了
export async function bootstrap(props) {
};
export async function mount(props) {
console.log(props)
render(props)
}
export async function unmount(props) {
instance.$destroy();
}
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: '/vue',
routes
})
export default router
react子项目
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
function render(){
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
}
if(!window.__POWERED_BY_QIANKUN__){
render();
}
export async function bootstrap(){
}
export async function mount() {
render()
}
export async function unmount(){
ReactDOM.unmountComponentAtNode( document.getElementById('root'));
}
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter, Route, Link } from 'react-router-dom'
function App() {
return (
<BrowserRouter basename="/react">
<Link to="/">首页</Link>
<Link to="/about">关于页面</Link>
<Route path="/" exact render={() => (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)}></Route>
<Route path="/about" render={()=><h1>about页面</h1>}></Route>
</BrowserRouter>
);
}
// qiankun 无关的技术栈
export default App;