一个js库就把你的网页的底裤🩲都扒了——import-html-entry

概述

import-html-entry 是一个用于动态加载和处理 HTML 和 JS 文件的库,主要用于微前端架构中。它能够从远程服务器拉取 HTML 内容,并对其中的 JS 和 CSS 进行处理,以便在主应用中加载和执行。这个库是 qiankun 微前端框架的核心依赖之一,提供了强大的动态加载和执行能力。在微前端框架 qiankun 中,import-html-entry 被用来解决 JS Entry 的问题,通过 HTML Entry 的方式,让用户接入微应用就像使用 iframe 一样简单。

使用方法

安装

首先,你需要通过 npm 或 yarn 安装 import-html-entry

bash 复制代码
npm install import-html-entry

或者

bash 复制代码
yarn add import-html-entry

基本使用

以下是一个简单的示例,展示如何使用 import-html-entry 加载一个远程的 HTML 文件, 我们看官网的例子

在index.html中 使用import-html-entry加载./template.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>index</title>
</head>
<body>
<script type="module">
	window.onerror = e => {
		console.log('error', e.message);
	};
	window.onunhandledrejection = (e) => {
		console.log('unhandledrejection', e.reason.message);
	};

	import('./dist/index.js').then(({ importEntry }) => {
		importEntry('./template.html').then(res => {
			console.log(res);
			return res.execScripts().then(exports => {
				console.log(exports);
			});
		}).catch(e => {
			console.log('importEntry failed', e.message);
		});
	});
</script>
</body>
</html>

template.html如下:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
	<link href="https://unpkg.com/antd@3.13.6/dist/antd.min.css" rel="stylesheet">
	<link href="https://unpkg.com/bootstrap@4.3.1/dist/css/bootstrap-grid.min.css" rel="stylesheet">
</head>
<body>

<script src="./a.js"></script>
<script ignore>alert(1)</script>
<script src="./b.js"></script>
<script src="./c.js"></script>
<script src="https://unpkg.com/react@16.4.2/umd/react.production.min.js"></script>
<script src="https://unpkg.com/mobx@5.0.3/lib/mobx.umd.js"></script>
<script src="https://www.baidu.com"></script>
</body>
</html>

template.html被import-html-entry处理过后如下:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
    <style>
        /* antd样式被内链进入 */
    </style>
    <style>
        /* bootstrap样式被内链进入 */
    </style>
</head>
<body>

<!--   script http://127.0.0.1:7001/a.js replaced by import-html-entry -->
<!-- ignore asset js file replaced by import-html-entry -->
<!--   script http://127.0.0.1:7001/b.js replaced by import-html-entry -->
<!--   script http://127.0.0.1:7001/c.js replaced by import-html-entry -->
<!--   script https://unpkg.com/react@16.4.2/umd/react.production.min.js replaced by import-html-entry -->
<!--   script https://unpkg.com/mobx@5.0.3/lib/mobx.umd.js replaced by import-html-entry -->
<!--   script https://www.baidu.com/ replaced by import-html-entry -->
</body>
</html>

可以发现html中的css被处理成为内链样式的了,其中的js代码script被注释掉了

importHTML返回值有如下几个:

1、template---处理过后的html

2、assetPublicPath---资源路径

3、getExternalScripts---执行后返回脚本信息
4、getExternalStyleSheets---执行后返回样式信息

5、execScripts---js代码执行器,可以传入代理的window对象

我们可以看出来,经过import-html-entry 处理后能够拿到这个html中的js、css内容,其中css会被处理成为内链样式嵌入HTML中,js我们可以通过execScripts传入自己的代理window可以实现js沙箱隔离

qiankun中如何使用的?

我们观察qiankun源码中是如何使用的import-html-entry

在src/loader.js中如下:

js 复制代码
// 266行
const { template, execScripts, assetPublicPath, getExternalScripts } = await importEntry(entry, importEntryOpts);
// 347行
const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox, {
    scopedGlobalVariables: speedySandbox ? cachedGlobals : [],
  });
  // get the lifecycle hooks from module exports
  const { bootstrap, mount, unmount, update } = getLifecyclesFromExports(
    scriptExports,
    appName,
    global,
    sandboxContainer?.instance?.latestSetProp,
  );

