From d4923527164b04f898fae52b86dcc0ce47a9e561 Mon Sep 17 00:00:00 2001 From: saurabhraghuvanshii Date: Mon, 14 Sep 2026 03:19:21 +0530 Subject: [PATCH] perf: make the published package tree-shakeable 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/]` (c34bfaac) 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 --- eslint.config.js | 8 ++++++++ package.json | 16 +++++++++++++++- src/custom/Carousel/Carousel.tsx | 3 ++- .../DesignTableColumnConfig.tsx | 3 ++- src/custom/CatalogDetail/helper.ts | 2 +- .../CollaboratorAvatarGroup.tsx | 2 +- src/custom/CustomCatalog/Helper.ts | 2 +- .../DashboardWidgets/RecentDesignWidget.tsx | 2 +- src/custom/SearchBar.tsx | 2 +- .../Workspaces/WorkspaceTransferButton.tsx | 2 +- src/custom/Workspaces/WorkspaceViewsTable.tsx | 3 ++- 11 files changed, 35 insertions(+), 10 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 1c8dc0861..56904a037 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -94,6 +94,14 @@ module.exports = [ }, rules: { + // The @mui/icons-material barrel re-exports ~10,800 modules that every + // consumer's bundler must parse. Import icons by path instead. + "no-restricted-imports": ["error", { + paths: [{ + name: "@mui/icons-material", + message: "Import icons by path, e.g. '@mui/icons-material/ChevronLeft'.", + }], + }], ...js.configs.recommended.rules, ...typescript.configs.recommended.rules, }, diff --git a/package.json b/package.json index bf3a72522..2b7b4fa19 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,22 @@ "url": "git+ssh://git@github.com/layer5io/sistent.git" }, "main": "./dist/index.js", - "module": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "./package.json": "./package.json" + }, + "sideEffects": false, "files": [ "dist" ], diff --git a/src/custom/Carousel/Carousel.tsx b/src/custom/Carousel/Carousel.tsx index 50c2f05b3..864967929 100644 --- a/src/custom/Carousel/Carousel.tsx +++ b/src/custom/Carousel/Carousel.tsx @@ -1,4 +1,5 @@ -import { ChevronLeft, ChevronRight } from '@mui/icons-material'; +import ChevronLeft from '@mui/icons-material/ChevronLeft'; +import ChevronRight from '@mui/icons-material/ChevronRight'; import React, { ReactNode, useRef } from 'react'; import { CarouselButton, CarouselContainer, CarouselWrapper } from './style'; diff --git a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx index 9a0ef8748..85ec2f7cd 100644 --- a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx +++ b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx @@ -1,4 +1,5 @@ -import { Lock, Public } from '@mui/icons-material'; +import Lock from '@mui/icons-material/Lock'; +import Public from '@mui/icons-material/Public'; import { Theme } from '@mui/material'; import { MUIDataTableColumn, MUIDataTableMeta } from '@sistent/mui-datatables'; import { Typography } from '../../base'; diff --git a/src/custom/CatalogDetail/helper.ts b/src/custom/CatalogDetail/helper.ts index ccdc612cd..20a80556e 100644 --- a/src/custom/CatalogDetail/helper.ts +++ b/src/custom/CatalogDetail/helper.ts @@ -1,4 +1,4 @@ -import jsyaml from 'js-yaml'; +import * as jsyaml from 'js-yaml'; export const downloadYaml = (filteredData: string, itemName: string): void => { const yamlData = Array.isArray(filteredData) diff --git a/src/custom/CollaboratorAvatarGroup/CollaboratorAvatarGroup.tsx b/src/custom/CollaboratorAvatarGroup/CollaboratorAvatarGroup.tsx index 9c7143a48..671ed4e5c 100644 --- a/src/custom/CollaboratorAvatarGroup/CollaboratorAvatarGroup.tsx +++ b/src/custom/CollaboratorAvatarGroup/CollaboratorAvatarGroup.tsx @@ -1,4 +1,4 @@ -import { ExpandMore } from '@mui/icons-material'; +import ExpandMore from '@mui/icons-material/ExpandMore'; import { alpha } from '@mui/material'; import { MouseEvent, useState } from 'react'; import { Avatar, AvatarGroup, Button, Divider, Popover, Typography } from '../../base'; diff --git a/src/custom/CustomCatalog/Helper.ts b/src/custom/CustomCatalog/Helper.ts index ce1ac7126..5f18d35ce 100644 --- a/src/custom/CustomCatalog/Helper.ts +++ b/src/custom/CustomCatalog/Helper.ts @@ -1,4 +1,4 @@ -import jsyaml from 'js-yaml'; +import * as jsyaml from 'js-yaml'; import { Pattern } from './CustomCard'; const checkImageUrlValidity = async ( diff --git a/src/custom/DashboardWidgets/RecentDesignWidget.tsx b/src/custom/DashboardWidgets/RecentDesignWidget.tsx index 5112afe8d..d4305968e 100644 --- a/src/custom/DashboardWidgets/RecentDesignWidget.tsx +++ b/src/custom/DashboardWidgets/RecentDesignWidget.tsx @@ -1,4 +1,4 @@ -import { KeyboardArrowRight } from '@mui/icons-material'; +import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'; import SwapVertIcon from '@mui/icons-material/SwapVert'; import { styled } from '@mui/material/styles'; import { useState } from 'react'; diff --git a/src/custom/SearchBar.tsx b/src/custom/SearchBar.tsx index 099a0d49c..1e384867e 100644 --- a/src/custom/SearchBar.tsx +++ b/src/custom/SearchBar.tsx @@ -1,6 +1,6 @@ import { outlinedInputClasses } from '@mui/material/OutlinedInput'; import { Theme, ThemeProvider, createTheme } from '@mui/material/styles'; -import debounce from 'lodash/debounce'; +import debounce from 'lodash/debounce.js'; import React, { useCallback } from 'react'; import { ClickAwayListener } from '../base/ClickAwayListener'; import { TextField } from '../base/TextField'; diff --git a/src/custom/Workspaces/WorkspaceTransferButton.tsx b/src/custom/Workspaces/WorkspaceTransferButton.tsx index 37093d8a5..519c532b9 100644 --- a/src/custom/Workspaces/WorkspaceTransferButton.tsx +++ b/src/custom/Workspaces/WorkspaceTransferButton.tsx @@ -1,4 +1,4 @@ -import { SyncAlt as SyncAltIcon } from '@mui/icons-material'; +import SyncAltIcon from '@mui/icons-material/SyncAlt'; import { Grid2, Typography } from '../../base'; import { useTheme } from '../../theme'; import { getFullFormattedTime, getRelativeTime } from '../../utils'; diff --git a/src/custom/Workspaces/WorkspaceViewsTable.tsx b/src/custom/Workspaces/WorkspaceViewsTable.tsx index aef3b37c8..d16acafdc 100644 --- a/src/custom/Workspaces/WorkspaceViewsTable.tsx +++ b/src/custom/Workspaces/WorkspaceViewsTable.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Lock, Public } from '@mui/icons-material'; +import Lock from '@mui/icons-material/Lock'; +import Public from '@mui/icons-material/Public'; import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; import { MUIDataTableColumn, MUIDataTableMeta } from '@sistent/mui-datatables'; import React, { useState } from 'react';