Skip to content

EPBDS-16211 fix watch-lib: same config as the published build - #49

Merged
AlexSamBY merged 1 commit into
masterfrom
fix-watch-lib
Sep 15, 2026
Merged

AlexSamBY merged 1 commit into
masterfrom
fix-watch-lib

Conversation

@AlexSamBY

Copy link
Copy Markdown
Member

watch-lib had its own webpack config, and it had drifted. Measured against build-lib rather than read:

build-lib watch-lib (before)
dist/*.d.ts 2 0
stylesheet dist/static/all.css dist/static/ui-render.css
font.css, semantic stub, root-static re-exports yes no

All three matter for the one thing watch-lib exists for — a consumer yalc-linking the package to try a pre-release. They would get a stylesheet at a path neither the README nor the published package uses, no types at all, and a root static/ left over from an earlier build-lib that watch never refreshes: correct-looking and silently stale.

The types were the subtle one. Both configs set output.clean: true; build-lib gets away with it because gen-ts runs immediately after webpack. In watch mode nothing regenerates them, so every rebuild wiped the declarations a previous build-lib had produced.

Fixed by deleting the duplicate, not by syncing it

webpack.watch.config.mjs is gone and watch-lib uses webpack.library.config.mjs — which was already watch-aware, tapping watchRun as well as beforeRun. Two supporting changes: output.clean becomes { keep: /\.d\.ts$/ } so declarations survive a rebuild (a no-op for build-lib, where gen-ts rewrites them anyway), and watch-lib runs gen-ts once before starting the watcher.

Verified the way the divergence was found: afterwards watch-lib produces an identical file list to build-lib — 8 files, 2 .d.ts, stylesheet at dist/static/all.css re-exporting the root copy — and the published CSS keeps its sha256.

Also fixes a read-after-check race behind today's intermittent failures

css.pipeline.parity.test.js tested existsSync at module load and readFileSync in a beforeAll. Something empties the root static/ directory between those moments in a full-suite run, so the describe was not skipped and three tests died on ENOENT — invisible in an isolated run, and it reads like a broken assertion rather than a missing file. The file is now read once, at load: the suite either has the bytes it checked for, or it skips.

What is established about the emptying, and what is not, is written into that file rather than guessed:

  • it is reliably triggered by the LESS render in css.semantic-parity.test.js — bisected suite by suite, then by disabling that suite's compile (off: the directory survives; on: emptied every time);
  • it is not done through fs in the jest process — probes around rmSync/unlinkSync/rmdirSync/renameSync and their async and fs.promises forms, installed both in setupFiles and inside the suite itself, never fire, and no watcher process is running.

The mechanism is unexplained and left recorded rather than papered over. CI is unaffected either way: it runs jest before build-lib, so those three always skip there — which is what the describe name says.

Gates

jsdom 167 suites / 2540 tests on React 16.14, 17 and 18.3; Chrome 41/41; lint:js, lint:css, css:fixture:check, both docs checks, build-lib and build-css green; published CSS byte-identical.

🤖 Generated with Claude Code

`watch-lib` had its own webpack config, and it had drifted. Measured against
`build-lib` rather than read:

  dist/*.d.ts      build-lib: 2     watch-lib: 0
  stylesheet       build-lib: dist/static/all.css
                   watch-lib: dist/static/ui-render.css
  font.css, the semantic stub, the root-static re-exports: build-lib only

All three matter for the one thing `watch-lib` exists for — a consumer
`yalc`-linking the package to try a pre-release. They would get a stylesheet at a
path the README and the published package do not use, no types at all, and a
root `static/` left over from an earlier `build-lib` that watch never refreshes:
correct-looking and silently stale.

THE TYPES WERE THE SUBTLE ONE. Both configs set `output.clean: true`; `build-lib`
gets away with it because `gen-ts` runs immediately after webpack. In watch mode
nothing regenerates them, so every rebuild wiped the declarations a previous
`build-lib` had produced.

FIXED BY DELETING THE DUPLICATE, not by syncing it. `webpack.watch.config.mjs` is
gone and `watch-lib` uses `webpack.library.config.mjs`, which was already
watch-aware — it taps `watchRun` as well as `beforeRun`. Two supporting changes:
`output.clean` becomes `{ keep: /\.d\.ts$/ }` so declarations survive a rebuild
(a no-op for `build-lib`, where `gen-ts` rewrites them anyway), and `watch-lib`
runs `gen-ts` once before starting the watcher.

Verified the way the divergence was found: after the change, `watch-lib` produces
an IDENTICAL file list to `build-lib` — 8 files, 2 `.d.ts`, the stylesheet at
`dist/static/all.css` re-exporting the root copy — and the published CSS keeps
its sha256.

ALSO FIXES A READ-AFTER-CHECK RACE that has been producing intermittent failures
all day. `css.pipeline.parity.test.js` tested `existsSync` at module load and
`readFileSync` in a `beforeAll`; something empties the root `static/` directory
between those moments in a full-suite run, so the describe was not skipped and
three tests died on ENOENT — invisible in an isolated run, and it reads like a
broken assertion rather than a missing file. The file is now read once, at load,
so the suite either has the bytes it checked for or skips.

What is established about the emptying, and what is not, is written into that
file rather than guessed: it is reliably triggered by the LESS render in
`css.semantic-parity.test.js` (bisected suite by suite, then by disabling that
suite's compile), and it is NOT done through `fs` in the jest process — probes
around `rmSync`/`unlinkSync`/`rmdirSync`/`renameSync` and their async and
`fs.promises` forms, installed both in `setupFiles` and inside the suite, never
fire, and no watcher process is running. The mechanism is unexplained. CI is
unaffected either way: it runs jest before `build-lib`, so those three always
skip there.

Gates: jsdom 167 suites / 2540 tests on React 16.14, 17 and 18.3; Chrome 41/41;
`lint:js`, `lint:css`, `css:fixture:check`, both docs checks, `build-lib` and
`build-css` green; published CSS byte-identical.
@AlexSamBY
AlexSamBY merged commit 95268a4 into master Sep 15, 2026
5 checks passed
@AlexSamBY
AlexSamBY deleted the fix-watch-lib branch September 15, 2026 14:44
AlexSamBY added a commit that referenced this pull request Sep 15, 2026
The fix landed in #49; the plan still described it as open in three
places. R15 item 8, F1 step 5's "still open" list and H7 now say what is
true: `webpack.watch.config.mjs` is deleted, `watch-lib` runs the library
config itself, and H7 has two configs to unify rather than three.

Step 5's note also records what fixing watch surfaced — `CleanRootStatic`
tapping `watchRun`, so any source write emptied the root `static/` for
the length of a rebuild. It cost an afternoon and reads as flakiness, so
it is written down where the next person will look.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant