/* Partytown 0.10.2-dev1727590485751 - MIT builder.io */
Object.freeze((obj => {
const properties = new Set;
let currentObj = obj;
do {
Object.getOwnPropertyNames(currentObj).forEach((item => {
"function" == typeof currentObj[item] && properties.add(item);
}));
} while ((currentObj = Object.getPrototypeOf(currentObj)) !== Object.prototype);
return Array.from(properties);
})([]));
const resolves = new Map;
const swMessageError = (accessReq, $error$) => ({
$msgId$: accessReq.$msgId$,
$error$: $error$
});
const httpRequestFromWebWorker = req => new Promise((async resolve => {
const accessReq = await req.clone().json();
const responseData = await (accessReq => new Promise((async resolve => {
const clients = await self.clients.matchAll();
const client = ((clients, msgId) => {
const tabId = msgId.split(".").pop();
let client = clients.find((a => a.url.endsWith(`?${tabId}`)));
client || (client = [ ...clients ].sort(((a, b) => a.url > b.url ? -1 : a.url < b.url ? 1 : 0))[0]);
return client;
})([ ...clients ], accessReq.$msgId$);
if (client) {
const timeout = 12e4;
const msgResolve = [ resolve, setTimeout((() => {
resolves.delete(accessReq.$msgId$);
resolve(swMessageError(accessReq, "Timeout"));
}), timeout) ];
resolves.set(accessReq.$msgId$, msgResolve);
client.postMessage(accessReq);
} else {
resolve(swMessageError(accessReq, "NoParty"));
}
})))(accessReq);
resolve(response(JSON.stringify(responseData), "application/json"));
}));
const response = (body, contentType) => new Response(body, {
headers: {
"content-type": contentType || "text/html",
"Cache-Control": "no-store"
}
});
self.oninstall = () => self.skipWaiting();
self.onactivate = () => self.clients.claim();
self.onmessage = ev => {
const accessRsp = ev.data;
const r = resolves.get(accessRsp.$msgId$);
if (r) {
resolves.delete(accessRsp.$msgId$);
clearTimeout(r[1]);
r[0](accessRsp);
}
};
self.onfetch = ev => {
const req = ev.request;
const url = new URL(req.url);
const pathname = url.pathname;
if (pathname.endsWith("sw.html")) {
ev.respondWith(response('<!DOCTYPE html><html><head><meta charset="utf-8"><script src="./partytown-sandbox-sw.js?v=0.10.2-dev1727590485751"><\/script></head></html>'));
} else {
pathname.endsWith("proxytown") && ev.respondWith(httpRequestFromWebWorker(req));
}
};
这个 Service Worker 脚本主要与 Partytown 相关,Partytown 是一个用于在 Web Worker 或 Service Worker 中运行第三方脚本的库,以减少主线程阻塞,提升网页性能。以下是对其作用的详细分析:
主要作用
- 初始化和配置 Partytown 环境:
- 脚本开头通过
Object.freeze
冻结了一个函数的执行结果,该函数提取了某个对象的原型链中的所有方法名(function
类型属性),并返回为数组。这可能是 Partytown 用于初始化或限制某些对象的行为,确保不可变性或安全性。
- 处理 Service Worker 生命周期:
self.oninstall
:调用self.skipWaiting()
,使 Service Worker 立即进入激活状态,跳过等待阶段。self.onactivate
:调用self.clients.claim()
,让 Service Worker 立即接管所有客户端(例如页面或标签页),确保新版本的 Service Worker 生效。
- 消息通信机制:
self.onmessage
:处理来自客户端(例如网页)的消息。Service Worker 使用resolves
Map 存储消息 ID 对应的Promise
解析器和超时计时器。- 当收到消息(
ev.data
)时,查找对应的msgId
,清除超时计时器,并调用存储的resolve
函数处理响应数据。
- 拦截和处理网络请求:
self.onfetch
:拦截客户端发起的fetch
请求,并根据请求路径执行不同逻辑:- 如果请求路径以
sw.html
结尾,返回一个简单的 HTML 页面,包含一个指向partytown-sandbox-sw.js
的<script>
标签。这可能是 Partytown 的沙箱环境入口,用于加载其他脚本。 - 如果请求路径以
proxytown
结尾,调用httpRequestFromWebWorker
函数处理请求。
- 如果请求路径以
- 代理第三方脚本请求:
httpRequestFromWebWorker
:处理以proxytown
结尾的请求,通常用于代理第三方脚本的网络请求。- 从请求中提取 JSON 数据(
accessReq
)。 - 使用
self.clients.matchAll()
获取所有客户端,并根据msgId
找到目标客户端(通过 URL 中的tabId
匹配)。 - 如果找到客户端,将请求数据通过
postMessage
发送给客户端,并设置 120 秒(12e4
毫秒)的超时机制。 - 如果超时或未找到客户端,返回错误响应(
Timeout
或NoParty
)。 - 最终返回 JSON 格式的响应,设置
content-type
为application/json
和Cache-Control: no-store
。
- 从请求中提取 JSON 数据(
具体用途
- Partytown 的核心功能:这个 Service Worker 是 Partytown 框架的一部分,用于将第三方脚本(例如分析工具、广告脚本等)的执行从主线程转移到 Web Worker 或 Service Worker 中,从而减少对主线程的阻塞,提高页面性能。
- 消息代理:充当客户端与 Web Worker 之间的通信桥梁,处理第三方脚本的请求和响应。
- 沙箱环境:通过
sw.html
提供一个沙箱入口,可能用于加载 Partytown 的其他脚本或隔离第三方代码的执行。 - 动态路由:根据请求路径(
sw.html
或proxytown
)决定如何处理,灵活支持不同类型的请求。
技术细节
- 超时机制:每个消息请求有 120 秒的超时时间,防止请求挂起。
- 客户端匹配:通过
tabId
匹配客户端,如果没有匹配到特定客户端,则选择 URL 按字母顺序排序的第一个客户端作为默认目标。 - 响应头:设置
Cache-Control: no-store
确保响应不被缓存,适合动态内容。 - 错误处理:通过
swMessageError
返回标准化的错误消息,包含msgId
和错误信息(如Timeout
或NoParty
)。
总结
这个 Service Worker 是 Partytown 框架的核心组件,用于:
- 管理 Service Worker 生命周期(安装、激活)。
- 提供沙箱环境(
sw.html
)以加载 Partytown 脚本。 - 代理第三方脚本的网络请求(
proxytown
),通过消息通信在客户端和 Web Worker 之间传递数据。 - 确保高效、隔离的第三方脚本执行,优化网页性能。