chore(workspace-plugin): generate bundle-size fixtures from source - #36616
Draft
Martin Hochel (Hotell) wants to merge 2 commits into
Draft
chore(workspace-plugin): generate bundle-size fixtures from source#36616Martin Hochel (Hotell) wants to merge 2 commits into
Martin Hochel (Hotell) wants to merge 2 commits into
Conversation
Adds an Nx sync generator that keeps package.json entry point fields (main, module, typings) and the exports map in sync with each project's declared entry points, for non-private libraries tagged vNext + platform:web. Motivation: microsoft#36606 had to hand-fix 6 headless subpaths that shipped with the legacy flat shape ({types, node, import, require}, .js CJS paths, no .d.cts) mixed into an otherwise migrated map. The exports map is also the source of truth for generate-api, so a missing subpath silently drops a dts rollup. Entry points cannot be inferred from the file layout, so multi entry projects declare them via project.json#metadata.exportMap. Projects without the declaration default to a single root entry, which is what the other 79 packages already have. Also fixes react-headless-components-preview, whose main/module/typings were dead because its exports map had no "." key.
Generates bundle-size fixtures from source so they cannot silently fall behind what a package actually exports. Only fixtures declared in project.json#metadata.bundleSizeFixtures are generated; hand written monosize fixtures in the same folder are left alone. entryPoints kind namespace imports every non-root export subpath, resolved from metadata.exportMap rather than package.json#exports so a drifted export map cannot hide behind a matching drifted fixture. This immediately caught AllComponents.fixture.js missing menu-button, positioning and utils. baseHooks kind named imports every use*Base_unstable hook exported by the project's workspace dependencies, enumerated by walking each dependency's src/index.ts with the TypeScript parser. Source is used rather than the etc/*.api.md rollups because api.md is generated by generate-api, which itself reads package.json#exports - deriving fixtures from it would reintroduce the coupling this harness exists to break. Regenerating BaseHooks.fixture.js reproduces the hand maintained file byte for byte.
📊 Bundle size report
Unchanged fixtures
|
|
Pull request demo site: URL |
| @@ -51,6 +51,7 @@ jobs: | |||
| - name: Workspace lint | |||
There was a problem hiding this comment.
🕵🏾♀️ visual changes to review in the Visual Change Report
vr-tests-react-components/Positioning 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Positioning.Positioning end.chromium.png | 27 | Changed |
| vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png | 613 | Changed |
vr-tests-react-components/ProgressBar converged 3 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png | 48 | Changed |
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png | 126 | Changed |
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png | 40 | Changed |
vr-tests-react-components/Skeleton converged 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Skeleton converged.Opaque Skeleton with rectangle - Dark Mode.default.chromium.png | 14 | Changed |
vr-tests-react-components/TagPicker 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/TagPicker.disabled - High Contrast.disabled input hover.chromium.png | 1319 | Changed |
vr-tests-web-components/Avatar 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-web-components/Avatar. - Dark Mode.normal.chromium_1.png | 298 | Changed |
| vr-tests-web-components/Avatar. - Dark Mode.normal.chromium.png | 10380 | Changed |
vr-tests-web-components/Badge 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-web-components/Badge. - Dark Mode.normal.chromium.png | 443 | Changed |
vr-tests-web-components/MenuList 3 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-web-components/MenuList. - RTL.1st selected.chromium_2.png | 39384 | Changed |
| vr-tests-web-components/MenuList. - RTL.2nd selected.chromium_3.png | 38815 | Changed |
| vr-tests-web-components/MenuList. - RTL.normal.chromium_1.png | 39083 | Changed |
vr-tests/Callout 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests/Callout.No callout width specified.default.chromium.png | 2143 | Changed |
vr-tests/react-charting-VerticalBarChart 5 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests/react-charting-VerticalBarChart.Basic - Secondary Y Axis.default.chromium.png | 3 | Changed |
| vr-tests/react-charting-VerticalBarChart.Grouped - Wrap Labels.default.chromium.png | 1690 | Changed |
| vr-tests/react-charting-VerticalBarChart.Grouped - Wrap Labels - Dark Mode.default.chromium.png | 1745 | Changed |
| vr-tests/react-charting-VerticalBarChart.Stacked - RTL.default.chromium.png | 1865 | Changed |
| vr-tests/react-charting-VerticalBarChart.Grouped - Wrap Labels - RTL.default.chromium.png | 1674 | Changed |
There were 1 duplicate changes discarded. Check the build logs for more information.
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.

