react-md: [General] - Using modules does not exclude unused components as expected

I guess something is wrong with the rollup.config.js file. When doing:

import { Button } from 'react-md';

it looks like the full library is still being imported. The bundle size does not change even when more components are imported.

pasted image at 2017_10_12 09_16 am

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 3
  • Comments: 16 (12 by maintainers)

Commits related to this issue

Most upvoted comments

yes @stickfigure. That trick with the babel-plugin-transform-imports and of course a good code splitting. In my case, in development my bundle size was ~8.5 MB due to source maps. Besides those large bundles I had some app modules inside them so every time I saved a file the bundle was compiled.

ls -lh node_modules/react-md/es/react-md.js gives me

-rw-r--r-- 1 hisa staff 1.4M Dec 4 13:47 node_modules/react-md/es/react-md.js

I think the react-md build should be an index file that exports each of the components and not a bundle containing all the lib’s modules.

A library with tree-shaking working ok is "react-router": "^3.2.0",.

import { Route, IndexRoute } from "react-router";

results in a smaller bundle size than

import Route from 'react-router/lib/Route';
import IndexRoute from 'react-router/lib/IndexRoute';

With the tricks mentioned my vendor-base.js bundle is now 5.91MB … it is the largest in my output but it only contains vendor code so it doesn’t need to be updated each time a save a file in my app.

alas I can’t figure out how to get it to work with my own setup, but in case anyone else is using hisapy’s setup i put together a (i think) complete version of that switch statement:

module.exports = function transformImports(importName) {
  const reactMD = "react-md/lib";

  switch (importName) {
    // Autocompletes
    case "Autocomplete":
      return `${reactMD}/Autocompletes/${importName}`;
    // Avatars
    case "Avatar":
      return `${reactMD}/Avatars/${importName}`;
    // Badges
    case "Badge":
      return `${reactMD}/Badges/${importName}`;
    // Bottom Navigations
    case "BottomNav":
    case "BottomNavigation":
      return `${reactMD}/BottomNavigations/${importName}`;
    // Buttons
    case "Button":
    case "FlatButton":
    case "FloatingButton":
    case "IconButton":
    case "RaisedButton":
      return `${reactMD}/Buttons/${importName}`;
    // Cards
    case "Card":
    case "CardActionOverlay":
    case "CardActions":
    case "CardExpander":
    case "CardMedia":
    case "CardText":
    case "CardTitle":
    case "CardTitleBlock":
      return `${reactMD}/Cards/${importName}`;
    // Chips
    case "Chip":
      return `${reactMD}/Chips/${importName}`;
    // DataTables
    case "DataTable":
    case "DropdownMenuColumn":
    case "EditDialog":
    case "EditDialogColumn":
    case "MenuButtonColumn":
    case "SelectFieldColumn":
    case "TableBody":
    case "TableCardHeader":
    case "TableCheckbox":
    case "TableColumn":
    case "TableFooter":
    case "TableHeader":
    case "TablePagination":
    case "TableRow":
      return `${reactMD}/DataTables/${importName}`;
    // Dialogs
    case "Dialog":
    case "DialogContainer":
    case "DialogFooter":
    case "DialogTitle":
      return `${reactMD}/Dialogs/${importName}`;
    // Dividers
    case "Divider":
      return `${reactMD}/Dividers/${importName}`;
    // Drawers
    case "Drawer":
    case "DrawerTypes":
    case "Overlay":
      return `${reactMD}/Drawers/${importName}`;
    // ExpansionPanels
    case "ExpansionList":
    case "ExpansionPanel":
    case "PanelContent":
      return `${reactMD}/ExpansionPanels/${importName}`;
    // FABTransitions
    case "SpeedDial":
      return `${reactMD}/FABTransitions/${importName}`;
    // File Inputs
    case "FileInput":
    case "FileUpload":
      return `${reactMD}/FileInputs/${importName}`;
    // Font Icons
    case "FontIcon":
      return `${reactMD}/FontIcons/${importName}`;
    // Grids
    case "Cell":
    case "Grid":
    case "GridList":
      return `${reactMD}/Grids/${importName}`;
    // Helpers
    case "AccessibleFakeButton":
    case "AccessibleFakeInkedButton":
    case "Collapse":
    case "FocusContainer":
    case "HorizontalAnchors":
    case "IconSeperator":
    case "Layover":
    case "Portal":
    case "Positions":
    case "ResizeObserver":
    case "VerticalAnchors":
    case "anchorShape":
    case "fixedToShape":
    case "positionShape":
      return `${reactMD}/Helpers/${importName}`;
    // Inks
    case "Ink":
    case "InkContainer":
    case "injectInk":
      return `${reactMD}/Inks/${importName}`;
    // Lists
    case "List":
    case "ListItem":
    case "ListItemControl":
    case "ListItemText":
    case "TileAddon":
      return `${reactMD}/Lists/${importName}`;
    // Media
    case "Media":
    case "MediaOverlay":
      return `${reactMD}/Media/${importName}`;
    // Menus
    case "DropdownMenu":
    case "Menu":
    case "MenuButton":
      return `${reactMD}/Menus/${importName}`;
    // Navigation Drawers
    case "CloseButton":
    case "JumpToContentLink":
    case "MiniListItem":
    case "NavigationDrawer":
      return `${reactMD}/NavigationDrawers/${importName}`;
    // Papers
    case "Paper":
      return `${reactMD}/Papers/${importName}`;
    // Pickers
    case "CalendarDate":
    case "CalendarHeader":
    case "CalendarMonth":
    case "ClockFace":
    case "ClockHand":
    case "ClockTime":
    case "DatePicker":
    case "DatePickerCalendar":
    case "DatePickerContainer":
    case "DatePickerHeader":
    case "PickerControl":
    case "PickerFooter":
    case "TimePeriods":
    case "TimePicker":
    case "TimePickerContainer":
    case "TimePickerHeader":
    case "Year":
    case "YearPicker":
      return `${reactMD}/Pickers/${importName}`;
    // Progress
    case "CircularProgress":
    case "LinearProgress":
      return `${reactMD}/Progress/${importName}`;
    // Select Fields
    case "SelectField":
    case "SelectFieldInput":
    case "SelectFieldToggle":
      return `${reactMD}/SelectFields/${importName}`;
    // SelectionControls
    case "Checkbox":
    case "Radio":
    case "RadioGroup":
    case "SelectionControl":
    case "SelectionControlGroup":
    case "Switch":
    case "SwitchThumb":
    case "SwitchTrack":
      return `${reactMD}/SelectionControls/${importName}`;
    // Sidebars
    case "Sidebar":
      return `${reactMD}/Sidebars/${importName}`;
    // Sliders
    case "DiscreteValue":
    case "Slider":
    case "SliderLabel":
    case "Thumb":
    case "ThumbMask":
    case "Track":
    case "TrackFill":
      return `${reactMD}/Sliders/${importName}`;
    // Snackbars
    case "Snackbar":
    case "SnackbarContainer":
      return `${reactMD}/Snackbars/${importName}`;
    // Subheaders
    case "Subheader":
      return `${reactMD}/Subheaders/${importName}`;
    // SVG Icons
    case "SVGIcon":
      return `${reactMD}/SVGIcons/${importName}`;
    // Tabs
    case "MenuTab":
    case "Tab":
    case "TabIndicator":
    case "TabOverflowButton":
    case "TabPanel":
    case "Tabs":
    case "TabsContainer":
      return `${reactMD}/Tabs/${importName}`;
    // Text Fields
    case "FloatingLabel":
    case "InputField":
    case "Message":
    case "PasswordButton":
    case "TextArea":
    case "TextField":
    case "TextFieldDivider":
    case "TextFieldMessage":
      return `${reactMD}/TextFields/${importName}`;
    // Toolbars
    case "Toolbar":
    case "ToolbarTitle":
      return `${reactMD}/Toolbars/${importName}`;
    // Tooltips
    case "Tooltip":
    case "TooltipContainer":
    case "Tooltipped":
    case "injectTooltip":
      return `${reactMD}/Tooltips/${importName}`;
    // constants
    case "CSSTransitionGroupTick":
    case "additionalInkTriggerKeys":
    case "keyCodes":
    case "media":
      return `${reactMD}/constants/${importName}`;
    default:
      return `${reactMD}/${importName}s/${importName}`;
  }
};