可以看到和预期一样

1、使用import-html-entry拿到js执行器

2、执行execScripts,并且传入自己的globalContext

3、根据导出,拿到生命周期函数lifecycle

源码解析

import-html-entry 的核心功能是通过 fetch 获取指定 URL 的 HTML 内容,然后解析并处理这个 HTML 模板,最终返回一个包含处理后的 HTML、CSS 和 JS 的 Promise 对象。具体步骤如下:

  1. 拉取 HTML 并处理 :通过 fetch 获取到 URL 对应的全部内容(即 HTML 文件的字符串),然后解析出以下内容:经过初步处理后的 HTML(去掉外链 CSS 和外链 JS)、由所有 script 组成的数组、由所有 style 组成的数组。

  2. 嵌入 CSS :通过 fetch 拉取到上述 style 数组里面对应的 CSS,然后将拉取到的每一个 href 对应的 CSS 通过 <style> 包裹起来且嵌入到 HTML 中。

  3. 执行 JS 脚本:支持执行页级 JS 脚本以及拉取上述 HTML 中所有的外联 JS 并支持执行。因此,在微前端中,使用此依赖可以直接获取到子应用(某 URL)对应的 HTML 且此 HTML 上已经嵌好了所有的 CSS,同时还可以直接执行子应用的所有 JS 脚本且此脚本还为 JS 隔离(避免污染全局)做了预处理。

整体流程如下图所示:

execScripts

code = getExecutableScript()

通过function+with实现js沙箱

js 复制代码
function getExecutableScript(scriptSrc, scriptText, opts = {}) {
	const { proxy, strictGlobal, scopedGlobalVariables = [] } = opts;

	const sourceUrl = isInlineCode(scriptSrc) ? '' : `//# sourceURL=${scriptSrc}\n`;

	// 将 scopedGlobalVariables 拼接成变量声明,用于缓存全局变量,避免每次使用时都走一遍代理
	const scopedGlobalVariableDefinition = scopedGlobalVariables.length ? `const {${scopedGlobalVariables.join(',')}}=this;` : '';

	// 通过这种方式获取全局 window,因为 script 也是在全局作用域下运行的,所以我们通过 window.proxy 绑定时也必须确保绑定到全局 window 上
	// 否则在嵌套场景下, window.proxy 设置的是内层应用的 window,而代码其实是在全局作用域运行的,会导致闭包里的 window.proxy 取的是最外层的微应用的 proxy
	const globalWindow = (0, eval)('window');
	globalWindow.proxy = proxy;
	// TODO 通过 strictGlobal 方式切换 with 闭包,待 with 方式坑趟平后再合并
	return strictGlobal
		? (
			scopedGlobalVariableDefinition
				? `;(function(){with(this){${scopedGlobalVariableDefinition}${scriptText}\n${sourceUrl}}}).bind(window.proxy)();`
				: `;(function(window, self, globalThis){with(window){;${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy, window.proxy);`
		)
		: `;(function(window, self, globalThis){;${scriptText}\n${sourceUrl}}).bind(window.proxy)(window.proxy, window.proxy, window.proxy);`;
}

evalCode(scriptSrc, code)

通过eval执行代码

js 复制代码
export function evalCode(scriptSrc, code) {
	const key = scriptSrc;
	if (!evalCache[key]) {
		const functionWrappedCode = `(function(){${code}})`;
		evalCache[key] = (0, eval)(functionWrappedCode);
	}
	const evalFunc = evalCache[key];
	evalFunc.call(window);
}

processTpl

js 复制代码
const { template, scripts, entry, styles } = processTpl(getTemplate(html), assetPublicPath, postProcessTemplate);

看一下执行结果。

通过processTpl实现。

1、替换HTML

2、导出js入口列表

3、style列表

4、找到入口文件

相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者3 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人7 小时前
前端知识补充—CSS
前端·css