Make the ESM barrel tree-shakeable so consumers bundle only the CSS they use - #1415
Closed
stefankp wants to merge 1 commit into
Closed
Make the ESM barrel tree-shakeable so consumers bundle only the CSS they use#1415stefankp wants to merge 1 commit into
stefankp wants to merge 1 commit into
Conversation
Remove `./esm/index.esm.js` from `sideEffects` so bundlers can prune unused components (and their co-located CSS) when consumers import from the `@primer/react-brand/esm` barrel. Keep `**/*.css` and `./esm/css/stylesheets.js` side-effectful so used components' CSS and the base layer are always retained. Add a `./esm/*` subpath export (mirroring the existing `./lib/*`) so consumers can import individual component ESM modules and types directly, e.g. `@primer/react-brand/esm/Button/Button.js`. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: db807c62-fba1-4868-bb40-cd661ef1a30f
🦋 Changeset detectedLatest commit: 7e78b28 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
🟢 No design token changes found |
Contributor
Author
|
Closing temporarily pending internal sign-off on the description — will reopen shortly. No code change; branch stays as-is. |
Contributor
🟢 Bundle size report
|
Contributor
🟢 No unit test coverage changes foundAll components and hooks with tests maintain the same coverage as the main branch. |
Contributor
🟢 No visual differences foundOur visual comparison tests did not find any differences in the UI. |
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.
Summary
The ESM barrel (
@primer/react-brand/esm) was flagged as side-effectful inpackage.jsonsideEffects(via"./esm/index.esm.js"). That forced bundlers (webpack/rspack/vite) to fully evaluate the barrel and retain every component's co-located CSS, even when a consumer imported only a couple of components — so a 2-component island still pulled in the entire ~60-component CSS surface.This PR removes the barrel module from
sideEffects(keeping**/*.cssand the base./esm/css/stylesheets.jsside-effectful) so bundlers can tree-shake through the barrel and emit only the CSS of components that are actually imported. It also adds a granular./esm/*subpath export (mirroring the existing./lib/*) so consumers can import individual component modules and their types directly. The change is additive packaging metadata only — no runtime or source changes — and all existing entry points are preserved.List of notable changes:
sideEffectsinpackages/react/package.jsonto drop"./esm/index.esm.js", making the ESM barrel tree-shakeable while still emitting used components' CSS and always retaining the ~21 KB base layer."./esm/*": "./esm/*"to theexportsmap, exposing per-component ESM modules + co-located types, e.g.@primer/react-brand/esm/Button/Button.js.minorchangeset describing the behavior change and the CSS-drop risk for consumers implicitly relying on all Brand CSS being present.What should reviewers focus on?
sideEffectschange cannot drop used CSS:**/*.cssand./esm/css/stylesheets.jsremain side-effectful, so any imported component still emits its CSS and the base layer (design tokens, light/dark color modes, reset, utilities) is always retained..,./esm,./lib,./lib/*,./fonts/*, and./package.jsonare unchanged; only./esm/*is added and onesideEffectsentry removed.@primer/brand-css) explicitly. This is called out in the changeset.Steps to test:
Measured with a throwaway consumer that imports only
{ThemeProvider, MinimalFooter}from@primer/react-brand/esm, packed from this build and bundled with both webpack and rspack, extracting the emitted CSS:sideEffectspruning (all 73 modules retained)usedExportson and off)The base layer (
:roottokens for light + dark, reset, utilities) is present in every scenario; only unused components' CSS is removed. The barrel entry@primer/react-brand/esmresolves unchanged, and a granular@primer/react-brand/esm/Button/Button.jsimport resolves both JS and.d.ts.To reproduce:
npm ci && npm run build:libcd packages/react && npm pack --ignore-scripts, then install the tarball into a scratch app.import {ThemeProvider, MinimalFooter} from '@primer/react-brand/esm'and inspect the emitted CSS size before vs. after this change.Supporting resources (related issues, external links, etc):
@primer/react-brandconsumer investigation (marketing islands) where a 2-component island emitted ~22.6 KB compressed of Brand CSS.Contributor checklist:
tsc --noEmitclean,eslint0 warnings, 1228 Jest tests pass locally)