Skip to content

perf: make the published package tree-shakeable - #1841

Open
saurabhraghuvanshii wants to merge 4 commits into
layer5io:masterfrom
saurabhraghuvanshii:perf/tree-shakeable-package
Open

saurabhraghuvanshii wants to merge 4 commits into
layer5io:masterfrom
saurabhraghuvanshii:perf/tree-shakeable-package

Conversation

@saurabhraghuvanshii

@saurabhraghuvanshii saurabhraghuvanshii commented Sep 13, 2026

Copy link
Copy Markdown
Member

Consumers could not tree-shake @sistent/sistent at all. Importing one component from 0.22.6 pulls ~14,000 modules and 14.7 MB into a webpack build, 10,775 of those modules being @mui/icons-material. In layer5.io this was the largest single contributor to build memory.

Three causes, all fixed here:

  • module pointed at the CommonJS build. main and module were both ./dist/index.js, so no bundler ever selected dist/index.mjs. module now points at the ESM build, and an exports map routes import and require to the matching build and declarations.

  • There was no sideEffects field, so bundlers had to keep every export. The package has no top-level side effects (no CSS imports, no global registration), so it is now marked sideEffects: false.

  • Six components imported icons from the @mui/icons-material barrel, which re-exports ~10,800 modules. They now import by path, and a no-restricted-imports rule keeps the barrel out.

Serving the ESM build exposed two imports that only worked through CJS interop, and would have broken strict ESM consumers:

  • import jsyaml from 'js-yaml' has no default export in js-yaml's ESM build; now import * as jsyaml.
  • lodash/debounce has no extension and lodash has no exports map, which webpack rejects from an .mjs file (fullySpecified); now lodash/debounce.js.

Measured with webpack 5, a consumer that imports only Box:

published 0.22.6 this change*
modules 14,132 5,288
@mui/icons-material 10,775 55
output 14.68 MB 2.27 MB

  • with the matching @sistent/mui-datatables deep-import fix (layer5io/mui-datatables), which removes the remaining barrel.

The build succeeds (ESM, CJS and declarations), all 515 tests pass, and lint is clean. The public API is unchanged.

Not changed: noExternal: [/^@meshery\/schemas/] (c34bfaa) is what grew the package from 2.3 MB in 0.18.0 to 9.3 MB in 0.18.1, but it was added to fix a schema version mismatch in the Meshery UI, so un-bundling it needs a maintainer decision rather than a drive-by change.

Notes for Reviewers

This PR fixes #

Signed commits

  • Yes, I signed my commits.

Summary by CodeRabbit

  • Compatibility

    • Improved package entry-point handling for ESM consumers.
    • Improved compatibility with server-rendered applications and modern module environments.
    • Identified the package as side-effect free for more efficient bundling.
  • Maintenance

    • Standardized Material UI icon imports to dedicated module paths.
    • Updated YAML and utility imports for broader module compatibility.
    • Added linting guidance to prevent inefficient icon barrel imports.

@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Understand this PR’s impact

Explore downstream dependencies and potential security impact with Blast Radius.

View blast radius →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: bdfc6241-c2f1-4184-9941-a60fc6769c90

📥 Commits

Reviewing files that changed from the base of the PR and between a3bb448 and 9041832.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 0b044b24-d5ac-4739-99a5-2f3147c5e281

📥 Commits

Reviewing files that changed from the base of the PR and between 3d2c308 and a3bb448.

📒 Files selected for processing (5)
  • package.json
  • src/custom/DashboardWidgets/GettingStartedWidget/TeamSearchField.tsx
  • src/custom/ShareModal/ShareModal.tsx
  • src/custom/StyledSearchBar/StyledSearchBar.tsx
  • tsup.config.ts

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The package metadata and bundler configuration now use explicit ESM and dependency handling. ESLint rejects MUI icon barrel imports. Source files use dedicated MUI and lodash paths plus namespace imports for js-yaml.

Changes

Module resolution updates

