incrementalCompiler

tcmts.core.program.incrementalCompiler · #gpj4rjkf · term · runs on the server

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);
      },
    };
  };
}

uses

used by