Cool @stickfigure … I was doing that but since I’m a lazy developer I looked for a pluggable solution (like babel-plugin-lodash) so then I started writing my first babel plugin. Luckily, after some researching/reading I found the babel-plugin-transform-imports. Also, I remembered how tedious was rewriting the deep imports to namespaced imports when upgraded react-md to the version with tree-shaking

I’d suggest you try the https://github.com/Kaishiyoku/webpack-react-md-import-transformer created by @Kaishiyoku . In the future, when the tree-shaking gets fixed you’ll just have to unplug the the plugin to try tree-shaking again.

@hisapy You’re right! I updated the README.

I’ve created a small package with a transformer for most components: https://github.com/Kaishiyoku/webpack-react-md-import-transformer

Please read the installation instructions carefully.

@hisapy I stumbled across the same thing today but didn’t investigate any further but also wanted to build a custom import transformer for react-md. Will grab your code and extend it further to my needs, thanks!

Currently react-md is the largest dependency regarding bundle size (~1.25MB).

Hey guys, I’m struggling with my bundle size too. A temporary solution I found is to use babel-plugin-transform-imports … that way you can use the namespaced imports and the plugin will take care of transforming them to deep imports considerably reducing the bundle size.

For my current components I’m using the following transform function which you might like to extend according to your needs:

module.exports = function transformImports(importName) {
  const reactMD = "react-md/lib";

  switch (importName) {
    // Cards
    case "CardActions":
    case "CardText":
    case "CardTitle":
      return `${reactMD}/Cards/${importName}`;

    // DataTables
    case "TableHeader":
    case "TableCardHeader":
    case "TableBody":
    case "TableRow":
    case "TableColumn":
      return `${reactMD}/DataTables/${importName}`;

    // Inks
    case "injectInk":
      return `${reactMD}/Inks/${importName}`;

    // Tabs
    case "TabsContainer":
    case "Tabs":
    case "Tab":
      return `${reactMD}/Tabs/${importName}`;

    // Lists
    case "ListItem":
      return `${reactMD}/Lists/${importName}`;

    // ExpansionPanels
    case "ExpansionList":
      return `${reactMD}/ExpansionPanels/${importName}`;

    // Dialogs
    case "DialogContainer":
      return `${reactMD}/Dialogs/${importName}`;

    // Pickers
    case "DatePicker":
      return `${reactMD}/Pickers/${importName}`;

    // Progress
    case "CircularProgress":
      return `${reactMD}/Progress/${importName}`;

    // SelectionControls
    case "Checkbox":
      return `${reactMD}/SelectionControls/${importName}`;

    default:
      return `${reactMD}/${importName}s/${importName}`;
  }
};