一个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、找到入口文件

相关推荐
卸任5 分钟前
Vibe coding工具使用心得
前端·ai编程
brien91 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance1 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M1 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写1 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李1 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易1 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马2 小时前
前沿视界
前端
kyriewen2 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易2 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员