Why You Should Use Babel Resolvers

Or how to avoid ../../../ and similar circles of hell.

Izaak Schroeder
Dec 4, 2018 · 5 min read
Your new best friend.

The Problem

You’re developing a project with a sprawling directory structure and you have a ton of imports. Somewhere in there you’ve probably found yourself writing code like this:

import A from '../../../pages/A';
import B from '../../../pages/B';
import Container from '../../shared/components/Container';const App = () => (
  <Container>
    <Switch>
      <Route path="/" exact component={A}/>
      <Route path="/" exact component={A}/>
    </Switch>
  </Container>
);

Babel to the rescue

There is a babel plugin for doing custom module resolving available here: https://github.com/tleunen/babel-plugin-module-resolver. After installing it you can configure it:

{
  "plugins": [
    ["babel-plugin-module-resolver", {
      "alias": {
        "#shared": "./extra/shared",
        "#pages": "./pages"
      }
    }]
  ]
}
import A from '#pages/A';
import B from '#pages/B';
import Container from '#shared/components/Container';

A correct aliasing strategy

As soon as people realize they have to type less in order to import things, all bets are off and aliases are created for everything with totally arbitrary values. This is generally recognized as bad.

import test from 'test'; // Is this an alias or a module?
{
  "plugins": [
    ["babel-plugin-module-resolver", {
      "alias": {
        "": "./src",
      }
    }]
  ]
}
import Button from '/component/base/Button';
import X from '/Users/izaakschroeder/Projects/...';
import Cart from '#cart';
import Checkout from '#xo'import cardReducer from '#cart/reducer';
import checkoutReducer from '#xo/reducer';

Linting resolver imports

If you have tooling that verifies you’re importing correct/existing module paths like eslint-plugin-import then you also have to make sure it is setup correctly to handle these aliases.

npm install eslint-import-resolver-babel-module
rules:
  import/parser: babel-eslint
  import/resolver:
      eslint-import-resolver-babel-module:

Keeping flow happy

If you use the flow type checker then you also need to configure it to understand how to resolve aliased modules. Thankfully flow provides a module.name_mapper configuration directive which you can use to port over your alias rules. Our example might look something like:

# The rule for the `/` -> `./src` mapping:
module.name_mapper='^/\(.*\)$' -> '<PROJECT_ROOT>/src/\1'# The rules for the cart/checkout example mappings:
module.name_mapper='^#/cart\(.*\)$' -> '<PROJECT_ROOT>/cart/\1'
module.name_mapper='^#/xo\(.*\)$' -> '<PROJECT_ROOT>/checkout/\1'

Aside: Alternative by using webpack

For completeness, you can probably also use webpack to achieve this; it has a resolve.alias configuration option which allows for similar behaviour and has a corresponding eslint resolver plugin available. Generally I shy away from a webpack solution when a babel solution is viable, since many more projects and tools use babel (either standalone or with webpack). The part where the webpack version shines is when you need to hijack entire modules in your build (e.g. mapping third party dependency react to inferno). Things like this are typically not done with babel since babel is rarely configured to run on things inside the node_modules folder.

Onward & upward

Custom module resolvers will save you time and the frustration of dealing with ../ splattered everywhere. They take a bit to setup and ensure full cooperation with existing tooling, but the result and visual satisfaction of never having to see ../../../../../.. is well worth the initial outlay on big projects.

Bootstart

This will go.

Izaak Schroeder

Written by

Things.

Bootstart

Bootstart

This will go.