Skip to content

Fix: make published dist tree-shakeable - #191

Open
lcmohsen wants to merge 2 commits into
mainfrom
fix/tree-shakeable-dist
Open

Fix: make published dist tree-shakeable#191
lcmohsen wants to merge 2 commits into
mainfrom
fix/tree-shakeable-dist

Conversation

@lcmohsen

@lcmohsen lcmohsen commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Problem

Consumer bundlers cannot tree-shake @lambdacurry/forms. Importing a single component from the ./ui or ./remix-hook-form barrels drags ~200 KB gzip of date-fns, react-day-picker, cmdk, and input-otp into the consuming chunk. In the 360training monorepo the checkout app had to migrate every import to deep subpaths (@lambdacurry/forms/remix-hook-form/text-field) to work around it.

Two root causes:

  1. Chunking artifact: Vite lib-mode merged components into shared hash chunks (e.g. Checkbox lived in filter-selector-DCKG1gjI.js) and hoisted the chunks' transitive externals into every entry stub as bare side-effect imports — dist/ui/checkbox.js opened with ~40 lines like import "date-fns", import "react-day-picker", import "input-otp". Bundlers must preserve bare imports, so tree shaking was defeated.
  2. No sideEffects declaration, so bundlers had to assume every module matters.

A few source files also imported cn from the ../ui barrel (checkbox, dialog, slider, tabs), putting the entire barrel graph into those components' module graphs.

Changes

  • "sideEffects": false — the package ships no CSS and no side-effectful modules.
  • Build with preserveModules + hoistTransitiveImports: false: each source module now maps to exactly one dist module containing only its own imports. No more hash chunks or facade stubs.
  • Externalize all bare specifiers instead of a hand-maintained list. The list was stale: cmdk, @tanstack/react-table, and several Radix packages were being bundled into dist, and libphonenumber-js / react-stately were bundled while missing from dependencies entirely (now declared).
  • remix-hook-form was both a regular dependency (exact 7.1.0) and a peer — consumers could resolve a second nested copy alongside their own (360training currently resolves both 7.1.0 and 7.1.1). It is now peer-only, widened to ^7.1.0, with a devDependency for local development. Note for Yarn/pnpm consumers: if you don't already list remix-hook-form in your dependencies, add it (npm auto-installs peers).
  • Fixed barrel self-imports: cn from ./utils, useDataTableFilters from its own module.
  • Changeset included (minor).

Verification

esbuild bundle of import { Checkbox } from '@lambdacurry/forms/ui' with all npm deps external:

v0.22.7 this branch
package code in bundle 350 KB 6.6 KB
heavy deps retained date-fns, react-day-picker, input-otp, cmdk (bundled copy) none

import { TextField } from '@lambdacurry/forms/remix-hook-form': 357 KB → 9.4 KB, no heavy deps.

  • Every dist module path from v0.22.7 still exists (verified by file-list diff), so deep subpath imports are unaffected; 4 modules that previously hid inside hash chunks are now real files.
  • grep confirms zero bare side-effect imports remain anywhere in dist.
  • yarn build (components + docs Storybook), type-check, and Biome on changed files all pass. (yarn lint fails on scripts/release-if-needed.mjs formatting — pre-existing on main from 2e542e0.)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Published packages are now tree-shakeable, helping applications reduce unused code in production builds.
    • Improved module preservation and package compatibility for supported form integrations.
  • Bug Fixes

    • Corrected internal component and form-module imports to improve build reliability.
  • Chores

    • Updated package metadata and dependency handling without changing component behavior.

The barrel entries (./ui, ./remix-hook-form) welded ~200 KB gzip of
date-fns, react-day-picker, cmdk, and input-otp onto every consuming
chunk: Vite lib-mode chunking merged components into shared hash chunks
and hoisted their transitive externals into each entry stub as bare
side-effect imports (dist/ui/checkbox.js opened with ~40 of them).

- Declare "sideEffects": false (no CSS or side-effectful modules ship)
- Build with preserveModules + hoistTransitiveImports: false so each
  source module maps to exactly one dist module with only its own imports
- Externalize all bare specifiers instead of a hand-maintained list;
  libphonenumber-js and react-stately were silently bundled because the
  list missed them - they are now declared dependencies
- Import cn from './utils' instead of the '../ui' barrel in checkbox,
  dialog, slider, tabs; import useDataTableFilters from its module
  instead of the data-table-filter barrel in data-table-router-form
- Drop the remix-hook-form regular dependency (it stays a peer, widened
  to ^7.1.0) so consumers no longer resolve a second nested copy

Bundling `import { Checkbox } from '@lambdacurry/forms/ui'` with
esbuild: 350 KB package code + date-fns/react-day-picker/cmdk/input-otp
before, 6.6 KB and none of them after. All previous dist module paths
still exist, so deep subpath imports are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5f9760a8-3e8b-43da-afa2-1b268860149d

📥 Commits

Reviewing files that changed from the base of the PR and between 2e542e0 and 92c6424.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (9)
  • .changeset/tree-shakeable-dist.md
  • packages/components/package.json
  • packages/components/src/remix-hook-form/data-table-router-form.tsx
  • packages/components/src/ui/checkbox.tsx
  • packages/components/src/ui/dialog.tsx
  • packages/components/src/ui/slider.tsx
  • packages/components/src/ui/tabs.tsx
  • packages/components/vite.config.ts
  • scripts/release-if-needed.mjs

Included review availability: 4 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.


Walkthrough

The package metadata and Vite build now produce a tree-shakeable distribution. Runtime and peer dependencies are updated, internal imports use direct module paths, and release notes document the changes.

Changes

Tree-shakeable distribution

Layer / File(s) Summary
Distribution metadata and release contract
.changeset/tree-shakeable-dist.md, packages/components/package.json, scripts/release-if-needed.mjs
The package declares no side effects, updates dependency placement and peer ranges, and documents the minor release. The release script keeps the same control flow with formatting-only changes.
Module build and import resolution
packages/components/vite.config.ts, packages/components/src/remix-hook-form/data-table-router-form.tsx, packages/components/src/ui/*.tsx
The build externalizes bare npm imports, preserves modules, disables transitive import hoisting, and uses direct internal module paths.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 92c64

The PR improves published bundle tree-shaking and updates dependency declarations without any actionable merge-blocking risk remaining after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant ViteConfiguration
  participant Rollup
  participant PackageConsumer
  ViteConfiguration->>Rollup: classify bare npm imports as external
  ViteConfiguration->>Rollup: preserve modules and disable import hoisting
  Rollup->>PackageConsumer: emit individual package modules
Loading

Suggested reviewers: jaruesink

Poem

A rabbit trims the module tree,
And leaves each leafy branch carefree.
Bare packages hop outside,
While local paths stay tucked inside.
The bundle blooms, neat and light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 7 files. (2 skipped: 2 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: making the published distribution tree-shakeable.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 7 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tree-shakeable-dist

Comment @coderabbitai help to get the list of available commands.

Pre-existing on main; blocks the lint check on any PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📝 Storybook Preview: View Storybook

This preview will be updated automatically when you push new changes to this PR.

Note: The preview will be available after the workflow completes and the PR is approved for deployment.

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