Layer / File(s) Summary
Package and runtime configuration
package.json, tsup.config.ts
The package uses dist/index.mjs as its module entry point, declares no side effects, adds use-sync-external-store, and keeps that dependency external in tsup output.
MUI import policy and migrations
eslint.config.js, src/custom/Carousel/Carousel.tsx, src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx, src/custom/CollaboratorAvatarGroup/CollaboratorAvatarGroup.tsx, src/custom/DashboardWidgets/RecentDesignWidget.tsx, src/custom/Workspaces/*
ESLint rejects imports from the @mui/icons-material barrel package. Updated source imports use individual MUI icon paths.
Module-specific source imports
src/custom/CatalogDetail/helper.ts, src/custom/CustomCatalog/Helper.ts, src/custom/SearchBar.tsx, src/custom/DashboardWidgets/GettingStartedWidget/TeamSearchField.tsx, src/custom/ShareModal/ShareModal.tsx, src/custom/StyledSearchBar/StyledSearchBar.tsx
js-yaml uses namespace imports. Lodash utilities use explicit module paths with .js extensions.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: leecalcote

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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 main objective: making the published package tree-shakeable.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Consumers could not tree-shake @sistent/sistent at all. Importing one
component from 0.22.6 pulls ~14,000 modules and 14.7 MB into a webpack build,
10,775 of those modules being @mui/icons-material. In layer5.io this was the
largest single contributor to build memory.

Three causes, all fixed here:

- `module` pointed at the CommonJS build. `main` and `module` were both
  `./dist/index.js`, so no bundler ever selected `dist/index.mjs`. `module`
  now points at the ESM build, and an `exports` map routes `import` and
  `require` to the matching build and declarations.

- There was no `sideEffects` field, so bundlers had to keep every export.
  The package has no top-level side effects (no CSS imports, no global
  registration), so it is now marked `sideEffects: false`.

- Six components imported icons from the `@mui/icons-material` barrel,
  which re-exports ~10,800 modules. They now import by path, and a
  `no-restricted-imports` rule keeps the barrel out.

Serving the ESM build exposed two imports that only worked through CJS
interop, and would have broken strict ESM consumers:

- `import jsyaml from 'js-yaml'` has no default export in js-yaml's ESM
  build; now `import * as jsyaml`.
- `lodash/debounce` has no extension and lodash has no exports map, which
  webpack rejects from an `.mjs` file (`fullySpecified`); now
  `lodash/debounce.js`.

Measured with webpack 5, a consumer that imports only `Box`:

                      published 0.22.6    this change*
  modules                       14,132           5,288
  @mui/icons-material           10,775              55
  output                      14.68 MB         2.27 MB

  * with the matching @sistent/mui-datatables deep-import fix
    (layer5io/mui-datatables), which removes the remaining barrel.

The build succeeds (ESM, CJS and declarations), all 515 tests pass, and lint
is clean. The public API is unchanged.

Not changed: `noExternal: [/^@meshery\/schemas/]` (c34bfaa) is what grew the
package from 2.3 MB in 0.18.0 to 9.3 MB in 0.18.1, but it was added to fix a
schema version mismatch in the Meshery UI, so un-bundling it needs a
maintainer decision rather than a drive-by change.

Signed-off-by: saurabhraghuvanshii <saurabhsraghuvanshi@gmail.com>
@saurabhraghuvanshii
saurabhraghuvanshii force-pushed the perf/tree-shakeable-package branch from 8b46a58 to d492352 Compare September 13, 2026 22:06
Pointing `module` at `dist/index.mjs` exposed three ways the ESM build had
never actually been loaded by Node. Bundlers tolerate all three, so nothing
caught them; Next.js SSR does not, and Meshery failed with "Failed to collect
page data".

- `import { startCase } from 'lodash'` and two `import { debounce }` —
  lodash is CommonJS, and Node's ESM loader cannot detect its named exports:
  "Named export 'startCase' not found". Now imported as
  `lodash/startCase.js` and `lodash/debounce.js`, which also tree-shakes.

- `use-sync-external-store` (pulled in by @xstate/react, react-redux and
  @mui/x-date-pickers) is CommonJS and calls `require('react')` at runtime.
  Bundled into ESM output, esbuild can only emit that as a `__require` shim,
  which throws `Dynamic require of "react" is not supported`. It is now
  external, and declared as a dependency so consumers always resolve it.

- Dropped the `exports` map added in the previous commit. `main` + `module`
  already give bundlers the ESM build and Node the CJS one, and the map adds
  a resolution failure mode for no benefit here.

Verified: `import('@sistent/sistent')` now succeeds under Node 22 (735
exports), and a full Meshery UI production build (Next.js 16, Turbopack)
completes with zero errors, including
`/configuration/designs/configurator`. Build, lint and all 515 tests pass.

Consumer measurement is unchanged from the previous commit — webpack 5, a
consumer importing only `Box`, with the matching @sistent/mui-datatables fix:
14,132 -> 5,312 modules, 10,775 -> 55 icon modules, 14.68 MB -> 2.27 MB.

Signed-off-by: saurabhraghuvanshii <saurabhsraghuvanshi@gmail.com>

@banana-three-join banana-three-join left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build issues in Meshery and Cloud and being investigated...

This branch has not been deployed

No deployments
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.

2 participants