refactor(fields): build the published stylesheet with the shared builder - #6440
Merged
Merged
Conversation
`packages/fields/scripts/build-css.mjs` carried its own copy of the
components-sheet subtraction — the `var()` fallback normalisation, the
at-rule context keys and the write-time assertions — which
`scripts/build-plugin-stylesheet.mjs` generalised over `packageRoot` for
`@object-ui/plugin-grid` and `@object-ui/plugin-kanban`. Two implementations
of one subtraction meant a fix to either could silently not reach the other.
Fields now runs the shared builder. It keeps only what is specific to it:
its `MUST_SURVIVE` sentinels, its `CLASS_CEILING` of 600, the objectui#4059
rationale, and its stylesheet banner — the last through a new documented
`build({ header })` hook on the shared module rather than a retained second
copy of the builder.
`@object-ui/fields` ships this CSS, so the acceptance gate was byte identity
of the emitted artifact, not equivalent-looking code. `packages/fields/dist/
index.css` is `git hash-object f9cd6504a9e3a9603836230aea27c5a3a7e4e431`,
22761 bytes, `167 rules kept (163 classes)`, before and after — and now from
any working directory, since the shared `compile` pins Tailwind's source
detection to `packageRoot` instead of the process cwd.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6405
packages/fields/scripts/build-css.mjscarried its own copy of the components-sheetsubtraction — the
var()fallback normalisation, the at-rule context keys, the classextraction and the write-time assertions — which PR #6403 generalised over
packageRootinto
scripts/build-plugin-stylesheet.mjsfor@object-ui/plugin-gridand@object-ui/plugin-kanban. Fields now runs that shared builder, so one implementation ofthe subtraction exists and a fix to it reaches every package that ships a supplement
sheet.
The acceptance gate: byte identity of the published artifact
@object-ui/fieldsships this CSS to consumers, so the whole safety argument is that theemitted file does not change — not that the code looks equivalent. Measured, not eyeballed:
git hash-object packages/fields/dist/index.cssdd76dc3e0, this file untouched)f9cd6504a9e3a9603836230aea27c5a3a7e4e43122.22 kB — 167 rules kept (163 classes), 1358 rules + 75 at-rules already in @object-ui/components' sheet12eea094d)f9cd6504a9e3a9603836230aea27c5a3a7e4e431cmpagainst a snapshot of the pre-change build reports no difference, andsha256sumagrees (
5b250603e846f0798bc62a7b2bdcadd15bfeb5d7dc965f98695428488ebdb326both ways). Thedist/index.csswas deleted before each rebuild, so the second reading is a file the newcode actually wrote rather than a stale artifact.
Reproduced through the real build script (
pnpm --filter @object-ui/fields build), notonly the CSS step in isolation.
The gate is capable of failing — verified by ablation
A byte check nobody has seen go red is not a measurement. Dropping the one fields-specific
input this change adds (
header: HEADERinbuildOptions, so fields inherits the shareddefault banner) and rebuilding:
83163c2b1e436abde5bcda679b2f0ef0ab4d74e9— caught;22.22 kB — 167 rules kept (163 classes)— blind.The size/rule reading the card names as "the reading to reproduce" does not move for a
three-line banner difference in a published file. That is the concrete case for comparing
by hash. The mutation was confirmed on disk before the build (subject blob
2bb3e0ba…→b92b5bf8…, removed-text grep count 0, surviving-anchor count 1) and therestore confirmed after (blob back to
2bb3e0ba…,git diff HEADempty).The two sibling sheets are unaffected
This PR edits shared code that
plugin-gridandplugin-kanbanalso run, so their outputwas checked the same way: built with this branch's module, then rebuilt with the module
restored from
dd76dc3e0.plugin-grid—284dca92590193b39858dbc8e7b9425c5806658dboth ways (16.30 kB, 128 rules / 126 classes)plugin-kanban—8cebeb40d04c7504a19479ec77c5037143ddb599both ways (11.41 kB, 52 rules / 50 classes)What fields does that the shared module did not model
Four things. None of them is a retained second copy — each went into the shared module or
into the wrapper as package-specific input.
header()names a different producing script andwords the "supplement, not standalone" note differently. Those exact bytes have been at
the top of a published file since @object-ui/fields declares a
./style.cssexport but nothing buildsdist/index.cssfor it — the subpath quick-start teaches looks unresolvable #4059. → a documented per-package hook:build({ header })on the shared module, defaulted at the destructure todefaultHeader(packageName), with the reason for the hook's existence written abovedefaultHeader. Fields passesHEADER; a package with no such history passes nothingand inherits the default.
MUST_SURVIVEandCLASS_CEILING600, with their rationale — six named specimensspanning both reasons a rule can be fields-only, and a leak ceiling carrying the
main@59df371f7measurement (157 correct, 1923 with thesource(none)pin absent). →stay in the wrapper as this package's inputs, exactly as the two plugins hold theirs.
Not flattened into shared defaults.
deleted (155 fields-only classes, 17 of them resolving unpublished
@themetokens), the~180 kB duplication the narrow shape avoids, and the coupling statement. → stays in the
wrapper. What was removed from it is only the prose describing the algorithm that no
longer lives in this file; that description now has one home, next to the code.
postcss.process(css, { to: output })and nobase. The sharedcompilepassesbase: packageRootand noto. Both differences were measured before the rewrite, bycompiling fields' entry three ways: fields' current call, the shared call, and the
shared call based at the repo root. All three produced 182026 identical bytes — the
cwd-independence
src/index.css'ssource(none)comment claims, now also pinned by thebaseargument. So there was nothing to carry over, and the sheet is now provably thesame from any working directory (
node packages/fields/scripts/build-css.mjsrun fromthe repo root emits the same hash).
Two smaller alignments, both inside the shared module rather than beside it: the builder's
failure messages cited only objectui#4929, which is the wrong card to send a fields build
to, so they now cite objectui#4059 alongside it; and the module's "why fields is not on
this module" section was describing a state that no longer exists.
The wrapper also adopts the sibling shape —
isEntrypoint-guarded CLI half withPACKAGE_ROOT/MUST_SURVIVE/CLASS_CEILING/builder/buildOptionsexported —so the three build scripts are the same file with different constants.
Changeset: declared as no release, deliberately
.changeset/6405-fields-shared-stylesheet-builder.mdhas empty frontmatter.The gate does not demand one at all —
check-changeset-presence.mjsreports "0 of thempublished source of a package the release covers", because a package's
scripts/directory is neither under its
src/nor in fields'fileslist. The declaration is thereanyway, because the question a reader will have is not whether the gate fired but whether a
build-script change to a published package ships something, and silence answers that by
inference. It ships nothing: the artifact consumers install is byte-identical, so there is
no behaviour to release, and a bump would put a version in the changelog of all 39
fixed-group packages describing a file that did not change.
Verification
Run on
12eea094d, the final commit:pnpm exec vitest run --maxWorkers=2 packages/fields/—Test Files 113 passed (113),Tests 1850 passed (1850)pnpm exec vitest run --maxWorkers=2 scripts/__tests__/—Test Files 81 passed (81),Tests 2317 passed (2317)pnpm --filter @object-ui/fields build— fulltsc && vite build && node scripts/build-css.mjs, emitting the hash abovepnpm --filter @object-ui/fields type-check— cleanpnpm type-check:scripts— clean, after it caught a real regression: withheadertaking no default, the inferred options type made it required, and both existing
callers went red. Defaulting it at the destructure is what keeps it optional.
pnpm exec eslint scripts/build-plugin-stylesheet.mjs packages/fields/scripts/build-css.mjs— cleannode scripts/check-changeset-presence.mjs—✅ No source of a released package changed in this rangeWorth stating plainly:
pnpm --filter @object-ui/fields type-checksays nothing aboutthe file this PR rewrites —
packages/fields/tsconfig.jsonhas"include": ["src"]. Whatactually types
scripts/build-plugin-stylesheet.mjsispnpm type-check:scripts, throughthe pin test's import, which is why the red above appeared there and only there.
Out of scope
scripts/__tests__/plugin-published-stylesheet.test.tspins the two plugin sheets and not@object-ui/fields— the one that has actually been published since #4059. Newly cheap toclose now that fields exports the same module shape, but a different change with a
different review. Filed separately as #6438 (unassigned).
Generated by Claude Code