Semantic Diff

#9245 Replace lib/bs and lib/ocaml with a flat lib/rescript

3 commits

cb4406e1fa8f35f0f1716ad930bcf4b0b4027668..pr-9245

a9a54fd2d · cknitt + Claude · Nov 06, 2026, 13:35 UTC

Write each package's build artifacts flat to lib/rescript

Why

Every artifact existed in lib/bs, nested like the sources, and again in lib/ocaml, flat, and lib/bs also held copies of the sources and the generated JavaScript (#9151). The build now writes one flat lib/rescript.

modifiedLayout artifact directory of a package

text
Before
After
lib/bs/src/A.ast lib/bs/src/A.cmi lib/bs/src/A.res lib/bs/src/A.jslib/bs/.compiler.log lib/bs/compiler-info.json lib/bs/.sourcedirs.jsonlib/bs/build.ninjalib/ocaml/A.ast lib/ocaml/A.cmi lib/ocaml/A.cmj lib/ocaml/A.cmt lib/ocaml/A.reslib/build.lock lib/watch.locklib/rescript/A.ast lib/rescript/A.cmi lib/rescript/A.cmj lib/rescript/A.cmtlib/rescript/.compiler.log lib/rescript/compiler-info.json lib/rescript/.sourcedirs.jsonlib/rescript/build.lock lib/rescript/watch.lock
text
Before
After
bsc runs in lib/bs with -I ../ocaml; the stdlib is <runtime>/lib/ocamlbsc runs in lib/rescript with -I .; the stdlib is <runtime>/lib/rescript

modifiedPublication a compiled module's ASTs, interface and typed tree

text
Before
After
written to lib/bs by bsc, then copied to lib/ocaml held in the build file store until the request succeeded, then1. the interface, only when it changed2. the typed tree, which marks the module compiledeach renamed into place, or written in place when the rename failsa failed compile: interface forgotten, AST dated back, partial typed tree published

modifiedFreshness Build_freshness, Compiler_info, namespace maps

representation
Before
After
source removed or must be parsed again ↦ AST removedno compiler-info.json, lib/ocaml/.compiler.log exists ↦ cleanNs.mlmap changed or Ns outputs missing ↦ compile namespace source must be parsed again ↦ AST dated back to time 1, always parsed again
compiler-info.json written once the clean decision is made
compile namespace ↦ Ns.cmt removed until the compile publishes itlib/bs or lib/ocaml exists ↦ clean, which removes them

The compiler keeps early cutoff with one copy of each interface: it replaces an interface it cannot read instead of failing, and a .cmj cut short after its digest no longer counts as unchanged. Stale public JavaScript is removed when an AST proves the build generated it; the JS mirror and the build.ninja marker are gone. Clean empties lib/rescript but for the lock files.

Review

  • Why hold ASTs as well as interfaces? A failed parse used to leave its AST in lib/bs only; with one copy it would look current and the next build would skip the parse and lose the error.
  • Why rename into place, with an in-place write only as a fallback? A build killed during an in-place write leaves a truncated file, and a truncated typed tree with a fresh time counts as compiled for good. On Windows the rename fails while an editor has the file open without delete sharing, so the file is then written in place.
  • Why date an AST back instead of removing it? A later compile of the same build or watch session may still read it. Time 1 is below any real source time except the epoch, which is why such an AST is always parsed again.
  • What about a removed module's working .cmi, which cleanup deferred? It went to lib/bs, outside the include path. In lib/rescript it would let dependents compile against a deleted module, so it is removed at once.
805d621a5 · cknitt + Claude · Nov 06, 2026, 13:36 UTC

Read compiled files from the flat lib/rescript in analysis and gentype

Why

The tools found a module's compiled files through the nested lib/bs mirror of the sources and the source copies in lib/ocaml, which the previous commit removes.

Each tool now looks a module up by its compiled file name: - analysis reads <pkg>/lib/rescript/<Module>[-Ns].cmt, the stdlib's compiled files from <runtime>/lib/rescript and its sources from the runtime package root, .sourcedirs.json from lib/rescript, and incremental files from lib/rescript/incremental; - reanalyze scans each package's lib/rescript, and its -cmt-root walk skips the editor's incremental directory; - gentype finds a dependency module's source directory in the header of its AST, read once per build, and maps a namespace module to its package root; it looks an imported module of the same package up as <Name>[-Ns].cmt, trying the compiler's spelling first, and reads typed trees through the build file store, which holds the one being compiled, without keeping them.

Review

  • Why does the reanalyze snapshot move one line? The scan visited the nested source directories first and the build root last; one flat directory is scanned in name order.
5049bbca7 · cknitt + Claude · Nov 06, 2026, 13:36 UTC

Ship the runtime's lib/rescript and sources, and update CI, build tests and docs

Why

The runtime package shipped lib/ocaml with copies of its sources; it now ships lib/rescript and its own sources at the package root, which analysis reads (#9151).

The release artifact, its CI upload and the playground's cmij extraction take lib/rescript, its build state stays out of the package, and the ignore files name lib/rescript. Build tests read the in-source outputs that the JS mirror used to copy, and look for the debug IR, the belt interfaces and post-build paths under lib/rescript; the installation test looks for the watch lock there. The changelog fragment lists the new paths for tools that read the old ones and says that lib/rescript belongs in .gitignore.