Compare commits

...
Author SHA1 Message Date
LukeParkerDev 1919a336cf perf(codemode): load the TypeScript compiler on first transpile
#transpile imported typescript at module level, so every process that
imports @opencode/core (the service, the CLI, the TUI) evaluated the
~11 MiB compiler at startup even though it is only needed when a Code
Mode program is transpiled. Import it inside transpile() instead; both
runtime variants become async and parseProgram runs through
Effect.promise, so a transpile diagnostic still surfaces as the same
PendingThrow defect.

Compiled CLI, serve --service, median of 3: RSS after boot 170.2 -> 158.7
MB; registration ~8 ms earlier.
2026-09-19 00:13:48 +10:00
3 changed files with 14 additions and 13 deletions
+3 -3
View File
@@ -40,7 +40,7 @@ export const executeProgram = <R>(
Scope.make("parallel"),
(scope) =>
Effect.gen(function* () {
const program = parseProgram(code)
const program = yield* Effect.promise(() => parseProgram(code))
const pending = new Pending<R>(scope, builtins.Promise)
const ctx = new Interpreter<R>({ tools, pending, builtins, logs, globals })
const result = (yield* toBoundary(ctx, yield* ctx.run(program))) ?? null
@@ -109,8 +109,8 @@ export const executeProgram = <R>(
})
}
const parseProgram = (code: string): Program => {
const transpiled = transpile(`async function __codemode__() {\n${code}\n}`)
const parseProgram = async (code: string): Promise<Program> => {
const transpiled = await transpile(`async function __codemode__() {\n${code}\n}`)
if (transpiled.error !== undefined) {
throw new PendingThrow("SyntaxError", `Failed to parse TypeScript: ${transpiled.error}`, undefined, "ParseError")
@@ -1,24 +1,25 @@
import { DiagnosticCategory, ModuleKind, ScriptTarget, flattenDiagnosticMessageText, transpileModule } from "typescript"
export interface TranspileResult {
readonly outputText: string
readonly error?: string
}
// Full TypeScript transpilation on node/bun runtimes.
export const transpile = (source: string): TranspileResult => {
const transpiled = transpileModule(source, {
// Full TypeScript transpilation on node/bun runtimes. The compiler is ~11 MiB and takes tens of
// milliseconds to evaluate, so it is loaded when the first program is transpiled rather than when
// the host process starts.
export const transpile = async (source: string): Promise<TranspileResult> => {
const { default: ts } = await import("typescript")
const transpiled = ts.transpileModule(source, {
reportDiagnostics: true,
compilerOptions: {
target: ScriptTarget.ESNext,
module: ModuleKind.ESNext,
target: ts.ScriptTarget.ESNext,
module: ts.ModuleKind.ESNext,
},
})
const diagnostic = transpiled.diagnostics?.find((item) => item.category === DiagnosticCategory.Error)
const diagnostic = transpiled.diagnostics?.find((item) => item.category === ts.DiagnosticCategory.Error)
if (diagnostic) {
return {
outputText: transpiled.outputText,
error: flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
error: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
}
}
return { outputText: transpiled.outputText }
@@ -8,4 +8,4 @@ export interface TranspileResult {
// untranspiled. Plain-JS programs (the overwhelmingly common case) parse
// fine downstream via acorn; TypeScript-only syntax surfaces as a parse
// error from the interpreter instead of a transpile diagnostic.
export const transpile = (source: string): TranspileResult => ({ outputText: source })
export const transpile = async (source: string): Promise<TranspileResult> => ({ outputText: source })