Working with imports in JavaScript applications can be a mess if you have deeply nested folder structures.
This can be even worse if your editor or IDE doesn't have intellisense or if it's not configured properly.
So you end up with some imports like this:
// ❌
import { useForm } from "../../../../hooks/useForm";
import { TextInput } from "../../../form/TextInput";
import { Button } from "../../../ui/Button";
import { loginValidations } from "../../../../utils/validations";
import { routes } from "../../../../config/routes";
This will work but doesn't scale nor is readable to the developer.
TypeScript Path Mapping
When using TypeScript it's very common to use path mapping. This is useful because you can omit the relative paths from the import and then get the module using an absolute import.
// ✅
import { useForm } from "@hooks/useForm";
import { TextInput } from "@components/form/TextInput";
import { Button } from "@components/ui/Button";
import { loginValidations } from "@utils/validations";
import { routes } from "@config/routes";
This can be achieved adding the paths
config to your tsconfig.json
:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./src", // This is to avoid adding the ./src prefix to each path below.
"paths": {
"@components/*": ["components/*"],
"@hooks/*": ["hooks/*"],
"@utils/*": ["utils/*"]
"@config/*": ["config/*"]
}
}
}
This improves the readability a lot, and also if you move your components or modules to another folder, the chances that you need to update the imports are very low.
Vite + TypeScript Path Mapping
If you try to create paths using Vite you'll notice that it's not possible, since Vite doesn't know how to build the imports by default.
There are two solutions to this issue: adding the paths manually to the Vite build config or using a third-party package.
Adding paths manually
In order to add the paths manually to Vite build you'll need to configure all the aliases in vite.config.ts
:
// vite.config.ts
// ...
import path from "path";
export default defineConfig({
// ...
resolve: {
alias: [
{
find: "@components",
replacement: path.resolve(__dirname, "src/components"),
},
{
find: "@utils",
replacement: path.resolve(__dirname, "src/utils"),
},
{
find: "@hooks",
replacement: path.resolve(__dirname, "src/hooks"),
},
{
find: "@configs",
replacement: path.resolve(__dirname, "src/configs"),
},
],
},
});
// OR EVEN BETTER:
const aliases = ['components', 'utils', 'hooks', 'configs'];
export default defineConfig({
// ...
resolve: {
alias: aliases.map(alias => (
{
find: `@${alias}`,
replacement: path.resolve(__dirname, `src/${alias}`),
},
))
},
});
The downside of this approach is that every time the paths on tsconfig.json
change you'll need to update the vite.config.ts
too. This is not an ideal workflow.
Using vite-tsconfig-paths
To solve that you can use a package called vite-tsconfig-paths which does all that boring and repetitive work.
It's very simple to use. All the configuration can be done in 2 steps:
- Install it as a dev dependency:
# npm
npm i vite-tsconfig-paths --save-dev
# yarn
yarn add -D vite-tsconfig-paths
- Then add it as a Vite plugin:
// vite.config.ts
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [tsconfigPaths()],
});
And that's it! Every new path added will work without any changes needed.
If you have any feedback or suggestions, send me an email
Great coding!
Top comments (0)