逻辑挪进 Worker,一加载就报 document 未定义
把两万个点的聚合计算放进 Worker,想解放主线程,结果 Worker 刚启动就挂掉。
现象
Worker 的控制台(浏览器开发者工具的 Worker 上下文)报错,主线程收不到任何消息:
Uncaught ReferenceError: document is not defined
at leaflet-src.esm.js:4210
Uncaught ReferenceError: window is not defined
at proxied-module.js:1
原因
Worker 里没有 window、document、localStorage。
很多地图库在模块顶层就会探测 window 或创建 canvas,
仅仅 import 就会触发报错,哪怕没有调用任何地图 API。
另一类情况是把整个组件文件搬进 Worker,文件里连带引入了 UI 依赖, 依赖链上任意一个模块访问 DOM,都会在加载阶段失败。
排查过程
- 看报错栈最底部的模块名,确认是哪个依赖在顶层访问 DOM。
- 把 Worker 入口临时清空,只留一行日志,确认 Worker 本身可用。
- 逐个 import,找出触发报错的那一个。
// worker.js 排查用
console.log('worker 启动', typeof self);
// 然后逐个取消注释
// import './geo/aggregate.js';
// import 'leaflet';
解决方案
Worker 只放纯计算代码。聚合、坐标换算、网格统计这类逻辑抽成不依赖 DOM 的模块:
// geo/aggregate.js —— 不引入任何地图库
export function gridAggregate(points, cellSize) {
var buckets = new Map();
for (var i = 0; i < points.length; i++) {
var key = Math.floor(points[i].x / cellSize) + ':' +
Math.floor(points[i].y / cellSize);
var b = buckets.get(key) || { x: 0, y: 0, count: 0 };
b.x += points[i].x;
b.y += points[i].y;
b.count++;
buckets.set(key, b);
}
return Array.from(buckets.values());
}
// worker.js
import { gridAggregate } from './geo/aggregate.js';
self.onmessage = function (e) {
var result = gridAggregate(e.data.points, e.data.cellSize);
self.postMessage({ id: e.data.id, cells: result });
};
主线程用 Vite 的 Worker 导入语法,打包器会自动处理依赖与路径:
var worker = new Worker(
new URL('./workers/aggregate.worker.js', import.meta.url),
{ type: 'module' }
);
worker.onmessage = function (e) {
renderCells(e.data.cells);
};
worker.postMessage({ id: 1, points: points, cellSize: 24 });
确实需要在 Worker 里画图时,用 OffscreenCanvas 转交控制权,
不要试图在 Worker 里创建 DOM canvas:
// 主线程
var canvas = document.getElementById('map');
var offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
// worker.js
self.onmessage = function (e) {
var ctx = e.data.canvas.getContext('2d');
ctx.fillStyle = '#1c7ed6';
ctx.fillRect(10, 10, 40, 40);
};
验证
Worker 启动时不再出现 document is not defined。
主线程收到聚合结果,数量与主线程直接计算一致。
计算期间拖动地图,主线程帧率保持在 50 fps 以上。
边界要清楚:Worker 适合纯数据计算,不适合持有地图对象。 需要在地图上绘制时,把结果传回主线程,或走 OffscreenCanvas, 不要让地图库进入 Worker。
经纬志(2026)。《逻辑挪进 Worker,一加载就报 document 未定义》。https://www.jingweizhi.com/cookbook/webworker-dom-library.html(访问日期 2026-11-03)。