mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-12 21:01:52 -04:00
refactor(client): allow TS worker to initialize itself and have the client check readiness (#57055)
This commit is contained in:
committed by
GitHub
parent
e32c3d9a11
commit
c7936b44b9
@@ -20,7 +20,7 @@ import {
|
||||
import { WorkerExecutor } from '../utils/worker-executor';
|
||||
import {
|
||||
compileTypeScriptCode,
|
||||
initTypeScriptService
|
||||
checkTSServiceIsReady
|
||||
} from '../utils/typescript-worker-handler';
|
||||
|
||||
const { filename: sassCompile } = sassData;
|
||||
@@ -146,7 +146,7 @@ const babelTransformer = loopProtectOptions => {
|
||||
testTypeScript,
|
||||
async challengeFile => {
|
||||
await loadBabel();
|
||||
await initTypeScriptService();
|
||||
await checkTSServiceIsReady();
|
||||
const babelOptions = getBabelOptions(presetsJS, loopProtectOptions);
|
||||
return flow(
|
||||
partial(transformHeadTailAndContents, compileTypeScriptCode),
|
||||
|
||||
@@ -30,10 +30,10 @@ export function compileTypeScriptCode(code: string): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
export function initTypeScriptService(): Promise<boolean> {
|
||||
export function checkTSServiceIsReady(): Promise<boolean> {
|
||||
return awaitResponse({
|
||||
worker: getTypeScriptWorker(),
|
||||
message: { type: 'init' },
|
||||
message: { type: 'check-is-ready' },
|
||||
onMessage: (data, onSuccess) => {
|
||||
if (data.type === 'ready') {
|
||||
onSuccess(true);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PyodideInterface } from 'pyodide';
|
||||
import type { PyodideInterface } from 'pyodide';
|
||||
|
||||
export interface FrameDocument extends Document {
|
||||
__initTestFrame: (e: InitTestFrameArg) => Promise<void>;
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "NODE_OPTIONS=\"--max-old-space-size=7168\" webpack -c webpack.config.js"
|
||||
"build": "NODE_OPTIONS=\"--max-old-space-size=7168\" webpack -c webpack.config.cjs"
|
||||
},
|
||||
"type": "module",
|
||||
"keywords": [],
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// We have to specify pyodide.js because we need to import that file (not .mjs)
|
||||
// and 'import' defaults to .mjs
|
||||
// and 'import' defaults to .mjs.
|
||||
|
||||
// This is to do with how webpack handles node fallbacks - it uses the node
|
||||
// resolution algorithm to find the file, but that requires the full file name.
|
||||
// We can't add the extension, because it's in a bundle we're importing. However
|
||||
// we can import the .js file and then the strictness does not apply.
|
||||
import { loadPyodide, type PyodideInterface } from 'pyodide/pyodide.js';
|
||||
import pkg from 'pyodide/package.json';
|
||||
import type { PyProxy, PythonError } from 'pyodide/ffi';
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "CommonJS",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["WebWorker", "DOM"],
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"pyodide/ffi": ["./node_modules/pyodide/ffi.d.ts"] // Remove after updating pyodide (later versions correctly export ffi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ declare const ts: typeof import('typescript');
|
||||
const ctx: Worker & typeof globalThis = self as unknown as Worker &
|
||||
typeof globalThis;
|
||||
|
||||
let tsEnv: VirtualTypeScriptEnvironment | null = null;
|
||||
let compilerHost: CompilerHost | null = null;
|
||||
|
||||
interface TSCompileEvent extends MessageEvent {
|
||||
data: {
|
||||
type: 'compile';
|
||||
@@ -29,9 +26,9 @@ interface TSCompiledMessage {
|
||||
error: string;
|
||||
}
|
||||
|
||||
interface InitRequestEvent extends MessageEvent {
|
||||
interface CheckIsReadyRequestEvent extends MessageEvent {
|
||||
data: {
|
||||
type: 'init';
|
||||
type: 'check-is-ready';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,15 +39,23 @@ interface CancelEvent extends MessageEvent {
|
||||
};
|
||||
}
|
||||
|
||||
const TS_VERSION = '5'; // hardcoding for now, in the future this may be dynamic
|
||||
|
||||
let tsEnv: VirtualTypeScriptEnvironment | null = null;
|
||||
let compilerHost: CompilerHost | null = null;
|
||||
let cachedVersion: string | null = null;
|
||||
|
||||
async function setupTypeScript(version: string) {
|
||||
// TODO: make sure no racing happens if multiple inits arrive at once.
|
||||
if (cachedVersion == version) return tsEnv;
|
||||
// NOTE: vfs.globals must only be imported once, otherwise it will throw.
|
||||
importScripts('https://unpkg.com/@typescript/vfs@1.6.0/dist/vfs.globals.js');
|
||||
|
||||
function importTS(version: string) {
|
||||
if (cachedVersion == version) return;
|
||||
importScripts('https://unpkg.com/typescript@' + version);
|
||||
importScripts('https://unpkg.com/@typescript/vfs@1.6.0/dist/vfs.globals.js');
|
||||
cachedVersion = version;
|
||||
}
|
||||
|
||||
async function setupTypeScript() {
|
||||
importTS(TS_VERSION);
|
||||
const compilerOptions: CompilerOptions = {
|
||||
target: ts.ScriptTarget.ES2015,
|
||||
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,20 +92,15 @@ async function setupTypeScript(version: string) {
|
||||
// We freeze this to prevent learners from getting the worker into a
|
||||
// weird state.
|
||||
Object.freeze(self);
|
||||
|
||||
cachedVersion = version;
|
||||
return env;
|
||||
}
|
||||
|
||||
// TODO: figure out how to start setting up TS in the background, but allow the
|
||||
// client to wait for it to be ready. Currently the waiting works, but the setup
|
||||
// is done on demand.
|
||||
// void setupTypeScript('5');
|
||||
|
||||
ctx.onmessage = (e: TSCompileEvent | InitRequestEvent | CancelEvent) => {
|
||||
ctx.onmessage = (
|
||||
e: TSCompileEvent | CheckIsReadyRequestEvent | CancelEvent
|
||||
) => {
|
||||
const { data, ports } = e;
|
||||
if (data.type === 'init') {
|
||||
void handleInitRequest(ports[0]);
|
||||
if (data.type === 'check-is-ready') {
|
||||
void handleCheckIsReadyRequest(ports[0]);
|
||||
} else if (data.type === 'cancel') {
|
||||
handleCancelRequest(data);
|
||||
} else {
|
||||
@@ -108,13 +108,15 @@ ctx.onmessage = (e: TSCompileEvent | InitRequestEvent | CancelEvent) => {
|
||||
}
|
||||
};
|
||||
|
||||
const isTSSetup = setupTypeScript();
|
||||
|
||||
// This lets the client know that there is nothing to cancel.
|
||||
function handleCancelRequest({ value }: { value: number }) {
|
||||
postMessage({ type: 'is-alive', text: value });
|
||||
}
|
||||
|
||||
async function handleInitRequest(port: MessagePort) {
|
||||
await setupTypeScript('5');
|
||||
async function handleCheckIsReadyRequest(port: MessagePort) {
|
||||
await isTSSetup;
|
||||
port.postMessage({ type: 'ready' });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// TODO: this is a straight up copy of the format function from the client.
|
||||
// Figure out a way to share it.
|
||||
|
||||
import { inspect } from 'util/util';
|
||||
import { inspect } from 'util/util.js';
|
||||
|
||||
export function format(x) {
|
||||
// we're trying to mimic console.log, so we avoid wrapping strings in quotes:
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = (env = {}) => {
|
||||
resolve: {
|
||||
fallback: {
|
||||
buffer: require.resolve('buffer'),
|
||||
util: false,
|
||||
util: require.resolve('util'),
|
||||
stream: false,
|
||||
process: require.resolve('process/browser.js')
|
||||
},
|
||||
Reference in New Issue
Block a user