Skip to content

Commit bc11a7d

Browse files
2234839claude
andcommitted
fix: safe stringify prevents addLog from blocking main thread
Replace JSON.stringify with depth-limited (3) manual serialization. Lazy stringify on addLog hot path — only materializes when consumed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 53b1eef commit bc11a7d

3 files changed

Lines changed: 40 additions & 8 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vite-plugin-pilot",
3-
"version": "0.8.1",
3+
"version": "0.8.2",
44
"description": "AI Agent 驾驶浏览器的导航工具 — 打通 浏览器运行时 → Dev Server → 源码 → IDE 的完整链路",
55
"type": "module",
66
"bin": {

src/client/log-collector.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,34 @@ export const logCollectorCode = `
1414
var logs = [];
1515
window.__pilot_errorCount = 0;
1616
17-
function stringify(arg) {
18-
if (typeof arg === 'string') return arg;
19-
if (arg instanceof Error) return arg.message + '\\n' + arg.stack;
20-
try { return JSON.stringify(arg); } catch(e) { return String(arg); }
17+
/** 安全字符串化,限制深度和长度,防止复杂对象(Vue 实例、DOM 等)卡死主线程 */
18+
var MAX_STRINGIFY_DEPTH = 3;
19+
var MAX_STRINGIFY_LEN = 500;
20+
21+
function stringify(arg, depth) {
22+
if (depth === undefined) depth = 0;
23+
if (typeof arg === 'string') return arg.length > MAX_STRINGIFY_LEN ? arg.slice(0, MAX_STRINGIFY_LEN) + '...' : arg;
24+
if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg);
25+
if (arg === null || arg === undefined) return String(arg);
26+
if (arg instanceof Error) return arg.message;
27+
if (depth >= MAX_STRINGIFY_DEPTH) return '[Object]';
28+
try {
29+
/** 数组特殊处理:只取前 5 项 */
30+
if (Array.isArray(arg)) {
31+
if (arg.length > 5) return '[' + arg.slice(0, 5).map(function(a) { return stringify(a, depth + 1); }).join(', ') + ', ...(' + arg.length + ')]';
32+
return '[' + arg.map(function(a) { return stringify(a, depth + 1); }).join(', ') + ']';
33+
}
34+
/** 普通对象:只取前 5 个 key */
35+
var keys = Object.keys(arg);
36+
if (keys.length > 5) {
37+
var parts = keys.slice(0, 5).map(function(k) { return k + ': ' + stringify(arg[k], depth + 1); });
38+
var result = '{' + parts.join(', ') + ', ...(' + keys.length + ')}';
39+
return result.length > MAX_STRINGIFY_LEN ? result.slice(0, MAX_STRINGIFY_LEN) + '...' : result;
40+
}
41+
var parts = keys.map(function(k) { return k + ': ' + stringify(arg[k], depth + 1); });
42+
var result = '{' + parts.join(', ') + '}';
43+
return result.length > MAX_STRINGIFY_LEN ? result.slice(0, MAX_STRINGIFY_LEN) + '...' : result;
44+
} catch(e) { return String(arg); }
2145
}
2246
2347
/** 存储最近 3 条错误信息,供 snapshot 采集 */
@@ -28,10 +52,18 @@ export const logCollectorCode = `
2852
/** 已知噪音日志(vite 警告等),不计入 errorCount 也不纳入 lastErrors */
2953
var ERROR_NOISE = ['[vite]', 'failed to connect to websocket', '[Vue warn]'];
3054
55+
/** 将 args 延迟转换为 message 字符串(首次访问时缓存),暴露给 ws-client 消费 */
56+
window.__pilot_logToMessage = function(entry) {
57+
if (entry.message !== undefined) return entry.message;
58+
entry.message = Array.from(entry.args).map(stringify).join(' ');
59+
entry.args = null;
60+
return entry.message;
61+
}
62+
3163
function addLog(type, args) {
3264
if (type === 'error') {
65+
/** error 路径仍需立即 stringify(噪音检测+lastErrors),但 error 频率低 */
3366
var msg = Array.from(args).map(stringify).join(' ').slice(0, 100);
34-
/** 噪音日志仍记录到 logs(完整日志),但不计入错误统计 */
3567
var isNoise = ERROR_NOISE.some(function(k) { return msg.indexOf(k) >= 0; });
3668
if (!isNoise) {
3769
window.__pilot_errorCount++;
@@ -42,7 +74,7 @@ export const logCollectorCode = `
4274
logs.push({
4375
timestamp: new Date().toISOString(),
4476
type: type,
45-
message: Array.from(args).map(stringify).join(' ')
77+
args: args
4678
});
4779
if (logs.length > maxLogs) logs.shift();
4880
}

src/client/ws-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const wsClientCode = `
5959
return true;
6060
})
6161
.map(function(l) {
62-
var msg = l.message;
62+
var msg = window.__pilot_logToMessage(l);
6363
/** warn/info 截断到 150 字符(去掉 Vue 组件堆栈等冗余信息),error 保留完整信息 */
6464
if (l.type !== 'error' && msg.length > 150) {
6565
var cutIdx = msg.indexOf('\\n');

0 commit comments

Comments
 (0)