如何将JS运行时错误位置映射转换为原始TypeScript代码错误位置
问题描述
项目支持用户输入TypeScript代码,代码转译后在Web Worker中通过eval执行,当前流程运行正常。需求是代码运行出错时打印对应原始代码的错误提示,但目前只能获取转译后JS代码的错误行列位置,和原始TS代码的错误位置不匹配。
已尝试在转译阶段开启inline source maps生成配置,但无法解决位置映射问题。
原有转译实现代码:
import ts, { ScriptTarget } from "typescript"; const jsCode = ts.transpile(tsCode, { alwaysStrict: true, target: ScriptTarget.ES2020 }), worker.sendMessage({ task: "execute", code: jsCode });
原有Worker侧错误捕获代码:
worker.addEventListener("error", (event: ErrorEvent) => { worker.postContextMessage(worker.currentTaskId!, { api: ScriptingApi.Result, contextId: worker.currentContext!, result: `${event.message} (${event.colno}:${event.lineno})`, isError: true, final: true, }); event.preventDefault(); });
解决方案
inline source map不生效的核心原因有两个:
ts.transpile是简化封装接口,就算开启sourcemap配置,也仅会将sourcemap内容拼接在输出JS末尾,不会返回结构化的sourcemap数据- 通过
eval执行的动态脚本没有关联的文件标识,Worker运行时默认不会解析这段代码附带的sourcemap,无法建立映射关系
推荐使用手动映射方案,兼容性最好,不依赖浏览器/运行时的sourcemap解析行为,步骤如下:
- 替换转译接口,获取完整sourcemap
弃用ts.transpile,改用ts.transpileModule接口转译代码,同时输出编译后JS和完整sourcemap:const transpileResult = ts.transpileModule(tsCode, { compilerOptions: { alwaysStrict: true, target: ScriptTarget.ES2020, sourceMap: true, inlineSources: true, // 将原始TS代码内容嵌入sourcemap,无需额外存储原始文件 }, fileName: "user-input.ts" // sourcemap生成需要原始文件标识,可自定义 }); const jsCode = transpileResult.outputText; const sourceMapContent = transpileResult.sourceMapText; - 初始化sourcemap查询器
转译完成后,基于生成的sourcemap初始化位置查询实例,将实例和当前任务ID关联存储,方便错误捕获时取用:import { SourceMapConsumer } from "source-map"; const consumer = await new SourceMapConsumer(JSON.parse(sourceMapContent)); // 按自身业务逻辑存储consumer,和worker.currentTaskId绑定即可 - 错误捕获时做位置转换
修改原有错误捕获逻辑,拿到运行时返回的JS行列号后,通过sourcemap查询器转换为原始TS代码的位置。注意运行时返回的行列号是1-based计数,和sourcemap要求的格式存在固定偏移,可根据实际运行环境微调偏移量:worker.addEventListener("error", (event: ErrorEvent) => { const consumer = getConsumerByTaskId(worker.currentTaskId!); // 转换为原始TS位置 const originalPosition = consumer.originalPositionFor({ line: event.lineno, column: event.colno - 1 }); worker.postContextMessage(worker.currentTaskId!, { api: ScriptingApi.Result, contextId: worker.currentContext!, result: `${event.message} (${originalPosition.column}:${originalPosition.line})`, isError: true, final: true, }); event.preventDefault(); });
如果不想手动维护映射逻辑,也可以选择运行时自动映射方案:在转译后的JS代码末尾追加sourceURL和base64格式的sourceMappingURL注释,再送入eval执行,现代浏览器会自动完成位置映射。但该方案在部分旧版本浏览器、定制Web Worker环境下存在兼容问题,稳定性不如手动映射。
追加注释的示例代码:
const codeToEval = `${jsCode} //# sourceURL=user-input.ts //# sourceMappingURL=data:application/json;charset=utf-8;base64,${btoa(unescape(encodeURIComponent(sourceMapContent)))} ` worker.sendMessage({ task: "execute", code: codeToEval });
内容的提问来源于stack exchange,提问作者Mike Lischke
相关产品推荐
相关产品推荐

