Handling duplicate bundling of MaterialUI with Webpack in monorepos

Maneet Goyal
3 min readOct 11, 2020
Photo by Louis Hansel @shotsoflouis on Unsplash

How to configure your Webpack based build routine in a Lerna + Yarn Workspaces based monorepo to avoid duplicate bundling of MaterialUI?

Now why is it a problem that should be solved in the first place? Apart from the general reason of bundle size optimization, it can cause another serious issue which may be better addressed in Material-UI team’s own docs.

In the situations wherein:

You accidentally bundle two versions of Material-UI. You might have a dependency not correctly setting Material-UI as a peer dependency.

a lot of styling issues can happen:

due to class name conflicts once your code is in a production bundle. For Material-UI to work, the className values of all components on a page must be generated by a single instance of the class name generator.

Can you be a bit more specific?

So the main problem we’ll look at is addressing the styling issues that can arise while using MaterialUI components due to duplicate bundling of their source code in a certain monorepo structure:

- monorepo-root
- package.json
- packages
- app
- package.json
- forms
- package.json
- ui-components
- package.json

And here’s a dependency graph of those child “packages”:

Monorepo package dependency graph

All the child packages (app, forms, and ui-components) import from @material-ui/core and treated that as a dependency. I didn’t imagine it would turn out to be the root cause of this problem.

Alright! So what can we do about it?

We have 2 major players in that monorepo: Webpack and Yarn.

1. Tell Yarn to treat MaterialUI as a peerDependency of forms and ui-components. This way the consumer of these packages will have the responsibility to install @material-ui/core as a dependency.

"name": "forms","version": "1.0.0",..."devDependencies": {  ...},"peerDependencies": {  ...  "@material-ui/core": "^4.11.0"},"dependencies": {  ...},

3. Tell Webpack to stop doing bundling the @material-ui/core peerDependency in forms and ui-components distribution bundles.

(Note: I used import {...} from "@material-ui/core"syntax only in all the source files. For different import styles, say, import Button from “@material-ui/core/Button", you may need to tweak the externalizing logic given below.)

But what if I am using Storybook too?

If your monorepo follows the same structure as described above, Storybook should “just work” out-of-the-box. This is because app has listed @material-ui/core as a dependency so yarn will install it to your node_modules folder. Now Storybook’s build process has access to that node_modules (due to dependency hoisting by Yarn Workspaces) and can therefore bundle it. But there can be situations wherein your monorepo follows a slightly different structure.

Say, none of the packages in your monorepo has @material-ui/core as a dependency. Say, they are only using it as a peerDependency. This implies yarn will not add @material-ui/core to your node_modules. So when Storybook runs its build process, it won’t have access to the @material-ui/core source code.

To counter this, add MaterialUI as a devDependency in addition to it already being a peerDependency in the concerned package. Check out this thread too if you have some concerns with this approach.

"name": "forms","version": "1.0.0",..."devDependencies": {  ...  "@material-ui/core": "^4.11.0"},"peerDependencies": {  ...  "@material-ui/core": "^4.11.0"},"dependencies": {  ...},

Have any other comments?

Yes. It turns out that this problem is so common that Material-UI team listed it number 1 question on their FAQs. It’s a good effort on their end to inform the users on how to deal with this issue. I still had to invest a lot of time to get it all working so thought of sharing that workflow.

Please comment on any issues you see with the approach. Will bring in any updates to make it a more reliable resource for Webpack-Lerna-Yarn-Workspace clan ⚒ .

--

--