Skip to content

Commit c7936b4

Browse files
refactor(client): allow TS worker to initialize itself and have the client check readiness (freeCodeCamp#57055)
1 parent e32c3d9 commit c7936b4

File tree

9 files changed

+44
-32
lines changed

9 files changed

+44
-32
lines changed

client/src/templates/Challenges/rechallenge/transformers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import { WorkerExecutor } from '../utils/worker-executor';
2121
import {
2222
compileTypeScriptCode,
23-
initTypeScriptService
23+
checkTSServiceIsReady
2424
} from '../utils/typescript-worker-handler';
2525

2626
const { filename: sassCompile } = sassData;
@@ -146,7 +146,7 @@ const babelTransformer = loopProtectOptions => {
146146
testTypeScript,
147147
async challengeFile => {
148148
await loadBabel();
149-
await initTypeScriptService();
149+
await checkTSServiceIsReady();
150150
const babelOptions = getBabelOptions(presetsJS, loopProtectOptions);
151151
return flow(
152152
partial(transformHeadTailAndContents, compileTypeScriptCode),

client/src/templates/Challenges/utils/typescript-worker-handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ export function compileTypeScriptCode(code: string): Promise<string> {
3030
});
3131
}
3232

33-
export function initTypeScriptService(): Promise<boolean> {
33+
export function checkTSServiceIsReady(): Promise<boolean> {
3434
return awaitResponse({
3535
worker: getTypeScriptWorker(),
36-
message: { type: 'init' },
36+
message: { type: 'check-is-ready' },
3737
onMessage: (data, onSuccess) => {
3838
if (data.type === 'ready') {
3939
onSuccess(true);

tools/client-plugins/browser-scripts/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PyodideInterface } from 'pyodide';
1+
import type { PyodideInterface } from 'pyodide';
22

33
export interface FrameDocument extends Document {
44
__initTestFrame: (e: InitTestFrameArg) => Promise<void>;

tools/client-plugins/browser-scripts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
"main": "index.js",
2121
"scripts": {
2222
"test": "echo \"Error: no test specified\" && exit 1",
23-
"build": "NODE_OPTIONS=\"--max-old-space-size=7168\" webpack -c webpack.config.js"
23+
"build": "NODE_OPTIONS=\"--max-old-space-size=7168\" webpack -c webpack.config.cjs"
2424
},
25+
"type": "module",
2526
"keywords": [],
2627
"devDependencies": {
2728
"@babel/plugin-syntax-dynamic-import": "7.8.3",

tools/client-plugins/browser-scripts/python-worker.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
// We have to specify pyodide.js because we need to import that file (not .mjs)
2-
// and 'import' defaults to .mjs
2+
// and 'import' defaults to .mjs.
3+
4+
// This is to do with how webpack handles node fallbacks - it uses the node
5+
// resolution algorithm to find the file, but that requires the full file name.
6+
// We can't add the extension, because it's in a bundle we're importing. However
7+
// we can import the .js file and then the strictness does not apply.
38
import { loadPyodide, type PyodideInterface } from 'pyodide/pyodide.js';
49
import pkg from 'pyodide/package.json';
510
import type { PyProxy, PythonError } from 'pyodide/ffi';
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
{
22
"compilerOptions": {
33
"target": "es2022",
4-
"module": "CommonJS",
4+
"module": "ESNext",
5+
"moduleResolution": "Bundler",
56
"lib": ["WebWorker", "DOM"],
67
"allowJs": true,
78
"strict": true,
89
"forceConsistentCasingInFileNames": true,
910
"esModuleInterop": true,
1011
"resolveJsonModule": true,
11-
"noEmit": true
12+
"noEmit": true,
13+
"paths": {
14+
"pyodide/ffi": ["./node_modules/pyodide/ffi.d.ts"] // Remove after updating pyodide (later versions correctly export ffi)
15+
}
1216
}
1317
}

tools/client-plugins/browser-scripts/typescript-worker.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ declare const ts: typeof import('typescript');
1313
const ctx: Worker & typeof globalThis = self as unknown as Worker &
1414
typeof globalThis;
1515

16-
let tsEnv: VirtualTypeScriptEnvironment | null = null;
17-
let compilerHost: CompilerHost | null = null;
18-
1916
interface TSCompileEvent extends MessageEvent {
2017
data: {
2118
type: 'compile';
@@ -29,9 +26,9 @@ interface TSCompiledMessage {
2926
error: string;
3027
}
3128

32-
interface InitRequestEvent extends MessageEvent {
29+
interface CheckIsReadyRequestEvent extends MessageEvent {
3330
data: {
34-
type: 'init';
31+
type: 'check-is-ready';
3532
};
3633
}
3734

@@ -42,15 +39,23 @@ interface CancelEvent extends MessageEvent {
4239
};
4340
}
4441

42+
const TS_VERSION = '5'; // hardcoding for now, in the future this may be dynamic
43+
44+
let tsEnv: VirtualTypeScriptEnvironment | null = null;
45+
let compilerHost: CompilerHost | null = null;
4546
let cachedVersion: string | null = null;
4647

47-
async function setupTypeScript(version: string) {
48-
// TODO: make sure no racing happens if multiple inits arrive at once.
49-
if (cachedVersion == version) return tsEnv;
48+
// NOTE: vfs.globals must only be imported once, otherwise it will throw.
49+
importScripts('https://unpkg.com/@typescript/[email protected]/dist/vfs.globals.js');
5050

51+
function importTS(version: string) {
52+
if (cachedVersion == version) return;
5153
importScripts('https://unpkg.com/typescript@' + version);
52-
importScripts('https://unpkg.com/@typescript/[email protected]/dist/vfs.globals.js');
54+
cachedVersion = version;
55+
}
5356

57+
async function setupTypeScript() {
58+
importTS(TS_VERSION);
5459
const compilerOptions: CompilerOptions = {
5560
target: ts.ScriptTarget.ES2015,
5661
skipLibCheck: true // TODO: look into why this is needed. Are we doing something wrong? Could it be that it's not "synced" with this TS version?
@@ -87,34 +92,31 @@ async function setupTypeScript(version: string) {
8792
// We freeze this to prevent learners from getting the worker into a
8893
// weird state.
8994
Object.freeze(self);
90-
91-
cachedVersion = version;
9295
return env;
9396
}
9497

95-
// TODO: figure out how to start setting up TS in the background, but allow the
96-
// client to wait for it to be ready. Currently the waiting works, but the setup
97-
// is done on demand.
98-
// void setupTypeScript('5');
99-
100-
ctx.onmessage = (e: TSCompileEvent | InitRequestEvent | CancelEvent) => {
98+
ctx.onmessage = (
99+
e: TSCompileEvent | CheckIsReadyRequestEvent | CancelEvent
100+
) => {
101101
const { data, ports } = e;
102-
if (data.type === 'init') {
103-
void handleInitRequest(ports[0]);
102+
if (data.type === 'check-is-ready') {
103+
void handleCheckIsReadyRequest(ports[0]);
104104
} else if (data.type === 'cancel') {
105105
handleCancelRequest(data);
106106
} else {
107107
handleCompileRequest(data, ports[0]);
108108
}
109109
};
110110

111+
const isTSSetup = setupTypeScript();
112+
111113
// This lets the client know that there is nothing to cancel.
112114
function handleCancelRequest({ value }: { value: number }) {
113115
postMessage({ type: 'is-alive', text: value });
114116
}
115117

116-
async function handleInitRequest(port: MessagePort) {
117-
await setupTypeScript('5');
118+
async function handleCheckIsReadyRequest(port: MessagePort) {
119+
await isTSSetup;
118120
port.postMessage({ type: 'ready' });
119121
}
120122

tools/client-plugins/browser-scripts/utils/format.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// TODO: this is a straight up copy of the format function from the client.
22
// Figure out a way to share it.
33

4-
import { inspect } from 'util/util';
4+
import { inspect } from 'util/util.js';
55

66
export function format(x) {
77
// we're trying to mimic console.log, so we avoid wrapping strings in quotes:

tools/client-plugins/browser-scripts/webpack.config.js renamed to tools/client-plugins/browser-scripts/webpack.config.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ module.exports = (env = {}) => {
8484
resolve: {
8585
fallback: {
8686
buffer: require.resolve('buffer'),
87-
util: false,
87+
util: require.resolve('util'),
8888
stream: false,
8989
process: require.resolve('process/browser.js')
9090
},

0 commit comments

Comments
 (0)