Previous Behavior
bundle-sizefixtures were hand-maintained, so they could silently fall behind what a package actually exports — and they had:react-headless-components-preview/bundle-size/AllComponents.fixture.jsimported 52 subpaths whileexportsdeclared 55. Missing:menu-button,positioning,utils. Those three entry points were never covered byverify-bundle-isolation.react-components/bundle-size/BaseHooks.fixture.js(added in chore: add verify-bundle-isolation to workspace plugin, cleanup deps and onboard react-components to track base hooks creep #36587) enumerates all 103use*Base_unstablehooks by hand. Every new base hook has to be remembered.New Behavior
An Nx sync generator generates these fixtures from source.
Only fixtures declared in
project.json#metadata.bundleSizeFixturesare generated — the 6 hand-written monosize fixtures in the same folders are left alone and keep reportingSKIPPED.{ "metadata": { "bundleSizeFixtures": { "AllComponents.fixture.js": { "kind": "entryPoints", "name": "react-headless-components-preview: entire library" } } } }nameis the monosize fixture name, which doubles as the bundle size report baseline key — declared rather than derived, since renaming it drops that fixture's recorded history.entryPointsNamespace imports every non-root export subpath. Resolved from
metadata.exportMap(see #36615) rather than frompackage.json#exports, so a drifted export map cannot hide behind a matching drifted fixture. The root entry is deliberately excluded — importing it would pull in the whole library and defeat per-subpath isolation.baseHooksNamed imports every
use*Base_unstablehook exported by the project's workspace dependencies, enumerated by walking each dependency'ssrc/index.tswith the TypeScript parser (lib/public-exports.ts).Source is used rather than the
etc/*.api.mdrollups on purpose:api.mdis generated bygenerate-api, which itself readspackage.json#exports. Deriving fixtures from it would reintroduce the exact coupling this harness exists to break, and would go stale between a source change and the nextgenerate-apirun.The walker uses
ts.createSourceFileonly — nots.createProgram, no type checker, since the Tree has no real filesystem and only binding names are needed. Type-only exports are excluded (a type imported as a value would not survive to runtime),export *is followed transitively with a cycle guard, and an unresolvableexport *warns loudly rather than silently under-reporting.Verification
BaseHooks.fixture.jsregenerates byte-for-byte identical to the hand-maintained file from chore: add verify-bundle-isolation to workspace plugin, cleanup deps and onboard react-components to track base hooks creep #36587 — all 103 hooks, same grouping and ordering. That independently validates the source walker against API Extractor's view.AllComponents.fixture.jsgained exactly the 3 missing subpaths (menu-button,positioning,utils) — the only content change in this PR.src/zz-probe.ts→ fixture flagged and fixed.react-buttonsource only →BaseHooks.fixture.jsflagged, without runninggenerate-apifirst.nx run-many -t verify-bundle-isolation→react-componentsPASS WITH DEBT - 1 fixture, 4 skipped, 0 regressions, 4 allowed violations(matches chore: add verify-bundle-isolation to workspace plugin, cleanup deps and onboard react-components to track base hooks creep #36587); previewCLEAN/PASS. The 3 added subpaths introduce no forbidden packages.workspace-plugin314 tests / 42 suites green.Performance
Measured against the real workspace (256 projects), since this parses TypeScript:
getProjects()Runs once per
nxcommand, not per task (verified with an invocation counter across a 3-project build that fanned out to many dependency builds). For comparison,export-maps-syncfrom #36615 measures ~53ms.Related Issue(s)
Note
Second of a 3 PR stack, branched off #36615 — its commit will show here until that merges.