Babel For Fun and Profit

Louis Lombardo
4 min readSep 26, 2020

Using react-native-bundle-visualizer to check on the size of my React Native bundle gave me quite the surprise the other day.

2.28MB for icons? Something was wrong. Poking around on the React Native Font Awesome Repo I found this issue:

Looking at my repo I found several instances of what I now know is the wrong import statement:

import { faTrash } from “@fortawesome/free-solid-svg-icons”;

Sitting and thinking about updating all of these imports across my repo seemed really annoying. I also knew that the auto-import VSCode would do for me would be wrong. I thought for a little while about writing an ESLint rule to fix the imports and prevent this from happening again. But an ESLint rule seemed like a gross fix. No one wants to see giant blocks of imports at the tops of their files like this:

import { 1 } from “@fortawesome/free-solid-svg-icons/1”;import { 2 } from “@fortawesome/free-solid-svg-icons/2”;import { 3 } from “@fortawesome/free-solid-svg-icons/3”;import { n } from “@fortawesome/free-solid-svg-icons/n”;

When they could just have this:

import { 1, 2, 3, … n } from “@fortawesome/free-solid-svg-icons”;

But what if I could have my cake and eat it too? What if I wanted the nicer import style without the nasty bundling? Turns out you can with Babel!

Just want the fix? You can check out the finished Babel plugin GitHub Repo

What is Babel?

“Babel is a JavaScript compiler” — Babel

Basically, Babel lets you provide JavaScript in one format and get another as an output. It's useful for polyfills, syntax transformations, and just about any crazy thing you can think of. Babel works by transforming the AST of your code. A Babel plugin is just a collection of functions that run against the AST provided to them by Babel. You check out what the AST of your code looks like with AST explorer. The AST for our example looks like this:

One of the other great things about AST Explorer is in the bottom left corner. Here you can write a babel plugin and see it’s output in the bottom right. Here is the complete code for our plugin:

export default function (babel) {
const { types: t } = babel;
return {
visitor: {
ImportDeclaration(path) {
if (!path.node.source.value.match(/^@fortawesome\/.+svg-icons$/gm)) {
return;
}
path.replaceWithMultiple(
path.node.specifiers.map((specifier) => {
return t.ImportDeclaration(
[
t.ImportSpecifier(
t.Identifier(specifier.local.name),
t.Identifier(specifier.imported.name)
),
],
t.stringLiteral(
`${path.node.source.value}/${specifier.imported.name}`
)
);
})
);
},
},
};
}

If you want to follow along you can check out the plugin in the AST Explorer

Let’s break it down

An ImportDeclaration is a Babel type. You can read more about it here. The body of ImportDeclaration is a function that runs whenever it’s passed an ImportDeclaration. The first thing it does is check to see if we should modify this import statement. I used Regex101 to create a small regex for this but you could really do anything you want here.

if (!path.node.source.value.match(/^@fortawesome\/.+svg-icons$/gm)) {
return;
}

What’s important to note here is that this won’t match to any import statements that are “correct”.

import { icon } from "@fortawesome/<icon set>-svg-icons"; // Matches
import { icon } from "@fortawesome/<icon set>-svg-icons/icon"; // Won't Match

This is important because Babel will consider any nodes you add here as new and pass them to your function again. If you’re not careful you can easily get caught in an infinite loop.

The next part of the code is fairly self-explanatory. If you’re not sure what to provide as arguments you can check the Babel types. If you aren’t sure what kind of manipulations you can do check out the Babel Handbook.

path.replaceWithMultiple(
path.node.specifiers.map((specifier) => {
return t.ImportDeclaration(
[
t.ImportSpecifier(
t.Identifier(specifier.local.name),
t.Identifier(specifier.imported.name)
),
],
t.stringLiteral(`${path.node.source.value}/${specifier.imported.name}`)
);
})
);

So how did we do?

// Source Code
import { faCalendar, faUsers } from "@fortawesome/free-solid-svg-icons";
import { faUsers as proUsersIcon } from "@fortawesome/pro-light-svg-icons";

✨ Babel Magic ✨

// Bundle
import { faCalendar } from "@fortawesome/free-solid-svg-icons/faCalendar";
import { faUsers } from "@fortawesome/free-solid-svg-icons/faUsers";
import { faUsers as proUsersIcon } from "@fortawesome/pro-light-svg-icons/faUsers";

Looks great! Let’s check our React Native bundle again.

2.47MB! We were almost able to cut our bundle in half!

Making Your Own

I hope I’ve made you excited to start getting into the nitty-gritty with Babel. ESLint and Babel plugins are a great way to learn how to work with ASTs if you don’t already. Happy Hacking!

Links

--

--