feat: allow configuration of the typescript-compiler (#66241)

This commit is contained in:
Oliver Eyton-Williams
2026-03-05 05:18:18 +01:00
committed by GitHub
parent c94fe2d40e
commit 2972485a87
5 changed files with 27 additions and 18 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ vi.mock(
await compiler.setup({ useNodeModules: true });
return {
...actual,
checkTSServiceIsReady: () => Promise.resolve(true),
setupTSCompiler: () => Promise.resolve(true),
compileTypeScriptCode: code => {
const { result, error } = compiler.compile(code, 'index.tsx');
if (error) throw error;
@@ -20,7 +20,7 @@ import { version } from '@freecodecamp/browser-scripts/package.json';
import { WorkerExecutor } from './worker-executor';
import {
compileTypeScriptCode,
checkTSServiceIsReady
setupTSCompiler
} from './typescript-worker-handler';
const protectTimeout = 100;
@@ -148,7 +148,7 @@ const getJSXModuleTranspiler = loopProtectOptions => async challengeFile => {
const getTSTranspiler = loopProtectOptions => async challengeFile => {
await loadBabel();
await checkTSServiceIsReady();
await setupTSCompiler();
const babelOptions = getBabelOptions(presetsJS, loopProtectOptions);
return flow(
partial(transformHeadTailAndContents, compileTypeScriptCode),
@@ -159,7 +159,7 @@ const getTSTranspiler = loopProtectOptions => async challengeFile => {
const getTSXModuleTranspiler = loopProtectOptions => async challengeFile => {
await loadBabel();
await loadPresetReact();
await checkTSServiceIsReady();
await setupTSCompiler();
const baseOptions = getBabelOptions(presetsJSX, loopProtectOptions);
const babelOptions = {
...baseOptions,
@@ -31,10 +31,12 @@ export function compileTypeScriptCode(code: string): Promise<string> {
});
}
export function checkTSServiceIsReady(): Promise<boolean> {
export function setupTSCompiler(
compilerOptions?: Record<string, unknown>
): Promise<boolean> {
return awaitResponse({
messenger: getTypeScriptWorker(),
message: { type: 'check-is-ready' },
message: { type: 'setup', ...(compilerOptions && { compilerOptions }) },
onMessage: (data, onSuccess) => {
if (data.type === 'ready') {
onSuccess(true);
@@ -16,10 +16,15 @@ export class Compiler {
this.tsvfs = tsvfs;
}
async setup(opts?: { useNodeModules: boolean }) {
async setup(opts?: { useNodeModules?: boolean; compilerOptions?: unknown }) {
const ts = this.ts;
const tsvfs = this.tsvfs;
const parsedOptions = ts.convertCompilerOptionsFromJson(
opts?.compilerOptions ?? {},
'/'
);
const compilerOptions: CompilerOptions = {
target: ts.ScriptTarget.ES2024,
module: ts.ModuleKind.Preserve, // Babel is handling module transformation, so TS should leave them alone.
@@ -28,7 +33,8 @@ export class Compiler {
// sync with TypeScript over time. It was last synced with TypeScript
// 3.8.0-rc."
jsx: ts.JsxEmit.Preserve, // Babel will handle JSX,
allowUmdGlobalAccess: true // Necessary because React is loaded via a UMD script.
allowUmdGlobalAccess: true, // Necessary because React is loaded via a UMD script.
...parsedOptions.options
};
const fsMap = opts?.useNodeModules
@@ -1,3 +1,4 @@
import type { CompilerOptions } from 'typescript';
import { Compiler } from './modules/typescript-compiler';
// Most of the ts types are only a guideline. This is because we're not bundling
@@ -23,9 +24,10 @@ interface TSCompiledMessage {
error: string;
}
interface CheckIsReadyRequestEvent extends MessageEvent {
interface SetupEvent extends MessageEvent {
data: {
type: 'check-is-ready';
type: 'setup';
compilerOptions?: CompilerOptions;
};
}
@@ -59,12 +61,10 @@ function importTS(version: string) {
cachedVersion = version;
}
ctx.onmessage = (
e: TSCompileEvent | CheckIsReadyRequestEvent | CancelEvent
) => {
ctx.onmessage = (e: TSCompileEvent | SetupEvent | CancelEvent) => {
const { data, ports } = e;
if (data.type === 'check-is-ready') {
void handleCheckIsReadyRequest(ports[0]);
if (data.type === 'setup') {
void handleSetupRequest(data, ports[0]);
} else if (data.type === 'cancel') {
handleCancelRequest(data);
} else {
@@ -75,15 +75,16 @@ ctx.onmessage = (
importTS(TS_VERSION);
const compiler = new Compiler(ts, tsvfs);
const isSetup = compiler.setup();
// This lets the client know that there is nothing to cancel.
function handleCancelRequest({ value }: { value: number }) {
postMessage({ type: 'is-alive', text: value });
}
async function handleCheckIsReadyRequest(port: MessagePort) {
await isSetup;
async function handleSetupRequest(data: SetupEvent['data'], port: MessagePort) {
await compiler.setup({
compilerOptions: data.compilerOptions
});
// We freeze this to prevent learners from getting the worker into a weird
// state.
Object.freeze(self);