How to fix "resolve.alias" problem in Vite.js

This article was published on Jul 16, 2022, and takes approximately 2 minutes to read.

I've decided to migrate the building of one of my packages from esbuild o use vite.

My driver was mainly because I had both installed into my repository, and because they were doing the same thing (building packages), I wanted to stick with a single one.

Also, Vite.js is getting more and more traction among the community due to its power, simplicity, and stable API, but that's not the subject here.

My package is just a collection of small functions written in TS. From there, I need to parse it to both ES Modules and CommonJS.

So, I had a very simple vite.config.ts:

vite.config.ts
import path from "path";
import { defineConfig } from "vite";

function isExternal(id: string) {
  return !id.startsWith(".") && !path.isAbsolute(id);
}

export default defineConfig(() => ({
  resolve: {
    alias: {
      '~/config': path.resolve(__dirname, './src/config/'),
      '~/utils': path.resolve(__dirname, './src/utils/index.ts'),
    },
  },
  build: {
    outDir: "dist",
    sourcemap: true,
    lib: {
      fileName: "core",
      entry: path.resolve(__dirname, "src/index.ts"),
      formats: ["es", "cjs"],
    },
    rollupOptions: {
      external: isExternal,
    },
  },
}));

That worked just fine. While running pnpm exec vite build, my package was built successfully.

The problem was when I tried to import in my website. I got the following message:

Cannot find package '~' imported from /Users/raulmelo/development/raulmelo-studio/packages/core/dist/scripts.js

I straight jumped into the dist files and noted something weird: the aliases were not being resolved:

dist/core.js
import { client as s, sanityConfig as h } from "~/config";
import { SUPPORTED_LANGUAGES as p } from "~/config/languages";
// ...

That was weird because I had explicitly declared the resolve.alias property that was meant to solve this.

After a few hours of debugging, I realized that my function isExternal was responsible for causing the bug.

This function is a tiny utility that we pass to Rollup (used by Vite) to decide if the code should be included in the code or if it should be handled as an external dependency, and you might have guessed, it's executed for every single import your have in your code.

vite.config.ts
// ...

function isExternal(id: string) {
  return !id.startsWith(".") && !path.isAbsolute(id);
}

// ...

The first condition will check if the import does not starts with . (e.g., import foo from './bar').

The second condition will check if it isn't an absolute path (this is more for the internals.

But then, it was missing a condition to check my aliases.

When this function hits something like ~/config, none of these conditions were satisfied (true && true), leading Rollup to consider this import as external.

My fix was adding another condition:

vite.config.ts
// ...

function isExternal(id: string) {
  return !id.startsWith(".") && !path.isAbsolute(id) && !id.startsWith('~/');
}

// ...

Now, it'll define if it's an external dependency, if it does not start with ., is not an absolute path, and does not start with ~/, resulting in my aliased imports being correctly imported to the final bundle.

Conclusion

It's tough to solve problems like this, mainly because you don't know what's influencing what is under the hood.

My advice is always to create another repository and reproduce the same behavior but in a smaller size. Always give baby steps until you either find a bug or realize that you were doing something wrong.

References