fix: v0.4.1 worker 创建开销优化与文档对齐 - worker 延迟至去抖到期才创建(去抖期内取消零创建开销、无 Worker 回退同样走去抖)、质量门禁文档补 npm test、README 架构树补 hooks、Markdown 报告标题移出引用块、安装包排除开发期文件
This commit is contained in:
@@ -362,7 +362,7 @@ describe('App - 提示与容错', () => {
|
||||
})
|
||||
|
||||
describe('App - 大输入与 worker 回退', () => {
|
||||
it('超过快路径阈值的文件在无 Worker 环境回退同步计算', async () => {
|
||||
it('超过快路径阈值的文件在无 Worker 环境去抖到期后回退同步计算', async () => {
|
||||
const big = Array.from({ length: 2100 }, (_, i) => `row${i}`).join('\n')
|
||||
window.api = mockApi({
|
||||
openFile: async () => ({
|
||||
@@ -376,9 +376,9 @@ describe('App - 大输入与 worker 回退', () => {
|
||||
render(<App />)
|
||||
fireEvent.click(screen.getByText('打开左侧'))
|
||||
expect(await screen.findByText('导出报告')).toBeInTheDocument()
|
||||
// jsdom 无 Worker:同步回退完成计算,不出现计算中遮罩,行已渲染
|
||||
// jsdom 无 Worker:去抖到期后同步回退完成计算,行渲染且计算中遮罩消失
|
||||
expect(await screen.findByText(/row0/)).toBeInTheDocument()
|
||||
expect(screen.queryByText('正在计算差异…')).not.toBeInTheDocument()
|
||||
expect(screen.getAllByText(/row0/).length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -48,10 +48,11 @@ describe('buildTxtReport', () => {
|
||||
})
|
||||
|
||||
describe('buildMarkdownReport', () => {
|
||||
it('输出表格化报告', () => {
|
||||
it('输出表格化报告,标题独立成行不进引用块', () => {
|
||||
const { rows, summary } = computeDiff('a', 'b')
|
||||
const md = buildMarkdownReport(rows, ctx, summary)
|
||||
expect(md).toContain('# DiffLens 差异报告')
|
||||
expect(md.startsWith('# DiffLens 差异报告')).toBe(true)
|
||||
expect(md).not.toContain('> # DiffLens 差异报告')
|
||||
expect(md).toContain('| 类型 |')
|
||||
expect(md).toContain('| 左行号 |')
|
||||
})
|
||||
|
||||
@@ -179,7 +179,9 @@ export function buildMarkdownReport(
|
||||
summary: DiffSummary
|
||||
): string {
|
||||
const head = headLines(ctx, summary, '# DiffLens 差异报告')
|
||||
const out: string[] = [...head.map((h) => `> ${h}`), '']
|
||||
// 标题独立成行(不套进引用块),其余元信息行以引用块展示
|
||||
const [title, ...meta] = head
|
||||
const out: string[] = [title, '', ...meta.map((h) => `> ${h}`), '']
|
||||
out.push('| 类型 | 左行号 | 左文件 | 右行号 | 右文件 |')
|
||||
out.push('| --- | --- | --- | --- | --- |')
|
||||
for (const row of rows) {
|
||||
|
||||
@@ -66,15 +66,15 @@ describe('useDiff - 快路径', () => {
|
||||
})
|
||||
|
||||
describe('useDiff - worker 后台计算', () => {
|
||||
it('重输入先进入 computing,去抖后派发,回传后更新', () => {
|
||||
it('重输入先进入 computing,去抖到期才创建 worker 并派发,回传后更新', () => {
|
||||
const factory = makeFactory()
|
||||
const left = heavyText(1100)
|
||||
const right = heavyText(1100)
|
||||
const { result } = renderHook(() => useDiff(left, right, NO_OPTIONS, factory))
|
||||
expect(result.current.computing).toBe(true)
|
||||
expect(result.current.diff).toBeNull()
|
||||
// 去抖期内尚未派发
|
||||
expect(factory.workers[0].posted).toHaveLength(0)
|
||||
// 去抖期内尚未创建 worker(零创建开销)
|
||||
expect(factory.workers).toHaveLength(0)
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(250)
|
||||
})
|
||||
@@ -117,11 +117,11 @@ describe('useDiff - worker 后台计算', () => {
|
||||
w1.respond(jobId1, computeDiff('a', 'a', {}))
|
||||
})
|
||||
expect(hook.result.current.diff).toBeNull()
|
||||
// 新 worker 的结果生效
|
||||
const w2 = factory.workers[1]
|
||||
// 新 worker 的结果生效(新任务同样在去抖到期才创建 worker)
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(250)
|
||||
})
|
||||
const w2 = factory.workers[1]
|
||||
const jobId2 = w2.posted[0].jobId
|
||||
expect(jobId2).toBeGreaterThan(jobId1)
|
||||
const res2 = computeDiff('m', 'n', {})
|
||||
@@ -132,7 +132,7 @@ describe('useDiff - worker 后台计算', () => {
|
||||
expect(hook.result.current.computing).toBe(false)
|
||||
})
|
||||
|
||||
it('去抖期内输入变更:旧任务未派发即取消(零浪费计算)', () => {
|
||||
it('去抖期内输入变更:旧任务未创建即取消(零创建与计算开销)', () => {
|
||||
const factory = makeFactory()
|
||||
const hook = renderHook((p: string) => useDiff(p, '', NO_OPTIONS, factory), {
|
||||
initialProps: heavyText(2100)
|
||||
@@ -141,34 +141,51 @@ describe('useDiff - worker 后台计算', () => {
|
||||
vi.advanceTimersByTime(100)
|
||||
})
|
||||
hook.rerender(heavyText(2300))
|
||||
expect(factory.workers[0].posted).toHaveLength(0)
|
||||
expect(factory.workers[0].terminated).toBe(true)
|
||||
// 去抖期内取消:worker 从未被创建
|
||||
expect(factory.workers).toHaveLength(0)
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
expect(factory.workers[1].posted).toHaveLength(1)
|
||||
expect(factory.workers).toHaveLength(1)
|
||||
expect(factory.workers[0].posted).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('worker 不可用时回退主线程同步计算', () => {
|
||||
it('worker 不可用时去抖到期回退主线程同步计算', () => {
|
||||
const left = heavyText(2100)
|
||||
// 工厂必须稳定引用:内联箭头函数每次渲染变引用会引发 effect 无限重跑
|
||||
const nullFactory: DiffWorkerFactory = () => null
|
||||
const { result } = renderHook(() => useDiff(left, 'x', NO_OPTIONS, nullFactory))
|
||||
// 去抖期内同样处于 computing(无 Worker 回退不豁免去抖)
|
||||
expect(result.current.computing).toBe(true)
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(250)
|
||||
})
|
||||
expect(result.current.diff).toEqual(computeDiff(left, 'x', {}))
|
||||
expect(result.current.computing).toBe(false)
|
||||
})
|
||||
|
||||
it('卸载时终止 worker 并清理去抖定时器', () => {
|
||||
it('去抖期内卸载:worker 从未创建且不再派发', () => {
|
||||
const factory = makeFactory()
|
||||
const left = heavyText(2100)
|
||||
const hook = renderHook(() => useDiff(left, '', NO_OPTIONS, factory))
|
||||
const w = factory.workers[0]
|
||||
hook.unmount()
|
||||
expect(w.terminated).toBe(true)
|
||||
// 卸载后去抖到期不再派发
|
||||
expect(factory.workers).toHaveLength(0)
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
expect(w.posted).toHaveLength(0)
|
||||
expect(factory.workers).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('去抖到期派发后卸载:worker 被终止', () => {
|
||||
const factory = makeFactory()
|
||||
const left = heavyText(2100)
|
||||
const hook = renderHook(() => useDiff(left, '', NO_OPTIONS, factory))
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(250)
|
||||
})
|
||||
const w = factory.workers[0]
|
||||
expect(w.posted).toHaveLength(1)
|
||||
hook.unmount()
|
||||
expect(w.terminated).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,7 +19,8 @@ export interface DiffState {
|
||||
/**
|
||||
* worker 化的 diff 计算 hook。
|
||||
* 小输入同步快路径(行为与 worker 化之前完全一致);
|
||||
* 大输入去抖后派发 worker 后台计算,输入变化时终止旧 worker 重建(JS worker 无法中断计算,销毁重建即取消),
|
||||
* 大输入去抖到期后才创建 worker 并派发后台计算(去抖期内取消的任务零创建开销),
|
||||
* 输入变化时终止旧 worker 重建(JS worker 无法中断计算,销毁重建即取消),
|
||||
* 响应按 jobId 匹配丢弃过期任务;worker 不可用的环境回退主线程同步计算。
|
||||
* workerFactory 仅测试注入用,生产调用保持缺省;注入时需保证引用稳定。
|
||||
*/
|
||||
@@ -49,16 +50,19 @@ export function useDiff(
|
||||
if (!heavy) return
|
||||
// 重输入先清空旧结果:避免交换左右等场景下展示与新面板不对应的过期差异
|
||||
setAsyncDiff(null)
|
||||
const w = factory()
|
||||
if (!w) {
|
||||
// 无 Worker 支持(jsdom/老环境):主线程同步回退
|
||||
setAsyncDiff(computeDiff(left, right, options))
|
||||
setComputing(false)
|
||||
return
|
||||
}
|
||||
const jobId = ++jobIdRef.current
|
||||
setComputing(true)
|
||||
let w: Worker | null = null
|
||||
// worker 延迟到去抖到期才创建:内联 worker 实例化含 base64 解码与脚本编译,
|
||||
// 去抖期内输入再变时旧任务未创建即取消,零创建与计算开销
|
||||
const timer = window.setTimeout(() => {
|
||||
w = factory()
|
||||
if (!w) {
|
||||
// 无 Worker 支持(jsdom/老环境):主线程同步回退
|
||||
setAsyncDiff(computeDiff(left, right, options))
|
||||
setComputing(false)
|
||||
return
|
||||
}
|
||||
const jobId = ++jobIdRef.current
|
||||
w.onmessage = (e: MessageEvent<DiffWorkerResponse>): void => {
|
||||
// 丢弃过期任务:输入已变化时旧计算结果不再生效
|
||||
if (e.data.jobId !== jobId) return
|
||||
@@ -69,7 +73,7 @@ export function useDiff(
|
||||
}, DEBOUNCE_MS)
|
||||
return () => {
|
||||
window.clearTimeout(timer)
|
||||
w.terminate()
|
||||
w?.terminate()
|
||||
}
|
||||
}, [left, right, options, heavy, factory])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user