注册
web

一个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


npm install import-html-entry

或者


yarn add import-html-entry

基本使用


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

在index.html中
使用import-html-entry加载./template.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如下:


<!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处理过后如下:


<!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---执行后返回脚本信息

image.png
4、getExternalStyleSheets---执行后返回样式信息


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


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


qiankun中如何使用的?


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

在src/loader.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 隔离(避免污染全局)做了预处理。

整体流程如下图所示:
image.png


execScripts


image.png


code = getExecutableScript()


通过function+with实现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执行代码


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


const { template, scripts, entry, styles } = processTpl(getTemplate(html), assetPublicPath, postProcessTemplate);

看一下执行结果。


image.png


通过processTpl实现。

1、替换HTML

2、导出js入口列表

3、style列表

4、找到入口文件


作者:吃饺子不吃馅
来源:juejin.cn/post/7445090940278276147

0 个评论

要回复文章请先登录注册