8000 only one worker for REPL · immutable-js/immutable-js@ca8e157 · GitHub
[go: up one dir, main page]

Skip to content

Commit ca8e157

Browse files
committed
only one worker for REPL
1 parent 7de9cc0 commit ca8e157

File tree

4 files changed

+139
-48
lines changed

4 files changed

+139
-48
lines changed

website/src/app/WorkerContext.tsx

Lines changed: 110 additions & 0 deletions
< 10000 tr class="diff-line-row">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
'use client';
2+
import { Element, JsonMLElementList } from '../worker/jsonml-types';
3+
import React, {
4+
createContext,
5+
JSX,
6+
useCallback,
7+
useEffect,
8+
useMemo,
9+
useRef,
10+
useState,
11+
} from 'react';
12+
13+
type Props = {
14+
children: React.ReactNode;
15+
};
16+
17+
type OnSuccessType = (result: JsonMLElementList | Element) => void;
18+
19+
type WorkerContextType = {
20+
runCode: (code: string, onSuccess: OnSuccessType) => void;
21+
};
22+
23+
const WorkerContext = createContext<null | WorkerContextType>(null);
24+
25+
export function useWorkerContext() {
26+
const context = React.useContext(WorkerContext);
27+
28+
if (!context) {
29+
throw new Error('useWorkerContext must be used within a WorkerProvider');
30+
}
31+
32+
return context;
33+
}
34+
35+
export function WorkerContextProvider({ children }: Props): JSX.Element {
36+
const workerRef = useRef<Worker | null>(null);
37+
const [successMap, setSuccessMap] = useState<Map<string, OnSuccessType>>(
38+
new Map()
39+
);
40+
41+
useEffect(() => {
42+
// Create a worker from the external worker.js file
43+
workerRef.current = new Worker(
44+
new URL('../worker/index.ts', import.meta.url)
45+
);
46+
47+
workerRef.current.onmessage = (event: {
48+
data: {
49+
key: string;
50+
output: JsonMLElementList | Element;
51+
error?: string;
52+
};
53+
}) => {
54+
const onSuccess = successMap.get(event.data.key);
55+
56+
if (!onSuccess) {
57+
console.warn(
58+
`No success handler found for key: ${event.data.key}. This is an issue with the single REPL worker.`
59+
);
60+
61+
return;
62+
}
63+
64+
if (event.data.error) {
65+
onSuccess(['div', 'Error: ' + event.data.error]);
66+
} else {
67+
const { output } = event.data;
68+
69+
if (typeof output === 'object' && !Array.isArray(output)) {
70+
onSuccess(['div', { object: output }]);
71+
} else {
72+
onSuccess(output);
73+
}
74+
}
75+
};
76+
77+
return () => {
78+
workerRef.current?.terminate();
79+
};
80+
}, []);
81+
82+
const runCode = useCallback(
83+
(code: string, onSuccess: OnSuccessType): void => {
84+
const key = Math.random().toString(36).substring(2, 15);
85+
86+
setSuccessMap((successMap) => successMap.set(key, onSuccess));
87+
88+
// ignore import statements as we do unpack all immutable data in the worker
89+
// but it might be useful in the documentation
90+
const cleanedCode = code; // .replace(/^import.*/m, '');
91+
92+
// send message to worker
93+
if (workerRef.current) {
94+
workerRef.current.postMessage({ code: cleanedCode, key });
95+
}
96+
},
97+
[]
98+
);
99+
100+
const value = useMemo(
101+
() => ({
102+
runCode,
103+
}),
104+
[runCode]
105+
);
106+
107+
return (
108+
<WorkerContext.Provider value={value}>{children}</WorkerContext.Provider>
109+
);
110+
}

website/src/app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Metadata } from 'next';
22
import React from 'react';
3+
import { WorkerContextProvider } from './WorkerContext';
34
import '../../styles/globals.css';
45
import '../../styles/prism-theme.css';
56

@@ -19,7 +20,9 @@ export default function RootLayout({
1920
}) {
2021
return (
2122
<html lang="en">
22-
<body>{children}</body>
23+
<body>
24+
<WorkerContextProvider>{children}</WorkerContextProvider>
25+
</body>
2326
</html>
2427
);
2528
}

