1. SOLID原则
单一功能、开闭原则、里氏替换、接口隔离以及依赖反转原则
2. 加载子应用
需要对html的解析做处理
import { fetchUrl } from '../util/fetchResources';
import { sandbox } from '../sandbox/sandbox';
import { findAppByName } from '../util';
const cache = {};
// 解析html
export const parseHtml = async (url, appName) => {
// 使用缓存保存内容,提高性能
if (cache[appName]) {
return cache[appName];
}
const div = document.createElement('div');
let scriptsArray = [];
div.innerHTML = await fetchUrl(url);
// 标签 link script
const [scriptUrls, scripts, elements] = getResources(div, findAppByName(appName));
const fetchedScript = await Promise.all(scriptUrls.map(url => fetchUrl(url)));
scriptsArray = scripts.concat(fetchedScript);
cache[appName] = [elements, scriptsArray];
return [elements, scriptsArray];
}
// 解析 js 内容
export const getResources = (root, app) => {
const scriptUrls = [];
const scripts = [];
function deepParse(element) {
const children = element.children;
const parent = element.parentNode;
// 处理位于 link 标签中的 js 文件
if (element.nodeName.toLowerCase() === 'script') {
const src = element.getAttribute('src');
if (!src) {
// 直接在 script 标签中书写的内容
let script = element.outerHTML;
scripts.push(script);
} else {
if (src.startsWith('http')) {
scriptUrls.push(src);
} else {
// fetch 时 添加 publicPath
scriptUrls.push(`http:${app.entry}/${src}`);
}
}
if (parent) {
let comment = document.createComment('此 js 文件已被微前端替换');
// 在 dom 结构中删除此文件引用
parent.replaceChild(comment, element);
}
}
// 处理位于 link 标签中的 js 文件
if (element.nodeName.toLowerCase() === 'link') {
const href = element.getAttribute('href');
if (href.endsWith('.js')) {
if (href.startsWith('http')) {
scriptUrls.push(href);
} else {
// fetch 时 添加 publicPath
scriptUrls.push(`http:${app.entry}/${href}`);
}
}
}
for (let i = 0; i < children.length; i++) {
deepParse(children[i]);
}
}
deepParse(root);
return [scriptUrls, scripts, root.outerHTML];
}
// 加载和渲染html
export const htmlLoader = async (app) => {
console.log('app', app);
const {
container: cantainerName, entry, name
} = app
let [dom, scriptsArray] = await parseHtml(entry, name);
let container = document.querySelector(cantainerName);
if (!container) {
throw Error(` ${name} 的容器不存在,请查看是否正确指定`);
}
container.innerHTML = dom;
scriptsArray.map((item) => {
sandbox(item, name);
});
}
3. 执行js内容
// 执行应用的 js 内容 new Function 篇
export const performScript = (script, appName, global) => {
const scriptText =
`try {
${script}
return window['${appName}']
} catch (err) {
console.error('runScript error:' + err);
}`;
const performer = new Function(scriptText);
return performer.call(global, global);
}
// 执行应用中的 js 内容 eval篇
export const performScriptForEval = (script, appName, global) => {
// 这儿是根据打包的时候设置的library名字获取对应的内容
const scriptText = `
(() => () => {
try {
${script}
return window['${appName}']
} catch (err) {
console.error('runScript error:' + err);
}
})()
`
return (() => eval(scriptText))().call(global, global)
}
4. 快照沙箱
// 应用场景: 比较老版本的浏览器
export class SnapShotSandBox {
constructor() {
this.proxy = window;
this.active();
}
active() {
this.snapshot = new Map(); // 创建 window 对象的快照
for (const key in window) {
// eslint-disable-next-line no-prototype-builtins
if (window.hasOwnProperty(key)) {
// 将window上的属性进行拍照
this.snapshot[key] = window[key];
}
}
}
inactive() {
for (const key in window) {
// eslint-disable-next-line no-prototype-builtins
if (window.hasOwnProperty(key)) {
// 将上次快照的结果和本次window属性做对比
if (window[key] !== this.snapshot[key]) {
// 还原window
window[key] = this.snapshot[key];
}
}
}
}
}
5. 代理沙箱
export const isFunction = (value) => typeof value === 'function';
// 代理沙箱
export class ProxySandBox {
constructor() {
this.proxy = window;
this.active();
}
active() {
const proxy = window;
const draftValue = Object.create(Object.getPrototypeOf(proxy))
this.proxy = new Proxy(proxy, {
get(target, propKey) {
// 函数做特殊处理
if (isFunction(draftValue[propKey])) {
return draftValue[propKey].bind(proxy)
}
if (isFunction(target[propKey])) {
return target[propKey].bind(proxy)
}
return draftValue[propKey] || target[propKey]
},
set(target, propKey, value) {
draftValue[propKey] = value
return true
}
})
}
inactive() {
console.log('关闭沙箱');
}
}
6. css隔离
- css modules
- shadow dom
Element.attachShadow() 即使同一个样式名称,也不会互相影响。(浏览器兼容性) - minicss
插件mini-css-extract-plugin
配置:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module: {
rules: [
{
test: /\.(cs|scs)s$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
},
]
},
将css打包成单独的文件。
7. 父子组件通信
- 定义store,通过props传递
export const creatStore = (initData) => (() => {
let store = initData;
let observers = [];
// 获取store
const getStore = () => {
return store;
}
const updateStore = (newValue) => new Promise((res) => {
if (newValue !== store) {
let oldValue = store; // 缓存
store = newValue;
res(store);
// 通知所有订阅者,监听store的变化
observers.forEach(fn => fn(newValue, oldValue));
}
})
// 添加订阅者
const subscribeStore = (fn) => {
observers.push(fn);
}
return { getStore, updateStore, subscribeStore }
})()
- 自定义事件 CustomEvent
// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) });
// create and dispatch the event
var event = new CustomEvent("cat", {
// detail 就是触发事件传递的参数
detail: {
hazcheeseburger: true
}
});
obj.dispatchEvent(event);
8. 预加载子应用
export const prefetch = async () => {
// 获取其余子应用
const appPieces = getList().filter(item => !window.location.pathname.startsWith(item.activeRule));
// 加载所有子应用
await Promise.all(appPieces.map(async app => await parseHtml(app.entry, app.name)))
};