DEV Community

Cover image for esbuild & react-native-web - IV: vector-icons
Dalci Bagolin
Dalci Bagolin

Posted on • Edited on

esbuild & react-native-web - IV: vector-icons

The react-native-vector-icons (rnvi) is a popular library in the react-native world, which is compatible with react-native-web.

But there is a problem. When used with react-native-web, rnvi significantly increases the bundle size. Each ttf font has a corresponding GlyphMap, which is a json file that associates the icon's name with its Unicode value. Even if you use only one icon in your project, the entire GhyphMap and the entire ttf will be imported by esbuild during the bundling. Some GlyphMaps, such as Ionicons and MaterialCommunityIcons, are over 100 kb, and minimization has almost no effect. And the MaterialCommunityIcons.ttf, which is more than 900 kb, will be download into your app.

It can get even worse, because popular libraries as react-native-paper and @react-navigation/material-bottom-tabs depend on rnvi for the MaterialCommunityIcons font. You can opt-out rnvi using babel-plugin-optional-require, but, as you can infer, it doesn't work with esbuild. When you install one of these libraries in a react-native-web project, esbuild will ask you to install rnvi and will bundle the large GlyphMap for the MaterialCommunityIcons, even if you haven't used any icons in your project.

In this post, we will discuss some options to solve this problem.

Expo

Before we discuss the options, let me give you a suggestion. Use @expo/vector-icons instead of react-native-vector-icons, because @expo/vector-icons is distributed with ES modules, that allow tree-shaking and it will configure the icon font in your webpage automatically for you:

<html>
<head>
<style id="expo-generated-fonts" type="text/css">
@font-face {
    font-family: material-community;
    src: url(/assets/MaterialCommunityIcons-QCSDDVWU.ttf);
    font-display: auto;
  }
</style>
...
Enter fullscreen mode Exit fullscreen mode

@expo/vector-icons allow the use of customized fonts or subsets of fonts created with Fontello or Icomoon. Unfortunately, these sites don't work with MaterialCommunityIcons.

Behind the scenes, @expo/vector-icons uses the library expo-font to do it. You can use these Expo libraries even out of a Expo project.

To use @expo/vector-icons with react-native-web and esbuild, configure your tsconfig.json/jsconfig.json:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "react-native": ["./node_modules/react-native-web"],
      "react-native-vector-icons/MaterialCommunityIcons": [
         "./node_modules/@expo/vector-icons/MaterialCommunityIcons.js"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

You need to specify one alias for each icon font that you use.

Solutions

Removing MaterialCommunityIcons when you are not using any Icon with react-native-paper or react-navigation

If you are using react-native-paper or @react-navigation/material-bottom-tabs and you are not using any icon from MaterialCommunityIcons, you can remove it with the following plugin:

require('esbuild')
  .build({
    ...
    plugins: [{
        name: 'remove-materialicons',
        setup(build) {
          build.onLoad({ filter: /MaterialCommunityIcons.js/ }, () => ({
            loader: 'jsx',
            contents: `  
                import * as React from 'react';
                import { Text } from 'react-native';
                export default ({ name, color, size, ...rest }) =>  (
                  <Text {...rest} 
                    style={{backgroundColor: 'transparent', color, fontSize: size }}
                    pointerEvents="none"
                    selectable={false}
                  >□</Text>
                );
              `,
          }))
        },
      },
    ]
  })
Enter fullscreen mode Exit fullscreen mode

For it to work you need to alias rnvi to @expo/vector-icons, according to the jsconfig.json/tsconfig.json above.

Using material-icons-subset

The best solution is to use the package material-icons-subset to create a subset of the MaterialCommunityIcons font.

npm i material-icons-subset

Pass a list of icon names to be included in the font as arguments in the command line:

material-icons-subset camera menu account-outline email archive 
Enter fullscreen mode Exit fullscreen mode

Or, you can pass the path for a config.json file:

material-icons-subset font-config.json 
Enter fullscreen mode Exit fullscreen mode

The config.json file must have an array called icons with the name of the icons to be included in the font:

{
   "icons": [
      "camera",
      "menu",
      "account-outline",
      "tune",
      "bookmark-outline",
      "pause",
      "arrow-left",
      "archive",
      "email"]
}
Enter fullscreen mode Exit fullscreen mode

The library will create two files:
materialdesignicons-webfont.ttf and
materialdesignicons-webfont.json with the GlyphMap.

You need to alias MaterialCommunityIcons.ttf and MaterialCommunityIcons.json files to the respectively new files that you created with material-icons-subset.
Unfortunately, I couldn make it work in the jsconfig.json/tsconfig.json.
Therefore, you need to use a esbuild plugin to make it work:

const materialIconsPlugin = {
  name: 'material-icons',
  setup(build) {
    build.onResolve({ filter: /MaterialCommunityIcons\.(ttf|json)/ }, (args) => ({
      path: resolve(`./src/assets/materialdesignicons-webfont${parse(args.path).ext}`),
    }))
  },
}

Enter fullscreen mode Exit fullscreen mode

Conclusion

That's it. Now you can use vector-icons with react-native-web and esbuild without compromising the bundle size of your app.

Top comments (0)