jsdom 与 worker-dom 学习记录
Last Edited Time
Jun 10, 2023 03:56 AM
date
Jan 9, 2023
slug
jsdom-and-worker-dom
status
Published
tags
W3C
JavaScript
DOM
summary
jsdom 与 worker-dom 学习以及改造成脱离 Node 的库
type
Post
简介为什么不选 worker-dom?原因使用实例(Hello World)宿主核心代码jsdom it is实现原理Web IDL 示例详解(History)快速开始测试方式运行 WPT 测试在 worker 里运行 tests Reference
简介
目前想在一个纯 JS 沙箱环境中使用 DOM 相关的操作,发现有 worker-dom 与 jsdom 两个库,其中:
- worker-dom 是在 web-worker 内实现的 DOM API 标准,主要用于提升 JS 代码运行速度
- jsdom 是纯 javascript 与 node 的实现的 DOM 与 HTML 标准,主要用于测试与抓包
为什么不选 worker-dom?
原因
- 原因一: worker-dom 本质上还是使用的是浏览器的 DOM 标准,在 worker 中操作 DOM 并发送消息到宿主,由宿主代为执行,并没有从零开发一个 DOM 标准
- 原因二:由于 worker 的通信是异步的订阅发布式,导致 DOM 的同步方法操作在 worker 中会变成异步操作,例如
getBoundingClientRect
方法
使用实例(Hello World)
- index.html
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2F26dd767b-dc58-42b3-9aa1-8f27f8a3e397%2FUntitled.png%3Fid%3D0e313c8d-2063-4e67-b4d3-dbecae47840d%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DGesFGItDgypl_LfJSpuP7ljfuFjPxzHr8KSd2DvtUXU?table=block&id=0e313c8d-2063-4e67-b4d3-dbecae47840d&cache=v2)
- hello-world.js
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2F266c1f3e-32cd-49fd-913c-6c7f99028645%2FUntitled.png%3Fid%3D3ab18b05-e6c2-4822-8d04-b4299cebe832%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DPNxyNwm8wYlbRmAS6OR7TZ5RowUHQ6iq3AuDB8e77Xk?table=block&id=3ab18b05-e6c2-4822-8d04-b4299cebe832&cache=v2)
运行效果
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2Fadddc9e7-a74a-4934-852f-047601fef606%2FUntitled.png%3Fid%3D45f3780b-875c-4a01-b656-d1556304a0c7%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DKgdqOQt-La83Bk3gDYNLcZbtdEcIbjJbySByMP0nNLU?table=block&id=45f3780b-875c-4a01-b656-d1556304a0c7&cache=v2)
宿主核心代码
- 在执行
MainThread.upgradeElement
的时候,会先请求代码字符串,然后执行install
方法
install
方法会创建workerContext
用于执行worker 代码
,并监听来自worker
的消息
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2F3a45f0cf-b2e7-4681-9945-463139832159%2FUntitled.png%3Fid%3Dfa225097-f7b4-49ce-b64d-7ed136546533%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DGqZ85VSrZ4wvI5Sf3L8bAfD-VHlHAaj2ZqC9rxJZJ4g?table=block&id=fa225097-f7b4-49ce-b64d-7ed136546533&cache=v2)
- 其中消息格式是被简化过的,具体格式见调试日志
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2Ff2a6d19b-6a63-4e41-9546-457295ae65f5%2FUntitled.png%3Fid%3Dc32ff41e-66d8-4791-89d2-d917f241afeb%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DAUxMkgYRVPQV1ZnzUnuYsAHimGb4sTLCDw5LSTTmMtY?table=block&id=c32ff41e-66d8-4791-89d2-d917f241afeb&cache=v2)
- 宿主环境接收到消息之后通过统一的
mutatorContext
来对宿主环境的DOM
进行操作
jsdom it is
实现原理
总的来说,Web 平台的类(像
Window
、Node
、Location
或 CSSStyleSheet
)可以被 Web IDL
语言来描述,Web IDL
抽象并减少了很多样板代码,比如类型转换、参数校验、属性映射等。综上所述,大多数
jsdom
里的 Web 类由以下两个文件实现- IDL 文件,比如
Attr.webidl
,或多或少直接从过饭中搬来
- 实现文件,比如
Attr-impl.js
,包括相关的实现逻辑
最后通过
yarn prepare
命令生成一个公共 API 文件(例如 Attr.js
),里面包括了从 Web IDL 衍生出来的样板代码,引用到实现文件里的重要逻辑。然后把所有的功能通过 lib/jsdom/living/interfaces.js
文件暴露到 jsdom windows 里面。Web IDL 示例详解(History)
History.webidl
文件从 WHATWG HTML Spec History Interface 拷贝而来(有微调),如下:
enum ScrollRestoration { "auto", "manual" };
[Exposed=Window]
interface History {
readonly attribute unsigned long length;
attribute ScrollRestoration scrollRestoration;
readonly attribute any state;
undefined go(optional long delta = 0);
undefined back();
undefined forward();
undefined pushState(any data, DOMString unused, optional USVString? url = null);
undefined replaceState(any data, DOMString unused, optional USVString? url = null);
};
History-impl.js
实现核心的逻辑
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2F35f7bfd9-3b35-453b-9cd2-ff0793341999%2FUntitled.png%3Fid%3Dea9fb714-4ca5-4358-8cc4-af8039822728%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3Duv0-ovegDD6fepSHIo3tU6tOdFos61-2o4WbyjOJDWo?table=block&id=ea9fb714-4ca5-4358-8cc4-af8039822728&cache=v2)
- 自动生成
History.js
文件
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2F9595fc9a-510f-49fd-8981-59257bbece8d%2FUntitled.png%3Fid%3D4e06ff6c-408a-494b-96e7-c3d99a1a48a0%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DMWnec5hh89HLSy4NE9YI-vl3FBM_gdImkUn3ghy7Y78?table=block&id=4e06ff6c-408a-494b-96e7-c3d99a1a48a0&cache=v2)
- 其中
install
方法会被interface.js
的installInterface
方法引用到
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2F8ce668a5-aba1-446b-9368-81bb4f8035df%2FUntitled.png%3Fid%3D755c4652-53a2-4bd4-acb3-3f1d8e1ee4d8%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DSd3cmrh4zfDd64-B0Qn01fiPUPt9v-AMS3awXeYXUAA?table=block&id=755c4652-53a2-4bd4-acb3-3f1d8e1ee4d8&cache=v2)
installInterface
方法会在创建window
的时候被执行
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2F91d91868-2780-4599-8725-9576968034b6%2FUntitled.png%3Fid%3D3af80896-20cb-47ec-9c01-d2824eb0b6c7%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DcH-gK8aapegSlRSe0k7toqt_jUaEVjQAyBdxpZLl4QM?table=block&id=3af80896-20cb-47ec-9c01-d2824eb0b6c7&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2Fb48002d7-8b58-476e-b6bf-960cfbc35925%2FUntitled.png%3Fid%3D38466b64-79dd-47ca-b3bb-3faa701ccef5%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DkcttydBn6g8W_MY7fgblwrtwTVCFunaJ-a4PLepfqn4?table=block&id=38466b64-79dd-47ca-b3bb-3faa701ccef5&cache=v2)
快速开始
- 下载代码并用
yarn
安装 NPM 包
- 执行构建命令
yarn prepare
,该命令会执行以下逻辑 - 执行
./scripts/webidl/convert.js
命令,生成lib/jsdom/living/generated
目录与文件,改文件会被lib/jsdom/living/interfaces.js
引用到 - 执行
yarn generate-js-globals
命令,运行vm
并通过Object.getOwnPropertyDescriptors(this)
命令获取所有的全局变量属性,并写到lib/jsdom/browser/js-globals.json
文件里面,后续会用于生成windowInstance
对象
- 初始化
web-platform-tests
源码与环境 - 初始化 git submodule,这一步有可能要挂代理,切等待时间较长
- 设置
python3
环境与host
,详情见 Running Tests from the Local System - 由于 wpt 的执行需要 Python3.6 或以上版本,而且
node child_process
的执行不支持bash alias 和 bash function
,需要把start-wpt-server
与run-tuwpt.js
的python
改为python3
- 执行测试命令
测试方式
- web 平台功能测试
yarn test-wpt
,可以指定测试执行yarn test-wpt --fgrep dom/events
- 自定义的 web 平台功能测试
yarn test-tuwpt
,可以指定测试运行yarn test-tuwpt --fgrep domparsin
- 与 wpt 无关的 API 测试
yarn test-api
,使用 mocha 合 chai 编写
- 在浏览器中直接运行
yarn test-browser-*
,使用 karma 编写
运行 WPT 测试
- 运行
yarn test-wpt --fgrep dom/events
结果
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2Ff5d0ac55-ce46-4553-ab24-ea4f5312aa56%2FUntitled.png%3Fid%3Da95e7529-e50a-4f8d-91d8-8b54c929b148%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DAwa4vl3cm8xGXp_giUp_NRohGL0g_G3QO7lGEbf4i5E?table=block&id=a95e7529-e50a-4f8d-91d8-8b54c929b148&cache=v2)
在 worker 里运行 tests
- 刚开始看到
yarn test-browser-worker
十分开心,感觉jsdom
一定能在 worker 里面运行,然而在看了karma
配置后发现大意了,wpt
相关测试都不能在 iframe 或 worker 内运行
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2Fdf6b3f30-3e5d-4163-b3da-0f7ca2f27783%2FUntitled.png%3Fid%3Db9f17fe9-2d24-4c28-82d9-49174f575259%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3DWhrRrXMpRaGntRspMXeng-sXBJMP1gjoTfPYUv8nvNQ?table=block&id=b9f17fe9-2d24-4c28-82d9-49174f575259&cache=v2)
- 不过至少可以确定打包后的代码在经过
browserify
转换后可以在 worker 与 iframe 中运行不报错
- 运行
yarn test-browser-worker
结果如下
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2F6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%2F8dcc1d6d-04c3-4220-b45d-a2beb17ecf25%2FUntitled.png%3Fid%3D38cf19dd-2525-46df-9f8f-b7639d0e1020%26table%3Dblock%26spaceId%3D6e1664a6-54f4-4dec-8dd1-868c4c0cf7ea%26expirationTimestamp%3D1720015200000%26signature%3Dk83sQ6QRROutWdDQj7wi-t_hYDt4h2ClUYwyfANfmIs?table=block&id=38cf19dd-2525-46df-9f8f-b7639d0e1020&cache=v2)