Revolutionising component imports with Webpack aliases

Francesco Lanciana
8 min readMay 27, 2018

--

Current me no longer believes this is a good idea

Feel free to still give this post a read but I no longer believe assigning an alias to every component (allowing easy fluid imports) is a good idea. There are two reasons for this:

  1. For everyone that has never seen this before (almost all people) this is magic and confusing. Magic should be avoided in large teams.
  2. VS Code has the concept of a jsconfig file that allows you to provide extra information for the editor to do its job. For Intellisense to work with webpack aliases you can define an object in the jsconfig where the key is your alias and the value is the path it maps to. VS Code will then know where to find the file imported with an alias so you can do things like path lookups when importing and jumping into the file. You can’t automate creating this map of aliases to paths because it’s in a json file. If you have an alias for every component manually defining this map is painful.

I have found simply providing a few aliases to key folders, e.g. Components, Styles, Utils, ect. and structuring your components in a sane way is actually pretty good. You will have to occasionally redo imports but it’s worth it for Intellisense and less magic.

The post

A project’s structure is often more fluid than you may think, particularly the location of components. A components location is usually based on how generic it is; with the more generalised components being located closer to the root folder of the project. Unfortunately it is almost impossible to perfectly predict what is needed upfront.

Design changes can easily increase the need for a specific component. Techniques picked up over time can open up new ways of generalising components. Even just liaising with a team mate can reveal a greater need for your component.

Therefore it seems odd that whenever you import a file you need to use either a relative or absolute path, both of which are subject to change if the component location changes. Why not abstract over the file system by utilising Webpack aliases in an automated way in order to make a project structure be as flexible and fluid as it can be. By that I mean being able to import any component you want with minimal effort, while completely removing the need to change import statements when a component is moved.

No more of this:

Much more of this:

The following article is written with ReactJS in mind, but adapting it for another library/framework is very straightforward. So without further ado, let’s begin. (If you already have some experience with Webpack aliases skip to the section: All the Aliases)

What are aliases?

Well, simply put Webpack aliases allow you to import modules with ease by having a particular string act as a shortcut for a file path. In our Webpack config if we have the following piece of code:

The path assigned to utilities is just an example

What we have just done is assign to the keyword ‘Utilities’ the full path to the utilities folder, i.e.

Now when we use the following import:

Webpack will essentially replace the string ‘Utilities’ so that the following example will become:

Webpack with only do this replace in import/require statements so the rest of your code is safe from tampering.

Why do we need aliases?

For example, let’s say you have an app. Let’s say it’s a dashboard app. A dashboard app that will revolutionise how people think about dashboards!

Crafted with love… in 20 minutes

This dashboard design has been carefully crafted to maximize productivity and efficiency. With this design in hand we now need to code it up! You can see it has a number of sections, so in all your modular wisdom you start creating the folder structure. Here’s where you end up.

This makes sense. You have a section for the graph and a section for the metrics in rings. The metric section contains some numbers with a ring around them to indicate something and make this example look a bit prettier. This seems like a pretty reusable component across a number of pages so you define it at the top level of the Components directory.

But now when you need to import it for use in the RingStats component you have to do:

That sucks… a lot. Instead we can define a Webpack alias to the word Components like so:

Now we can just write:

I think we could all agree that this is much better. However we can still do better! While we now have a shortcut to the Components folder, we still need to give the full path from the Components folder to the component we need. This will get plenty annoying if its multiple layers deep. It also hasn’t allowed us to move the PercentageRing component without changing the import statement.

All the Aliases

I would now like to make it so that every component, no matter where it is located can be imported with minimal effort. Furthermore, I would like to make it so that if we change the location of this component we don’t then have to change the corresponding imports.

But how? Wouldn’t that mean we have to define an alias for every single component manually? What is your secret sauce!?

Well… actually you were right. It’s as simple as creating an alias for every component that satisfies a set of conditions, just not manually; we are programmers after all.

Aliases under the hood

Why the set of conditions? Why not just create create an alias for every file you have?

The answer: Aliases don’t come for free.

For every alias you define, Webpack in turn instantiates an instance of the AliasPlugin class. When traversing your dependency graph it then cycles through all instances trying to find a match whenever it encounters an import/require statement. For larger projects containing a lot of imports and a lot of aliases this can add up.

Despite the fact that premature optimisation is the devil, I believe this is a worthwhile optimisation primarily because having fewer aliases actually enforces better practices in the long term (plus the logic to implement these conditions is negligible).

Assumptions and patterns

In order to achieve this audacious goal we need to decide which files deserve an alias, and therefore what our set of conditions is. To do this it helps to decide on our component’s structure.

The following component structure is definitely not the only option, but I have found it to work quite well.

  • Each component is placed in it’s own folder
  • The folder name and the component name are identical
  • The component’s styles and test suite are placed in the same folder
  • Components can be nested inside other components depending on their specificity
RingStats is made up of three PercentageRing’s (which are generic components), however it’s only used by MetricSection and is therefore a child of MetricSection

When using this structure the only file that needs an alias is the file containing the component (e.g. MetricSection.js). The test file doesn’t need to be imported as test runners allow you to specify which files are run using globs. The component styles should only ever be imported by the component so an alias in this case is irrelevant.

The conditions in this case are pretty straightforward. We only want files that satisfy the following conditions:

  1. The filename contains only a js or jsx extension (no .test or any other extension)
  2. The filename (without the extension) is identical to the folder name

One last thing, what should the alias name for each component be? This part is completely up to you but after having used this method for a while I have found that using the following format avoids the most confusion:

Components:the_component_name

For a while I used Components/the_component_name however in larger teams where very few of the devs were familiar with the webpack configuration, we found that this alias was being confused for an actual path name. The more we nested components the more confused everyone became.

The secret sauce

Ok! We have our conditions, we have our logic, we just have to write the code. So here it is:

When writing the code I found it useful to split the logic into two functions.

walkDirectorySync recursively walks the root directory of your components, compiling an array of files that meet condition 1. (This is the more rigid of the two conditions).

createComponentAliases cycles through the array returned by walkDirectorySync, only keeping entries that meet condition 2. In this way it’s easy to add or remove additional conditions. This is the function you invoke with a top level directory to receive an object containing an alias for every component.

The very last step is to actually assign the object returned by createComponentAliases in the webpack config like so (the directory passed in will be different for you):

So now when you go to import PercentageRing we can simply write:

It doesn’t matter where the component is, we never need to change this line.

How I Do It

I know the last thing we all need is another boilerplate project, however I have included a link to the webpack configuration I am currently using. Who knows? You might even find it to be useful. Look in webpack.common.js to see how I assign the aliases.

Link: My Webpack Template

Final Thoughts and remarks

It would be interesting to see if the lookup for aliases could be implemented with a Trie structure. In this case the lookup could be done in O(1) time which would make having an alias for 1000’s of files a much more efficient endeavour. It is completely possible that the time needed to create the trie structure might outweigh any benefit to actually using it… but considering the number of import/require statements is usually far greater (when taking into account node modules) than the number of aliases this might not be a problem.

This implementation was inspired by the idea of a colleague from work who wanted to allow importing a component that had the same name as its containing folder to not involve typing the same thing twice (e.g. import example from “./example/example.js”).

I’d love to hear your thoughts on this approach, and if anyone actually tries it out, do let me know!

--

--