Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { TypoWorkerCancelMessage, TypoWorkerIssue, TypoWorkerRequest, TypoWorkerResponse } from './typoWorkerMessages';
import typoWorkerUrl from './typoWorker?worker&url';

type PendingRequest = {
resolve: (value: { issues: TypoWorkerIssue[] }) => void;
Expand All @@ -18,7 +19,18 @@ function createAbortError(): DOMException | Error {

export async function createTypoJsProvider() {
// Run Typo.js work inside a dedicated worker to avoid UI stalls.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth saying why in the comment - the next reader has to open App.tsx to see the lifecycle.

Suggested change
// Run Typo.js work inside a dedicated worker to avoid UI stalls.
// Worker is lazily created/recreated to survive dispose() calls
// (App.tsx reuses the same provider across SuperDoc destroy/recreate).

const worker = new Worker(new URL('./typoWorker.ts', import.meta.url), { type: 'module' });
// Worker is lazily created/recreated to survive dispose() calls.
let worker: Worker | null = null;

function ensureWorker(): Worker {
if (!worker) {
worker = new Worker(typoWorkerUrl, { type: 'module' });
worker.addEventListener('message', handleMessage);
worker.addEventListener('error', handleError);
}
return worker;
}

const pending = new Map<number, PendingRequest>();
let nextRequestId = 0;

Expand Down Expand Up @@ -48,8 +60,7 @@ export async function createTypoJsProvider() {
pending.clear();
};

worker.addEventListener('message', handleMessage);
worker.addEventListener('error', handleError);
ensureWorker();

return {
id: 'typo-js',
Expand Down Expand Up @@ -83,7 +94,7 @@ export async function createTypoJsProvider() {
pending.delete(requestId);
cleanup();
const cancel: TypoWorkerCancelMessage = { type: 'cancel', id: requestId };
worker.postMessage(cancel);
ensureWorker().postMessage(cancel);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureWorker().postMessage(cancel) in the abort handler can spin up a fresh worker just to send a cancel for an id it doesn't know. doesn't fire today (the dispose loop removes the abort listener via entry.cleanup()), but the abort path shouldn't have worker-creating side effects.

Suggested change
ensureWorker().postMessage(cancel);
worker?.postMessage(cancel);

reject(createAbortError());
};

Expand All @@ -104,14 +115,17 @@ export async function createTypoJsProvider() {
},
};

worker.postMessage(payload);
ensureWorker().postMessage(payload);
});
},

dispose() {
worker.removeEventListener('message', handleMessage);
worker.removeEventListener('error', handleError);
worker.terminate();
if (worker) {
worker.removeEventListener('message', handleMessage);
worker.removeEventListener('error', handleError);
worker.terminate();
worker = null;
}

for (const [, entry] of pending) {
entry.cleanup();
Expand Down
9 changes: 9 additions & 0 deletions examples/features/spell-check/typo-js/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,13 @@ import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [react()],
worker: {
format: 'es',
rollupOptions: {
output: {
// Output workers as separate files, not inline base64
entryFileNames: 'assets/[name]-[hash].js',
},
},
},
});
Loading