我们来详细讲解一下“微前端qiankun沙箱实现源码解读”的完整攻略。
什么是微前端
首先,我们需要知道什么是微前端。简单地说,微前端是一种前端架构模式,它将大型Web应用程序分解为较小的、易于管理的模块,这些模块可以独立地开发、测试和部署。每个模块可以由不同的团队开发,并且可以以不同的速度进行更新和发布。这种模式使得公司可以更加灵活地开发和部署前端应用程序。
qiankun框架
qiankun是一个完整的微前端解决方案,它使用了类似于iframe的方式来加载子应用程序,并且提供了沙箱机制来隔离子应用程序之间的全局变量污染和CSS冲突等问题。在qiankun的实现中,一个主应用程序可以加载多个子应用程序,并且可以将它们形成一个整体应用程序。qiankun的实现还涉及到一些复杂的技术,例如浏览器端路由、应用程序通信、外部样式加载、主应用程序与子应用程序之间的上下文等。
qiankun沙箱机制
qiankun的沙箱机制是其最核心的功能之一。它的主要目的是确保每个子应用程序在运行时都被隔离,避免全局变量的污染和CSS冲突等问题。在实现中,qiankun通过以下方式来实现沙箱机制:
- 拦截和修改子应用程序的JS代码
qiankun通过拦截和修改子应用程序的JS代码来避免全局变量污染和命名冲突问题。在这个过程中,qiankun会将子应用程序的所有全局变量和函数都保存到一个独立的命名空间中,确保子应用程序中的变量和函数与主应用程序和其他子应用程序的变量和函数保持隔离。
- 隔离CSS样式
qiankun还使用了类似于Shadow DOM的方式来隔离CSS样式。在实现中,qiankun会将子应用程序中的CSS代码进行重新编译,并添加唯一的命名空间标识符。这样,子应用程序中的CSS样式就不会与主应用程序和其他子应用程序的CSS样式产生冲突。
- 提供沙箱容器
qiankun还提供了沙箱容器来隔离子应用程序的DOM节点。在实现中,qiankun会在主应用程序中为每个子应用程序创建一个独立的DOM节点,子应用程序只能对自己的DOM节点进行操作,不能访问其他子应用程序的DOM节点。
qiankun沙箱实现源码解读
了解了qiankun沙箱机制的基本原理之后,我们可以进一步深入地阅读qiankun的源码,以了解其实现细节。下面我们将重点讲解qiankun的沙箱实现源码。
示例一:拦截和修改子应用程序的JS代码
在qiankun中,拦截和修改子应用程序的JS代码是通过emitLifeCycles函数来实现的。这个函数用于处理子应用程序的生命周期钩子函数,它会在子应用程序的生命周期钩子函数被调用之前进行一些必要的处理。
下面是emitLifeCycles函数的主要实现代码:
// 钩子函数名称
const QIANKUN_HOOKS = [
'bootstrap',
'mount',
'unmount',
];
// 发射生命周期方法
export function emitLifeCycles(app, oldApp, config, callback) {
// 遍历所有的钩子
QIANKUN_HOOKS.forEach(hook => {
app[hook] &&
setTimeout(() => {
// 如果旧应用存在,需要传递 oldApp 参数
hook === 'unmount'
? app[hook]({ ...config, ...{ beforeUnmount: () => callback(oldApp) } })
: app[hook](config);
});
});
}
在这个函数中,我们可以看到针对“bootstrap”、“mount”和“unmount”三个生命周期钩子的操作。在该函数中,没有涉及到修改子应用程序的代码的内容,那么子应用是如何被修改的呢?
首先,我们需要了解qiankun的loadMicroApp函数,这个函数是用于加载子应用程序的核心函数。在这个函数中,qiankun使用了一种特殊的代码处理方式,它通过调用被加载的子应用程序的bootstrap函数,并将该函数的执行结果进行分析,以获取子应用程序中的所有全局变量和函数,并将它们添加到qiankun的命名空间中。具体的实现代码可以如下展示:
// 拦截并标记子应用程序的调用函数
const hijackEffects = (app) => {
['pushState', 'replaceState'].forEach(type => {
window.history[type] = (...args) => {
window.history.__d = true;
window.history[type](...args);
};
})
// 保存子应用程序的原始对象
const rawSetTimeout = window.setTimeout;
const rawClearTimeout = window.clearTimeout;
const rawSetInterval = window.setInterval;
const rawClearInterval = window.clearInterval;
const rawAddEventListener = window.addEventListener;
const rawRemoveEventListener = window.removeEventListener;
// 监听setTimeout、clearTimeout、setInterval、clearInterval和addEventListener等函数
window.setTimeout = ((...args) => {
const handler = rawSetTimeout(...args);
recordWindowAccess('setTimeout');
return handler;
}) as any;
// 同理,hook其他函数
//......
// 返回子应用程序的可访问变量和方法的列表
return {
// 全局对象
get global() {
return window;
},
// 钩子
get hooks() {
return hijackers;
},
// WebSocket 对象
get WebSocket() {
return WebSocket;
},
// 触发历史记录
execScript(code: string) {
return eval(code);
}
};
};
// 加载微应用
export function loadMicroApp(app, configuration: any = {}) {
// 启用生命周期阶段中的强制沙盒环境切换,目前仅限在 usePublicPath 为 true 时使用,后续可能会用于样式隔离 & 预处理、事件隔离
const { sandbox, singular } = configuration;
// 设置 sandbox 注入方式及默认值
const { strictStyleIsolation, experimentalStyleIsolation, useEmbedSandbox } = sandbox || {};
// 对于一条 jest 测试用例(例如 singularMode.test.js),循环引用的问题会导致主应用和微应用的 window 对象不一致
// 这会导致微应用注册失败,因此需要确保主应用和微应用使用同一个 window 对象
if (window[__qiankun__]) {
app._container = document.getElementById(window[__qiankun__]);
return render(app);
}
// 创建全局命名空间
window.__$app_instance_of = window.__$app_instance_of || [];
window.__$app_global_variable = window.__$app_global_variable || {};
window.__$app_function = window.__$app_function || {};
// 创建唯一命名空间
const prefix = genNamespace(className);
window.__$app_instance_of[prefix] = new Set();
window.__$app_global_variable[prefix] = {};
window.__$app_function[prefix] = {};
// 创建沙盒环境,以隔离命名空间
const sandboxDocument = createSandboxContainer(document, strictStyleIsolation, experimentalStyleIsolation, useEmbedSandbox, appName);
const { proxy, obj } = createSandbox(sandboxDocument, prefix, className, singular, true);
// 拦截并标记全局变量和方法
const exposed = hijackEffects(app);
// 调用微应用程序的 bootstrap 方法
const bootstrap = sanitizeHtml(app.bootstrap({ ...exposed, ...configuration, ...{ rewriteURL: rewriteSandBoxFetch(appName) } }));
// 处理 bootstrap 方法返回的状态信息,以获取微应用程序中的全局变量和方法,并将它们添加到全局命名空间中
const jsSandbox = new JSSandbox(bootstrap, proxy);
// 创建 rendering 沙盒
const renderSandbox = createSandboxWithSpecialProps(obj, exposed, getAppWrapperGetter(sandboxDocument), className, appName, app);
// 渲染微应用程序
render({ ...app, _container: sandboxDocument.body, _proxy: (renderSandbox.proxy as any)._proxy, _cache: {} });
// 添加微应用程序到容器中
mountMicroApp(app, configuration, false, jsSandbox, exposed, renderSandbox, instance, callback);
return null;
}
通过以上的代码,我们可以看出,在加载子应用程序时,qiankun会先为子应用程序创建一个独立的命名空间,然后使用该命名空间来保存全局变量和函数,并将其与其他子应用程序和主应用程序进行隔离。在调用bootstrap函数时,qiankun会获取由子应用程序导出的所有全局变量和函数,并将其添加到命名空间中,然后将修改后的bootstrap函数的执行结果返回给加载函数,以进一步处理。由此,我们可以看出qiankun是如何通过拦截和修改子应用程序的JS代码来实现沙箱机制的。
示例二:隔离CSS样式
qiankun在隔离CSS样式方面,使用了类似于Shadow DOM的方式。在子应用程序中,所有的CSS样式都被编译为CSS Modules,并在原样式的基础上添加了独一无二的前缀,以区分不同子应用程序的样式。在qiankun的实现中,这个过程是通过withScopedStyle函数来实现的。
以下是withScopedStyle函数的主要实现代码:
const withScopedStyle = (id, css) => {
const uniqId = `qiankun-${id}`;
return `
${CSS_PREFIX} [class*="${uniqId}"] {
${css
.replace(/([\s\S]*)?(.*)/, '$1')
.replace(/&/g, `.${CSS_PREFIX} [class*="${uniqId}"]`)}
}`;
};
在该函数中,我们可以看到如何将原CSS样式编译为带有独立前缀的样式,同时,我们还可以看到局部作用域是如何被实现的(通过将子应用程序的class设置为一个唯一标识符)。
总结
通过以上的讲解,我们可以看出qiankun框架的沙箱机制是如何实现的。它通过拦截和修改子应用程序的JS代码,并使用独立命名空间来隔离全局变量和函数。同时,它还使用了类似于Shadow DOM的方式来隔离CSS样式,确保子应用程序的样式不会与其他子应用程序和主应用程序的样式产生冲突。这些技术的结合使得qiankun可以实现完整的微前端解决方案,并且可以确保子应用程序之间的隔离。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:微前端qiankun沙箱实现源码解读 - Python技术站