Skip to content

Publish CSS per area alongside the aggregate stylesheet - #309

Merged
woksin merged 2 commits into
mainfrom
feat/per-area-stylesheets
Sep 22, 2026
Merged

woksin merged 2 commits into
mainfrom
feat/per-area-stylesheets

Conversation

@woksin

@woksin woksin commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

@cratis/components published exactly one stylesheet. An application that mounts two dialogs and a data table downloaded PivotViewer, TimeMachine, Canvas and Chat as well — about 32 KiB gzip of CSS for surfaces it never renders. Every JavaScript subpath now also publishes its own stylesheet at <subpath>/styles, beside one shared @cratis/components/styles/base, so an application pays for what it imports. @cratis/components/styles is unchanged and remains the recommended default; nothing has to migrate.

Added

Changed

Indicative gzip transfer against roughly 32 KiB for the aggregate: about 2 KiB for the shared base, 6.4 KiB for Dialogs, 6.1 KiB for DataTables, 6.7 KiB for PivotViewer, 4.3 KiB for TimeMachine, 2.7 KiB for Filter.

No component's CSS content changed — this is packaging only, and every existing export, part name and token keeps working unchanged.

The package shipped exactly one stylesheet, `dist/esm/styles.css`, holding every
component's rules. An application that mounts two dialogs and a data table
downloaded PivotViewer, TimeMachine, Canvas and Chat as well. The single budget
that guarded it — 205 KiB raw, 32 KiB gzip — was down to roughly 200 gzip bytes
of headroom, the honest reductions were spent, and it had started deciding
authoring questions it has no business deciding: whether a component may have
its own stylesheet, and how many stylesheets a group of collection controls gets.

Every JavaScript subpath now also publishes `<subpath>/styles`, beside one
shared `@cratis/components/styles/base`. The aggregate is untouched in content
and remains the recommended default; this is additive.

The mapping is derived, not written down twice. A stylesheet's owning area is
the first path segment of its `@import` in `Source/styles.css` — the same
segment the exports map already uses for the JavaScript subpath — and a bare
specifier names its area in a `@cratis-area` comment, which only Allotment
needs. A subpath's sheet is then the union of the areas its built module graph
actually reaches, walked with the same closure helper `verify-package-graph.mjs`
uses, so `PivotViewer/styles` carries Filter's rules because PivotViewer renders
a FilterPanel, and nobody has to remember that. The build fails when the
package's stylesheet exports drift from what it emits.

Shared CSS is the compiled Tailwind theme and prefixed utility output plus the
cascade-layer order statement: generated from the whole package's JSX, and an
order that must be established exactly once. That is `styles/base`, imported
once and never duplicated into an area sheet. The `--cratis-*` seam and the
baseline look already had their own entry points and are unchanged.

Budgets follow the same shape. The aggregate keeps its reviewed raw, gzip and
declaration-block ceilings with their measurement history; each per-area sheet
gets its own gzip ceiling, because a 22 KiB PivotViewer stylesheet is fine for
a consumer that mounts a pivot viewer and unacceptable for one that does not.
A new area cannot ship without a measured number, and the emitted set has a
combined ceiling so the repetition that makes each sheet self-contained cannot
grow unreviewed.

No component's CSS content changed. (#301)
@woksin woksin added the minor label Sep 22, 2026
…unmapped

The 'migration contract' test in transform.test.js asserts that every
subpath in Source/package.json's exports map is either a codemod
rewrite target (a historical root-namespace symbol like Canvas or
DataTables) or explicitly listed as intentionallyUnmapped, so any new
subpath forces a conscious decision rather than silently falling
through the migration contract.

The new per-area */styles entries and ./styles/base are side-effect
CSS imports, not symbol imports the codemod rewrites, so they belong
in intentionallyUnmapped alongside the pre-existing aggregate
./styles export they extend. The Migrator has never handled style
imports; consumers on older Components versions still import
@cratis/components/styles, which keeps working unchanged, so no new
Migrator rewrite behavior is required for this PR.
@woksin
woksin merged commit 8882578 into main Sep 22, 2026
50 checks passed
@woksin
woksin deleted the feat/per-area-stylesheets branch September 22, 2026 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant