In this ongoing series, I am putting together a Github Repository Template for my "Go To" front-end tech stack of Next.js, React, TypeScript etc. Between the 2nd and 3rd posts in the series, I set up ESLint and Jest Testing to work with TypeScript. In this post, I'm going to introduce path aliases to make our imports cleaner.
Just want the Code? View the full changeset covered in this post or check out the 0.0.4 release of the repository.
Path Aliases - What and Why?
If you have ever spent more than 10 seconds trying to figure out where in your directory structure a module you want to import lives relative to the current file, you might need path aliases. If you have import statements that look like the following, path aliases might be for you.
import ('../../../../src/components/layout/CoolComponent')
While you can create lots of aliases, I tend to just want one to leverage absolute imports of things in my /src folder. As such, the above contrived import would become:
import ('~/components/layout/CoolComponent')
That feels nicer. Let's make it happen. Oh and I want to make sure ESLint and Jest testing continue to work as well.
Prerequisites
If you are following along, please complete part 3 of the series. I am immediately picking up where that post left off. Note: I accidentally, left an easter egg in my Jest config in the 0.0.3 release that will make things smoother later. I'll call it out when I get to that step below to avoid confusion.
Create an Ugly Import
In the previous post, I introduced the /src directory. Remember, Next.js pages, MUST exist in the /pages directory of our project root and every file must export a page component. Because of this, I tend to keep my page components light and have the actual display components live as "screen" components of the /src folder. This also makes them more testable.
Within the /src directory, I'll create a directory called screens. Inside that directory, I'll create a file called IndexContent.tsx with the following content pulled from the existing IndexPage component.
/src/screens/IndexContent.tsx
import React from 'react';
interface IndexProps { greeting: string }
const IndexContent: React.FC<IndexProps> = (props) => {
const { greeting } = props;
return (
<div>
<h1>
{greeting}
๐
!
</h1>
</div>
);
};
export default IndexContent;
Now I can remove the display logic from the Next.js Index page and let it just be responsible for routing and props resolution, etc. The new /pages/index.tsx file looks like:
/pages/index.tsx
import React from 'react';
import {
GetStaticProps, NextPage, GetStaticPropsContext, GetStaticPropsResult,
} from 'next';
import IndexContent from '../src/screens/IndexContent';
interface IndexProps { greeting: string }
const IndexPage:NextPage<IndexProps> = (props: IndexProps) => {
const { greeting } = props;
return (
<IndexContent greeting={greeting} />
);
};
export const getStaticProps: GetStaticProps = async (context: GetStaticPropsContext):
Promise<GetStaticPropsResult<IndexProps>> => ({
props: {
greeting: 'Hello Next.js',
},
});
export default IndexPage;
Note of the import in pages/index.tsx of the new IndexContent component.
import IndexContent from '../src/screens/IndexContent';
This might not seem terrible, but once I add more directory structure, it can get ugly pretty quick.
Define Path Aliases in tsconfig.json
Again, I'm just going to create a simple alias for for the /src directory so I can have absolute imports. I will use the ~ (tilde) symbol to be an alias for the /src directory. To do this, I need to simply add the following to the compilerOptions in the tsconfig.json:
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"],
}
...
At this point, the tsconfig.json should look like this.
Next I'll update the pages/index.tsx file to leverage the new import.
/pages/index.tsx
import React from 'react';
...
import IndexContent from '~/screens/IndexContent';
...
At this point, if when I restart your dev server via npm run dev
the page works as expected.
This works because Next.js plays well with TypeScript and tells the underlying Webpack setup to automagically obey directives in tsconfig.json. We are not so lucky with ESLint and Jest.
Updating ESLint for Aliases
Even though the Next.js app runs fine with the introduction of aliases, ESLint errors on the aliased path resolution inside of VSCode and when running npm run lint
:
Even though ESLint knows how to deal with TypeScript files, it doesn't obey the paths and baseUrl directive added above to tsconfig.json. Annoyingly, I need one more ESLint plugin to make this happen.
Run the following in the terminal to install the plugin.
npm install --save-dev eslint-import-resolver-typescript
Finally, to use the plugin, I have to update the existing eslintrc.json settings directive specifically TypeScript.
...
"settings": {
"import/resolver": {
"typescript": {}, // this loads <rootdir>/tsconfig.json to eslint
"node": {
"extensions": [".ts", ".tsx"]
}
}
},
...
At this point the eslintrc.json looks like this.
Running npm run lint
again, the path errors go away. Also, the errors in the IDE go away as well - although I had to restart the VSCode to have it pick up the changes.
At this point, I'm able to npm run build
and npm start
without any errors, as well. Almost done! One last thing... testing.
Updating Jest for Aliases
Why should the test suite not benefit from aliases as well? Next, I will ensure that Jest plays nicely with path aliases.
As a before comparison, here is the output from npm run test
for the tests introduced in part 3 of this series.
Next, I'll modify /src/utilities/add.test.ts to use our new alias.
import add from '~/utils/add';
...
The test file should now look like this
When I run npm run test
I receive the following error:
The reason for this error is that Jest doesn't know how to deal with aliases.
Note if you didn't get that error, it is likely because you are following along with the repository. In the 3rd part of the series, I accidentally added the necessary Jest config option to make this work. Feel free to read the next step however, as it is important to understand.
To make Jest understand path aliases, I need to add the following directive to the Jest configuration, which I chose to include in the package.json file in Part 3 of the series.
"jest": {
...
"moduleNameMapper": {
"~/(.*)$": "<rootDir>/$1"
},
...
}
At this point, my package.json looks like this
Now running npm run test
, the tests should pass. Note that coverage is maintained as well!
With the tests passing, I now have alias support for the app, ESLint, and Jest testing!
Closing Thoughts
The Github Project Template is almost ready to be useful - I have a Next.js app written in TypeScript, with ESlint and Jest Testing ready to go, and the benefits of path aliases. View the full changeset covered in this post or check out the 0.0.4 release of the repository. In part 5 of the series, I'll introduce Next.js environment variables and runtime configuration.
Discussion Topic
- If you use path aliases, what do you use them for?
- What other TypeScript features have you discovered the world should know about?
Image Credit: "Peaches" by Robbin Gheesling is licensed under CC BY-NC-ND 2.0