feat(client): tsx compilation (#62236)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Anna
2025-10-13 15:29:52 -04:00
committed by GitHub
parent b45ed39cd9
commit af70a07547
9 changed files with 149 additions and 14 deletions
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
import { type VirtualTypeScriptEnvironment } from '@typescript/vfs';
import type { CompilerOptions, CompilerHost } from 'typescript';
import reactTypes from './react-types.json';
// Most of the ts types are only a guideline. This is because we're not bundling
// TS in this worker. The specific TS version is going to be determined by the
@@ -40,7 +41,7 @@ interface CancelEvent extends MessageEvent {
}
// Pin at the latest TS version available as cdnjs doesn't support version range.
const TS_VERSION = '5.7.3';
const TS_VERSION = '5.9.2';
let tsEnv: VirtualTypeScriptEnvironment | null = null;
let compilerHost: CompilerHost | null = null;
@@ -54,7 +55,12 @@ importScripts(
function importTS(version: string) {
if (cachedVersion == version) return;
importScripts(
`https://cdnjs.cloudflare.com/ajax/libs/typescript/${version}/typescript.min.js`
/* typescript.min.js fails with
typescript.min.js:320 Uncaught TypeError: Class constructors cannot be invoked without 'new'
so we're using the non-minified version for now. */
`https://cdnjs.cloudflare.com/ajax/libs/typescript/${version}/typescript.js`
);
cachedVersion = version;
}
@@ -63,10 +69,13 @@ 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?
module: ts.ModuleKind.Preserve, // Babel is handling module transformation, so TS should leave them alone.
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?
// from the docs: "Note: it's possible for this list to get out of
// 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.
};
const fsMap = await createDefaultMapFromCDN(
compilerOptions,
@@ -75,6 +84,12 @@ async function setupTypeScript() {
ts
);
// This can be any path, but doing this means import React from 'react' works, if we ever need it.
const reactTypesPath = `/node_modules/@types/react/index.d.ts`;
// It may be necessary to get all the types (global.d.ts etc)
fsMap.set(reactTypesPath, reactTypes['react-18'] || '');
const system = tsvfs.createSystem(fsMap);
// TODO: if passed an invalid compiler options object (e.g. { module:
// ts.ModuleKind.CommonJS, moduleResolution: ts.ModuleResolutionKind.NodeNext
@@ -82,10 +97,11 @@ async function setupTypeScript() {
// show them the diagnostics from this function.
const env = tsvfs.createVirtualTypeScriptEnvironment(
system,
[],
[reactTypesPath],
ts,
compilerOptions
);
compilerHost = createVirtualCompilerHost(
system,
compilerOptions,
@@ -133,11 +149,12 @@ function handleCompileRequest(data: TSCompileEvent['data'], port: MessagePort) {
// TODO: If creating the file fresh each time is too slow, we can try checking
// if the file exists and updating it if it does.
tsEnv?.createFile('index.ts', code);
// TODO: make sure the .tsx extension doesn't cause issues with vanilla TS.
tsEnv?.createFile('/index.tsx', code);
const program = tsEnv!.languageService.getProgram()!;
const emitOutput = tsEnv!.languageService.getEmitOutput('index.ts');
const emitOutput = tsEnv!.languageService.getEmitOutput('index.tsx');
const compiled = emitOutput.outputFiles[0].text;
const message: TSCompiledMessage = {