Fluent UI React— How we cut more than 30% of component’s bundle size by creating icons package

Marija Najdova
Apr 27, 2020 · 5 min read

Fluent UI React is an open source React UI library used in Microsoft Teams. It allows users to build accessible and themable applications, by using a set of common controls. These powerful features are exposed behind simple APIs based on the natural language.

The library is being built as an exemplar of the Fluent UI design language, component specifications, and utilities (if you want to find out some more info, please visit our docs page).

In this post I want to talk a bit more about bundle size, specifically the bundle for an application that uses one of our components.

Let’s take the following example:

import * as React from "react";
import { Provider, Button, themes } from "@fluentui/react-northstar";
const App = () => (
<Provider theme={themes.teams}>
<Button content="Awesome button" />
</Provider>
);

This is basically a simple application, with a Provider that provides the Microsoft Teams theme, and one Button component inside.

Here is how the bundle for this application looks like. Pretty cramped…

Bundle size of an application using the Button component

Investigating the bundle, made us realize that the biggest chunk of it are actually the icons (you can see the big square in the left bottom corner). The irony is, we are not even using any icon in our app :(

Let me explain a bit why this is happening. The icons were initially part of the theme (on the code sample before I used the Teams theme inside the Provider).

  <Provider theme={themes.team}>

The theme object inside contains all icons that this theme supports in the following format:

const theme = {
siteVariables: {...}, // don't worry about these for now...
componentVariables: {...}, // some config for variables...
componentStyles: {...}, // some component styles...
// This is the part related to icons
icons: {
accept: {
svg: ({ classes }) => (
<svg viewBox="0 0 16 16" className={classes.svg}>{...}</svg>
),
},
robot: ({ classes }) => (
<svg viewBox="0 0 16 16" className={classes.svg}>{...}</svg>
),
},
// ... and hundreds more icons...
},
};

Basically in the theme object we had hundreds of svg icons defined, which clients could use in the following manner:

import { Icon } from "@fluentui/react-northstar";const App = () => (
<Icon name="accept" />
);

There were some benefits to this, like client’s could add some icon in the theme and they could immediately use then via the name prop, or they could override the svg for some icons if they want to change it, but these benefits were so not worth when compared to the problems they’ve created.

The first problem that I already talked about is the bundle size. The icons are always bundled no matter whether clients use one icon, hundreds or even don’t use icons at all!

Further more we had problems with catching regressions. The name inside the Icon component is just a string, so there are no type checks that will ensure that the icon name will exists in the theme. If somebody decides to remove an icon from the theme, or rename it, type checks won’t catch this change :(

Moreover, if clients want to add a specific prop for only one icon (for example a danger prop on the emergency icon), they could not do that, because frankly there is only one Icon component and all other icons will need to either support this, or ignore it, which is not good…

<Icon name=”call” blocked />
The blocked call icon

But you cannot block robots… Right?

<Icon name=”call” blocked /> // we don't want to support this :(
The robot icon (which cannot be blocked)

Based on all these, we decided that it’s time to change things. We looked around to see what other frameworks do with their icons and there was one paradigm that worked in many places — define component per icon. In short words, what we needed to do was to move those hundreds of icons outside of the theme and create new component for each of them.

But how do we start? We want icons still to be consistent in their behavior… Well, we started with defining a factory for creating icons. The usage is very simple:

import { createSvgIcon } from "@fluentui/react-northstar";const RobotIcon = createSvgIcon({
svg: ({ classes, props }) => (
<svg viewBox="0 0 16 16" className={classes.svg}>{...}</svg>
),
displayName: 'RobotIcon',
});

Keeping the API simple, allowed us to easily migrate the icons to components (Yeeey!). Another cool things about the factory and creating components per icon, was that now we can add specials props on some icons and we don’t need to support them on all icons. For example:

import { createSvgIcon } from "@fluentui/react-northstar";const EmergencyIcon = createSvgIcon<{ danger?: boolean }>({
svg: ({ classes, props }) => (
<svg
viewBox="0 0 16 16"
className={classes.svg}
style={props.danger ? {color: 'red' : {}}
>{...}</svg>
),
displayName: 'EmergencyIcon',
});

Apart from the type safety and ability to add special props, our bundle size looked much better

More precisely, we had a drop in the bundle size for the application using the Button component for about 34%!

There is still a long way to go. As you can see on the last picture, for rendering just one Button, we are pulling almost all components from the library. This is because we have some references of static fields inside the components, which we are planning to tackle next.

Anyway, going back to the icons, there were many lessons learned with this transition, but the most important one was definitely: Do not export gigantic objects from your library, split them up and let clients compose the things that they want to use. That way they will have in their bundle only the things they really care about.

Another important learning was that, you should always expose utilities for the clients. That will allow them to create custom controls, which will inter-op correctly in the whole environment.

That was all I had regarding icons. Stay tuned for the next post, where I will talk how are we going to cut the bundle size even more.

The Startup

Get smarter at building your thing. Join The Startup’s +792K followers.

Sign up for Top 10 Stories

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Marija Najdova

Written by

Software Engineer @Material-UI | ex @Microsoft

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +792K followers.

Marija Najdova

Written by

Software Engineer @Material-UI | ex @Microsoft

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +792K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store