Björn Friedrichs

That's me

Published August 13th, 2022

Resolving tree shake issues with MUI icons and Parcel 2 for Node builds

This website is built using MUI and bundled using Parcel. Parcel 2 enables a feature called "scope hoisting" by default. This includes tree shaking capabilities which in theory should resolve imports from their main package only if they are actually used, drastically reducing bundle size.

// Before
import { Import1, Import2 } from 'package';

// After
import Import1 from 'package/Import1';
import Import2 from 'package/Import2';

Example of the idea here, let's pretend package exports 50 modules in its main file but we only need two of them. Without tree shake we would include all 50 modules because they are inherently included, with tree shake enabled we only target the modules we actually use.

However, while inspecting the bundle size I noticed that @mui/icons-material/index.js was being imported fully. This made up a large chunk of my bundle as all these icons were included that I never even used. I am not 100% sure if the issue is with Parcel or MUI but I didn't want to dig around more than I had to. Instead I opted to replace the few icons I use with their direct imports myself.

// from this
import { Undo, Redo } from '@mui/icons-material';

// to this
import UndoIcon from '@mui/icons-material/Undo';
import RedoIcon from '@mui/icons-material/Redo';

This lead to me being greeted by an error createSvgIcon is not a function. After looking into the error a little bit it seems that MUI resolves this function to a utils package when building for node, but resolving this does not seem to quite work (and for a browser build it does work because the cjs version @mui/icons-material/utils/createSvgIcon.js of the file contains the needed code itself without another import).

// @mui/icons-material/esm/Undo.js
import createSvgIcon from './utils/createSvgIcon';

// @mui/icons-material/esm/utils/createSvgIcon.js
export { createSvgIcon as default } from '@mui/material/utils';

// @mui/material/utils/createSvgIcon.js
export const createSvgIcon ...

I have had issues with parcel and export { X as default } from before, so I suspect that their scope hoisting just does not properly work with some variations of it.

How to solve the problem

I am already using a custom Parcel resolver for other issues so I added a direct resolve for the ./utils/createSvgIcon import. You can set this up as follows and then run yarn install to enable the link.

// package.json
  "devDependencies": {
    "parcel-resolver-shim": "link:./parcel-resolver-shim"

// .parcelrc
  "extends": "@parcel/config-default",
  "resolvers": ["parcel-resolver-shim", "..."]

// parcel-resolver-shim/index.js
const { Resolver } = require('@parcel/plugin');
const path = require('path');

module.exports = new Resolver({
  async resolve({ specifier }) {
    if (specifier === './utils/createSvgIcon') {
      return {
        filePath: path.join(

    // Let the next resolver in the pipeline handle
    // this dependency.
    return null;

With this added to my project the import is resolved correctly and my bundle size is down, success!

These are the affected versions of MUI and Parcel I am using for reference:

    "@emotion/react": "^11.10.0",
    "@emotion/styled": "^11.10.0",
    "@mui/icons-material": "^5.8.4",
    "@mui/material": "^5.10.0",
    "@mui/styles": "^5.0.0",
    "parcel": "^2.7.0",
© Björn Friedrichs 2021 privacy & more