incrementalCompiler
uses tcmts.core.program.providedPaths, which uses node:fs
Compile files: a snapshot of them (overlay text over the disk) in the compiler process. Snapshots read the disk
afresh, and the compiler keeps what it parsed (the standard library, @types/node) between them, so a new one is
quick. A compilation stays usable until the same files are compiled again (an editor's open files each keep
theirs), or until it's one of more than keep others since.
import { providedPaths } from "#codebase/tcmts/core/program"; import type { Compile } from "#codebase/tcmts/core/program"; import { compiler } from "#codebase/tcmts/core/ts"; import type { ModuleResolver as ts_ModuleResolver, Snapshot as ts_Snapshot } from "typescript/sync"; export function incrementalCompiler(keep = 8): Compile { const live = new Map<string, ts_Snapshot>(); // resolving provided packages: one resolver per mapping and options, made again when either changes let resolver: { key: string; resolver: ts_ModuleResolver } | undefined; const release = (s: ts_Snapshot | undefined) => { try { s?.dispose(); } catch { // the compiler has already let it go } }; return (rootFiles, options, overlay, packages) => { const resolverKey = `${packages?.key ?? ""}\0${JSON.stringify(options)}`; const paths = providedPaths(rootFiles, options, overlay, packages, () => { if (resolver?.key !== resolverKey) { resolver?.resolver.dispose(); resolver = { key: resolverKey, resolver: compiler().createModuleResolver(options as never), }; } return resolver.resolver; }); const compilerOptions = paths ? { ...options, paths: { ...options.paths, ...paths } } : options; const files: Record<string, string> = {}; for (const [f, text] of overlay ?? []) files[f] = text; const snapshot = compiler().createSnapshot({ ...(overlay?.size ? { fileSystem: { kind: "layer", files } } : {}), createPrograms: [{ rootFiles, compilerOptions: compilerOptions as never }], } as never) as unknown as ts_Snapshot; const project = snapshot.getProjects()[0]!; const key = rootFiles.join("\0"); release(live.get(key)); live.delete(key); live.set(key, snapshot); for (const [k, s] of live) { if (live.size <= keep) break; release(s); live.delete(k); } return { program: project.program, checker: project.checker, service: project.languageService, options: compilerOptions, dispose: () => { if (live.get(key) !== snapshot) return; release(snapshot); live.delete(key); }, }; }; }