Webpack Aliases Keep My Code Sane
Michael McShinsky

Webpack Aliases Keep My Code Sane

How to resolve relative paths cleanly in JavaScript and TypeScript by using webpack aliases.

June 22, 2020

Reference Mistakes Make Developers Look Bad

If you were to ask me what are one of the most common mistakes are in basic web development, I would, without hesitation, tell you that reference errors are one of the big ones out there. Whether they are with variables or package imports, I encounter and solve many problems for myself and other developers that make these mistakes constantly. Specifically with package imports, I wanted to stop dealing with changing all the path locations every time files were refactoring and improved.

You are probably familiar with what this looks like with some example code below.

    import { UserCard } from '../../../components';

    import { apiServices, themeServices } from '../../../services';

    import { objUtil, dateUtil } from '../../../utils';

What happens when you move a file or do a mass update to references throughout your app? You have to painfully go through and make sure every reference is correct. Next, you have to confirm that none of your pages crash or fail if your compiler is misconfigured and doesn’t recognize it as an error. Even worse… you reference another file on accident that has the same named export. As a result, you don’t notice until the code is pushed to production and now customers are calling in complaining that features have stopped working.

Aliases to the Rescue

Webpack solves these problems by giving us the ability to use “aliases”. Aliases are a way to let webpack know where to find our code by providing a word or character that represents a partial reference to where the code is located. Once webpack knows this, the code can be properly resolved while it is compiling during development or building the final package. This is especially useful, not only for JavaScript and TypeScript, but CSS as well.

In order to add this feature to our code, we need to start out by setting up our “resolve” inside webpack. Your code will probably end up looking similar to this:

    const path = require('path');

    module.exports = {
      //...
      resolve: {
        extensions: ['js', 'jsx', 'ts', 'tsx'],
        alias: {
          '@': path.resolve(__dirname, 'src'),
          '@components': path.resolve(__dirname, 'src/components'),
          '@utilities': path.resolve(__dirname, 'src/utilities')
        }
      }
      //...
    };

You can use as many aliases as you want if you have the use case for it. Overall, it ends up being pretty easy to use only the ‘@’ character as an alias to reference the ‘src’ folder, but you can create any name for any alias path that you want to. Once we have modified our webpack configuration to be similar to the code above, our old imports could now look something similar to this:

    import { UserCard } from '@/components';

    import { apiServices, themeServices } from '@/services';

    import { objUtil, dateUtil } from '@/utils';

So much better! Now when you refactor your code, you will always be referencing the same packages you intended to.

Working with Babel and TypeScript

If you are resolving your code in combination with webpack inside of Babel, ESLint or TypeScript, you may need to update their config files. These file could be: .babelrc, .babel.json, .eslintrc.js, tsconfig.json, config.ts, etc… depending on how you are set up. There are a couple differences in these files you may have to make as opposed to the webpack.config.js when resolving aliases.

Babel

    // .babelrc
    // npm i babel-plugin-module-resolver -D

    {
      "plugins": [
        "module-resolver",
        {
          root: ["./src"],
          alias: {
            @: "./src",
          }
        }
      ]
    }

ESLint

    // .eslintrc.js
    // npm i eslint-import-resolver-alias -D

    const path = require('path');

    module.exports = {
      //...
      settings: {
        'import/resolver': {
          alias: {
            map: [['@', path.resolve(__dirname, 'src')]],
          },
        },
      },
    };

TypeScript

    // tsconfig.json
    // npm i tsconfig-paths-webpack-plugin -D

    {
      //...
      "compilerOptions": {
        "baseUrl": "src",
        "paths": {
          "@/*": ["*"]
        }
      }
    }

If you don’t want to resolve your aliases in TypeScript and webpack, here is an example where we use both our TypeScript and webpack configuration files, but using a package install, keep the aliases inside our tsconfig.json. Also note that you usually only need the alias configuration in your .babelrc file if you are using webpack and defining the aliases in webpack. The overall goal for moving our aliases into TypeScript and Babel config files in the following example can be to keep our code DRY and in one location. This can avoid updating one configuration and then accidentally forgetting to update it elsewhere.

    // webpack.config.js
    // npm i tsconfig-paths-webpack-plugin -D

    const path = require('path');
    const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')

    module.exports = { 
      //... 
      resolve: {
        extensions: ['js', 'jsx', 'ts', 'tsx'],
        plugins: [
          new TsconfigPathsPlugin()
        ],
      } 
    };

Combine this code with your TypeScript “tsconfig.json” alias setup are you are up and ready to go!

Summary

Aliases taking over major references for variable and file imports make developers look good. Your code will look cleaner and you are much less likely to make mistakes with file refactoring and consolidation. After I had discovered aliases, I make sure that they exist in just about every project I work on. I would encourage you as well to consider if aliases are the right answer for you in saving yourself from one more bug to take care of or worry about before shipping code anywhere.