website/src/repl/Repl.tsx

Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
'use client';
22
import dynamic from 'next/dynamic';
3-
import React, { useEffect, useRef, useState, type JSX } from 'react';
3+
import React, { useCallback, useEffect, useState, type JSX } from 'react';
44
import { Editor } from './Editor';
55
import FormatterOutput from './FormatterOutput';
6-
import './repl.css';
76
import { Element, JsonMLElementList } from '../worker/jsonml-types';
7+
import { useWorkerContext } from '../app/WorkerContext';
8+
import './repl.css';
89

910
type Props = {
1011
defaultValue: string;
@@ -15,52 +16,24 @@ type Props = {
1516
function Repl({ defaultValue, onRun, imports }: Props): JSX.Element {
1617
const [code, setCode] = useState<string>(defaultValue);
1718
const [output, setOutput] = useState<JsonMLElementList | Element>([]);
18-
const workerRef = useRef<Worker | null>(null);
19+
const { runCode: workerRunCode } = useWorkerContext();
1920

20-
useEffect(() => {
21-
// Create a worker from the external worker.js file
22-
workerRef.current = new Worker(
23-
new URL('../worker/index.ts', import.meta.url)
24-
);
21+
const onSuccess = (result: JsonMLElementList | Element): void => {
22+
if (onRun) {
23+
onRun(code);
24+
}
2525

26-
return () => {
27-
workerRef.current?.terminate();
28-
};
29-
}, []);
26+
setOutput(result);
27+
};
28+
29+
const runCode = useCallback(() => {
30+
workerRunCode(code, onSuccess);
31+
}, [workerRunCode]);
3032

3133
useEffect(() => {
3234
runCode();
3335
}, []);
3436

35-
const runCode = () => {
36-
if (workerRef.current) {
37-
// ignore import statements as we do unpack all immutable data in the worker
38-
// but it might be useful in the documentation
39-
const cleanedCode = code; // .replace(/^import.*/m, '');
40-
41-
// notify parent
42-
if (onRun) {
43-
onRun(cleanedCode);
44-
}
45-
46-
// send message to worker
47-
workerRef.current.postMessage(cleanedCode);
48-
workerRef.current.onmessage = (event) => {
49-
if (event.data.error) {
50-
setOutput(['div', 'Error: ' + event.data.error]);
51-
} else {
52-
const { output } = event.data;
53-
54-
if (typeof output === 'object' && !Array.isArray(output)) {
55-
setOutput(['div', { object: output }]);
56-
} else {
57-
setOutput(output);
58-
}
59-
}
60-
};
61-
}
62-
};
63-
6437
return (
6538
<div className="js-repl">
6639
<h4>Live example</h4>

website/src/worker/index.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,13 @@ immutableDevTools(Immutable);
8080
// hack to get the formatters from immutable-devtools as they are not exported, but they modify the "global" variable
8181
const immutableFormaters = globalThis.devtoolsFormatters;
8282

83-
self.onmessage = function (event) {
83+
self.onmessage = function (event: {
84+
data: { code: string; key: string };
85+
}): void {
86+
const { code, key } = event.data;
87+
8488
const timeoutId = setTimeout(() => {
85-
self.postMessage({ error: 'Execution timed out' });
89+
self.postMessage({ key, error: 'Execution timed out' });
8690
self.close();
8791
}, 2000);
8892

@@ -93,8 +97,6 @@ self.onmessage = function (event) {
9397
// globalThis.globalThisKeysBefore = [...Object.keys(globalThis)];
9498
// }
9599

96-
const code = event.data;
97-
98100
// track const and let variables into global scope to record them
99101

100102
// it might make a userland code fail with a conflict.
@@ -129,10 +131,13 @@ self.onmessage = function (event) {
129131

130132
// }
131133

132-
self.postMessage({ output: normalizeResult(immutableFormaters, result) });
134+
self.postMessage({
135+
key,
136+
output: normalizeResult(immutableFormaters, result),
137+
});
133138
} catch (error) {
134139
console.log(error);
135140
clearTimeout(timeoutId);
136-
self.postMessage({ error: String(error) });
141+
self.postMessage({ key, error: String(error) });
137142
}
138143
};

0 commit comments

Comments
 (0)
0