Making refactorings easier with folder aliases

What are aliases ?

Aliases are a way to use absolute paths to easily access files of your application, like you would do with modules.
For instances, instead of ../../../../components/Button you would use ALIAS/components/Button.

What it brings is the flexibility of moving entire folders when refactoring without having to rewrite all the imports from your own components.

How to define them ?

Webpack contains a full chapter about configuring resolvers here. In short they allow you to make shortcuts and configure the way Webpack will search for your modules.

By using the property resolve.alias we are able to effectively say that our src folder can be accessed through an alias. At first we wanted to have an alias for every subfolder our src folder, with their name being the same, for example components and services, but we realized that it would probably be easier (ie. easier to grep and less likely to shoot ourselves in the foot) to have only one alias clearly defined and that wouldn't cause any potential issue (a conflict with a npm package for instance).

So we decided to use __package_name__ (replace package_name by the name of your app) as our alias. Our alias definition looks like this :

alias: {
  __package_name__: path.resolve('src'),
}

Now, we can safely import our components by using import Button from '__package_name__/components/Button'.

Adapting the toolchain

While this all works well with Webpack, there are others parts of the environment that we needed to adapt in order to make sure they understand those aliases and follow the imports paths correctly.

Flowtype

As explained in the configuration documentation, you can use the module.name_mapper property to provide a replacement pattern similar to the aliases. Thus, you need to edit your .flowconfig file to understand the aliases you've setup, and, quite sadly, you have to keep it in sync manually. Whenever you change your aliases or define new ones, you will need to update your Flow config as well.

For our example with the __package_name__ alias for src, the configuration would contain two replacement patterns:

module.name_mapper='^__package_name__\/\(.*\)$' -> '<PROJECT_ROOT>/src/\1'
module.name_mapper='^__package_name__$' -> '<PROJECT_ROOT>/src'

ESLint

A module for ESLint exists that uses the Webpack config to resolve paths and check their existence. Two versions exist on npm :

  • the standalone version eslint-import-resolver-webpack,
  • the more generic version eslint-plugin-import.

As we are working with the Airbnb coding rules, and their associated ESLint configuration, we are stuck with a version of eslint-plugin-import that didn't feature the Webpack resolver, and we are forced to use the standalone version. With newer versions of the plugin (>= 13.0.0), you shouldn't have to install the Webpack resolver as a standalone.

It's pretty easy to configure, as you only need to link the configuration to your webpack one, and it will use the aliases:

"settings": {
  "import/resolver": {
    "webpack": {
      "config": "path_to_webpack_config"
    }
  }
},

Test harness

As our test runners don't necessarily use Webpack (viz. Mocha's built-in test runner), we need to make sure that the code used to run the tests loads the files properly. As we use Babel, what we do is using the babel-plugin-webpack-alias plugin, in our test environment only. This way, when the code is transpiled to be tested, the imports will use the aliases properly.

The config is very similar to what we did with ESLint, with the following part in our .babelrc:

"test": {
  "plugins": [
    [ "babel-plugin-webpack-alias", { "config": "path_to_webpack_config" } ]
  ]
}

IDEs

The last issue that we've had it the fact that many IDE allow you to go directly to the definition of an import with a simple shortcut. When we use the relative paths, or a npm module, the IDE path resolver usually works well. However none of the several IDEs that we use across the team (Atom, Intellij IDEA, Visual Studio Code) seems to be able to handle that, even through plugins.

Overall, as much as we'd prefer to have the ability for our IDE to resolve those paths correctly, the upsides of the aliases heavily outweigh this hindrance.

results matching ""

    No results matching ""