Skip to content

feat(worker): add WorkerDocxodus.prepare() comparison-path warmup#208

Merged
JSv4 merged 1 commit into
mainfrom
feat/worker-prepare-warmup
May 29, 2026
Merged

feat(worker): add WorkerDocxodus.prepare() comparison-path warmup#208
JSv4 merged 1 commit into
mainfrom
feat/worker-prepare-warmup

Conversation

@JSv4
Copy link
Copy Markdown
Owner

@JSv4 JSv4 commented May 29, 2026

Problem

createWorkerDocxodus() warms the .NET WASM runtime but does not exercise the comparison engine, so the first compareDocuments() pays a one-time warmup cost (comparison-assembly initialization + JIT of the diff/XML stack) — roughly 2× the steady-state latency. There was no way to pay this ahead of a user action, so downstream apps (crowdsourced-redlines-js, react-docxodus-viewer, …) each reinvented a workaround: ship a pair of seed .docx fixtures and run a throwaway compare purely for the side effect.

Tracking issue in the consumer: JSv4/crowdsourced-redlines-js#2

Fix

Add an optional, idempotent prepare(): Promise<void> on WorkerDocxodus (option 2/3 from the issue — a .NET-side warmup with no caller IO):

  • New Warmup() [JSExport] on DocumentComparer runs a real WmlComparer.Compare (+ GetRevisions) against two tiny seed documents built in-memory on the .NET side, forcing the full compare path to resolve and JIT. No seed fixtures shipped, no caller inputs.
  • New "prepare" worker message routes to it.
  • WorkerDocxodus.prepare() caches a single in-flight warmup promise so repeated/concurrent calls share it, and a compareDocuments() issued while a prepare() is pending does not double-load.

prepare() is never called automatically — skip it and the first compare absorbs the warmup exactly as before.

Note on framing: the issue described the cost as deferred Docxodus.*.wasm/System.*.wasm fetches. In the current build all 40 assemblies are fetched at runtime init, so the ~3s is really first-execution warmup (assembly init + JIT), not network. prepare() amortizes it the same way; the win shows up as timing, and the "no further .wasm fetches after prepare()" guarantee still holds.

Proof (npm/tests/worker-prepare.spec.ts, all green)

Verified via page-level .wasm request monitoring + in-worker performance.now() timing:

Test Result
After prepare(), a real compare triggers no additional .wasm fetches ✅ loaded: (none)
prepare() makes the first real compare meaningfully faster ✅ cold 1504ms → warm 758ms (~1.9×)
prepare() is idempotent — second call resolves <50ms ✅ first 753ms, second 0.1ms
compareDocuments() while prepare() is in flight does not double-load ✅ 40 requests, 40 unique

Existing worker.spec.ts (create/version/compare) still pass — the harness change is additive.

Surface touched

.NET WASM bridge (DocumentComparer.Warmup) · worker message (docxodus.worker.ts) · proxy (worker-proxy.ts) · types (types.ts) · test harness + new spec · README "First-call warmup" section · CHANGELOG.

Out of scope (per issue)

<link rel=preload> manifest, service-worker caching, and a separate prepareViewer() for the renderer assemblies — follow-ups.

🤖 Generated with Claude Code

createWorkerDocxodus() warms the .NET WASM runtime but does not exercise
the comparison engine, so the first compareDocuments() pays a one-time
warmup cost (comparison-assembly init + JIT of the diff/XML stack) — about
2x steady-state latency. Consumers worked around this by shipping seed
.docx fixtures and running a throwaway compare for the side effect.

Add an optional, idempotent prepare(): Promise<void> on WorkerDocxodus
that pays this cost up front with no caller IO:

- New Warmup() [JSExport] on DocumentComparer runs a real WmlComparer.Compare
  (+ GetRevisions) against two tiny seed documents built in-memory on the
  .NET side, forcing the full compare path to resolve and JIT.
- New "prepare" worker message routes to it.
- WorkerDocxodus.prepare() caches a single in-flight warmup promise, so
  repeated/concurrent calls share it and a compareDocuments() issued while
  prepare() is pending does not double-load.

Verified by npm/tests/worker-prepare.spec.ts (page-level .wasm request
monitoring + in-worker timing): after prepare() a real compare fetches no
additional .wasm; warm first compare ~758ms vs cold ~1504ms; second
prepare() resolves in <50ms; concurrent prepare+compare never double-loads.

Refs JSv4/crowdsourced-redlines-js#2
@JSv4 JSv4 merged commit 9a61d29 into main May 29, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant