We've all been there: you're multiple folders deep in a project, and you must reach up to grab a component in a top-level folder with an unknown level of ../
's to traverse.
These complications increase drastically as a project grows in size and complexity; they might even cause fear whenever the word "refactor" is uttered. Fear not! There is a solution to your woes—path aliases.
What Are Path Aliases?
Path aliases are a way to change the starting location of an import from your file to a custom-named, predetermined destination. While not all paths should be aliased, path aliases are another tool to help simplify the development process. They should be an access point for commonly used files like reusable components, utility functions, and services.
Think of it as giving direction from a familiar place instead of from the starting point. Rather than pathing out from your current file, with path aliases, you can access components and other necessary code like this:
import FancyComponent from "@components/FancyComponent";
Path aliases are not a JavaScript feature; they are a tooling mechanism provided by bundlers through their module resolvers to help improve project architecture and developer experience (DX).
Path aliases allow developers to give semantic names to modules frequently accessed in an application. By using meaningful (semantic) aliases, we get the added benefit of conveying a clear intention of the import. Lastly, path aliases make refactors and folder reorganizations smoother since moving files has fewer consequences.
How Do Path Aliases Work?
Bundlers contain a resolver method to manage their module resolution. Common React tooling such as Create React App, NextJS, and Storybook use Webpack as their bundler.
Webpack uses enhanced-resolve to resolve modules during their bundling. There are plenty of other bundlers out there like Rollup (used by Vite), Browserfy, and Parcel, all of which support path aliases.
Path aliasing can be used for any type of file import. Whether a JavaScript, TypeScript, CSS, or image file: If you can import it - you can alias it!
How to Implement Path Aliases
This example will use module aliasing for a project using JavaScript/TypeScript with Webpack, along with an additional consideration like Storybook. The following sections will assume your project uses Webpack and that you have access to the configuration file.
Create React App (CRA) does not give developers access to the Webpack config under the hood; however, there are libraries like CRACO that allow you to modify the Webpack config.
Create Module Aliases in Webpack
For this example, let's assume you have a preexisting application running on Webpack that looks like this:
src
└── services
└── components
└── utilities
└── app
Ideally, an import from services, components, or utilities from these directories will look like this.
import <thing> from '@<directory>/<additional-path-if-needed>'
Note: the @ is NOT required, it is just a standard convention. Your alias can be anything!
Establish the Module Aliases
- Open your
webpack.config.js
file. - If it's not already there, add a resolve property to the configuration object and make it an empty object.
- Add a property called “alias” set to another empty object inside the resolve object. It should look like this:
const config = {
// ... rest of Webpack configuration
resolve: {
alias: {},
},
};
From here, all that's left is to create the actual alias.
const path = require("path");
const config = {
// ... rest of Webpack configuration
resolve: {
alias: {
"@components": path.resolve(__dirname, "src/components"),
"@services": path.resolve(__dirname, "src/services"),
"@utilities": path.resolve(__dirname, "src/utilities"),
},
},
};
The snippet above uses node's path module, which which provides the resolve function that pieces your paths together. Now all that's left to do is to use your path aliases.
import FancyComponent from "@components/FancyComponent";
How to Use Path Aliases with TypeScript & Storybook
If you're using additional tools like TypeScript and Storybook with your application, you'll need to inform them of the new aliases in order for them to piece together properly.
Inform TypeScript of the Module Aliases
To inform TypeScript of the new path aliases, you can add a paths property to the compilerOptions
of the tsconfig
file:
{
"compilerOptions": {
//...rest of compiler options
"paths": {
"@components/_": ["./src/components/_"],
"@services/_": ["./src/services/_"],
"@utilities/_": ["./src/utilities/_"]
}
}
//... rest of config
}
Note: If you set a
baseUrl
option in thecompilerOptions
, the paths need to be relative to the baseUrl. Recreating the example above with abaseUrl
we'd have
//... rest of compiler options
"baseUrl": "src/",
"paths": {
"@components/_": ["components/_"],
"@services/_": ["services/_"],
"@utilities/_": ["utilities/_"]
}
Something to note is that Webpack takes a string and the tsconfig takes an array. This allows for fallback locations, which are other places the compiler can look for the file in case it's not in the specified path. Webpack version 4 does not support this notation. Webpack 5 introduced this functionality, so if needed, you will need to bump your Webpack versioning to Webpack 5
Using Module Aliases with Storybook
Storybook is an open-source tool for building UI components and pages in isolation. Many applications use it, and out of the box, Storybook won't know about your custom module aliases. Storybook is powered by Webpack and provides a way for you to update the config before the build occurs.
How to update Storybook's config
- Go to
main.js
in the.storybook
directory. - Add a
webpackFinal
field to the exported configuration object. This field expects an async callback with the Webpack config as the first argument. - Update the config to add your aliases.
It's important to note that this isn't one-to-one with the Webpack config set up earlier. You'll need to update the path.resolve here in order to properly resolve these aliases since this file doesn't exist at the project root.
module.exports = {
// ... rest of config
webpackFinal: async (config, other) => {
config.resolve.alias = {
"@components": path.resolve(__dirname, "../", "src/components"),
"@services": path.resolve(__dirname, "../", "src/services"),
"@utilities": path.resolve(__dirname, "../", "src/utilities"),
};
return config;
},
};
If you're using Webpack in your project, you don't need to redefine these aliases. You can use the app's webpack.config.js
inside this callback.
const projectConfig = require("../webpack.config");
module.exports = {
//... rest of config
webpackFinal: async (config) => {
return {
...config,
resolve: { ...config.resolve, alias: { ...projectConfig.resolve.alias } },
};
},
};
Conclusion
Path aliases are a great way to simplify the development process. Path aliases allow you to give semantic names to the modules you access most frequently. Using meaningful aliases provides the added benefit of conveying a clear intention of the import and simplifying refactors and reorganizations.
Webpack enables you to implement your path aliases across tools like TypeScript and Storybook, optimizing your application and streamlining your development process.