Header menu logo issie

Build Speed and Fable Caching

How the dev build gets fast startup, and what silently makes it slow again.

The scripts

Every one of these reaches scripts/start.js, which frees the two ports it needs before using them (scripts/free-port.js): 8672 for the webpack dev server and 9222 for the DevTools protocol that scripts/inspect-canvas.js talks to. A session interrupted in any of the usual ways leaves both held, and the failure that follows is worse than it sounds: the port is bound after a full Fable compile and after Electron has been told to open, so the window appears, stays blank, and the log says ready. A stale Electron on 9222 is quieter still — inspect-canvas connects to it and reports the previous run's canvas. Whoever holds the port is killed, whatever it is; that is a more reliable test than matching command lines.

scripts/clean-dev.js is still the tool for sweeping a whole abandoned session, because it also catches a fable watch that holds no port at all and can still recompile the tree under you.

How Fable decides it can start fast

Fable has no on-disk cache of typed ASTs: a cold compile type-checks every file of the Renderer (~200 including dependencies) and takes on the order of a minute. What it does have:

  1. Project cracking cache (build-fable/<project>/fable_modules/project_cracked.json) — restoring project options takes ~200ms instead of several seconds.
  2. Up-to-date detection — if every generated .fs.js is strictly newer than its .fs, a one-shot compile is skipped entirely, and fable watch runs its --run command immediately (recompiling silently in the background to build its watch graph). dev.js uses that --run hook (scripts/fable-ready.js) as the signal to start Electron, so an unchanged tree starts the app in seconds either way.

What breaks it

If the cache seems wedged, dotnet fable clean in the project directory removes the generated files and caches for a genuinely fresh start.

Type something to start searching.