starcorn
starcorn

Reputation: 8551

Webpack resolve.alias does not work with typescript?

I try to shorten my imports in typescript

from import {Hello} from "./components/Hello";

to import {Hello} from "Hello";

For that I found out you can use resolve.alias in webpack thus I configured that part as following

resolve: {
    root: path.resolve(__dirname),
    alias: {
        Hello: "src/components/Hello"
    },
    extensions: ["", ".ts", ".tsx", ".js"]
},

Webpack builds, and the output bundle.js works. However typescript's intellisense complain it cannot find the module

So my question is whether or not webpack's resolve.alias works with typescript?

I found following issue but there's no answer to it.

Upvotes: 86

Views: 91034

Answers (10)

Cornchips007
Cornchips007

Reputation: 165

In react 18/nextjs 14, typescript, storybook 7, this is what worked for me to mock a hook that is a named export (not a default export):

// .storybook/main.ts in the config setup

webpackFinal: async (config: Configuration, { configType }) => {
  const myMock = path.resolve(
    __dirname,
    '../apps/src/myFunctionOrHookName'
  );

  config.resolve.alias = {
    ...config.resolve?.alias,
    [myMock]: require.resolve(
      '../__mocks__/myFunctionOrHookName'
    ),
  };
} 

Note:

  • the key myMock is actually the resolved path of where the real import sits currently in relation to the storybook main.ts sits.
  • the key myMock is wrapped in square brackets [] as we need to have it calculated before webpack takes it as a string type key which is what it's expecting.
  • the mock file has the same name as the file we're trying to mock.
  • the mock file should end in .js but we don't need to include it in the alias path.

You might also find that because config's resolve is optional, your linter goes crazy. I wrapped mine in an if so it looked like below, so that I could try and force it to not silently fail if it was missing for whatever reason. So far it's never reached that else (fingers crossed).

  const myMock = path.resolve(
    __dirname,
    '../apps/src/myFunctionOrHookFileName'
  );

 if (config.resolve) {
    config.resolve.alias = {
      ...config.resolve.alias,
      [myMock]: require.resolve('../__mocks__/myFunctionOrHookFileName'),
    }
  } else {
    throw new Error('Resolve should exist on Webpack build step');
  }

Upvotes: 2

Marvin
Marvin

Reputation: 754

It's not good practice to use alias for just 1 particular component and you should tell to webpack config that you have existing aliases setup in my ts-config by using tsconfig-paths-webpack-plugin, here is the sample config

// webpack.config.js
module.exports = {
  ...
  resolve: {
    plugins: [new TsconfigPathsPlugin({/* options: see below */})]
  }
  ...
}
//ts-config.json
{
  "compilerOptions": {
    "baseUrl": "./src",
    "components/*": "components/*"
    ...
  },
  ...
}

and second solution, you might as well manual resolve the issue by specifying the aliases what you want in webpack.config, but if you don't want, you may follow the above config

// webpack.config.js
module.export = {
  resolve: {
    alias: {
      'components': path.resolve(__dirname, 'src/components')
    }
  }
}

remember the role of tsconfig or jsconfig is to provide an intellisense to your editor and give direction to your bundler

//ts-config.json
{
  "compilerOptions": {
    "baseUrl": "./src",
    "components/*": "components/*"
    ...
  },
  ...
}

either what solution you chose, you should still use it as this

import {Home} from 'components';
...

and if you are out of your curiosity how webpack.config rely on your tsconfig.json, you may read ts-loader

Upvotes: 1

ansavchenco
ansavchenco

Reputation: 685

You can also configure tsconfig-paths-webpack-plugin in order not to duplicate your aliases in several places. It will pick up aliases from tsconfig.json file and automatically add them to webpack.

Upvotes: 14

Caio Saldanha
Caio Saldanha

Reputation: 1090

You are missing one very important point in tsconfig.json: The matching pattern!

It should be configured like this:

"baseUrl": ".",
"paths": {
    "appSrc/*": [
        "src/*"
    ]
 }

The "*" is the important part to tell TS to match anything on the right side.

I found that out from this article: Type-safe es2015 module import path aliasing with Webpack, Typescript and Jest

NOTE

  • Make sure all your webpack.config.js are updated (e.g. if you use storybook).
  • If you use Visual Studio Code you may need to restart it, in order for the squiggly lines to disappear.

Upvotes: 96

Yash Agrawal
Yash Agrawal

Reputation: 464

There are 2 cases

  1. When you write custom webpack It works with typescript but not directly. To explain, there are 2 types of compilation happening in backstage. First tsx -> js for which tsconfig.json plays the role, but when you actually compile the js code then webpack comes into picture. So for aliases, resolve should be placed at both places i.e in tsconfig and webpack to successfully run the application
  2. when you use default(after create react app): You just need to add "baseUrl": "./src" in tsconfig and see the code work.

Upvotes: 1

Michael Kang
Michael Kang

Reputation: 52867

As others have mentioned, you need to provide an alias in your webpack.config.js:

    resolve: { 

        extensions: [".ts", ".js"],
        alias: {
            forms: path.resolve(__dirname, "src/forms/")
        } 
    },

This needs to be in synch with your tsconfig.json file (baseUrl and paths are required).

"compilerOptions":  {
    baseUrl: "./",
    ...
    paths: {
       "forms/*": ["src/forms/*"]
    }
}

Note: The wildcard pattern is necessary to match with your resolve alias configuration.

Then you can import any library using your alias:

import { FormsModule } from "forms/my-forms/my-forms.module";

Upvotes: 46

metal bar
metal bar

Reputation: 600

If anyone still have this issue, don't forget to add your folder to the "include" option on tsconfig.json like this:

{
  "compilerOptions": {
    "sourceMap": true,
    "allowJs": true,
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "lib": [
      "es2016",
      "dom"
    ]
  },
  "outDir": "./built/",
  "include": [
    "./src/**/*",
    "./tests/**/*"
  ]
}

Upvotes: 12

AMilassin
AMilassin

Reputation: 1196

I had to make a small adjustment to Caio Saldanha's solution to make it work in my environment.

I am using Babel 7 with babel-plugin-module-resolver to resolve aliases. No ts-loader or awesome-typescript-loader as Babel 7 supports TypeScript out of the box using @babel/preset-typescript. I had to add an extra path configuration for each alias to load the module root (e.g. index.ts) automagically:

"baseUrl": ".",
"paths": {  // this must be synchronized with .babelrc.js's module-resolver alias config
    "component": ["src/component/index.ts"],
    "component/*": ["src/component/*"],
    ...
}

Having an index.ts in the /component folder with the following content:

export { default as Logo } from './Logo';

Without the extra .../index.ts line this import didn't work for me:

import { Logo } from 'component';  

Alias config in .babelrc.js:

plugins: [
[
    'module-resolver',
    {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
        root: ['./src'],
        alias: {
        // this must be synchronized with tsconfig.json's path configuration
            component: './src/component',
        },
    },
],

Upvotes: 5

Daniel Rosenwasser
Daniel Rosenwasser

Reputation: 23483

If you're using ts-loader, you might have to synchronize your webpack alias/resolve settings with your paths setting in your tsconfig.json.

{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "Hello": ["src/components/Hello"]
        }
    }
}

If you're using awesome-typescript-loader, then webpack can figure this out automatically from the paths setting in your tsconfig.json, as per the status on this issue from the repo. That way, you don't need to duplicate the same information in your Webpack alias field.

Upvotes: 104

Dimitris Karagiannis
Dimitris Karagiannis

Reputation: 9386

I think you can do this and have it work the way you describe:

resolve: {
    root: [
        path.resolve(__dirname),
        <path_to_components_directory> //e.g. path.resolve(__dirname, 'src', 'components')
    ], 
    extensions: ["", ".ts", ".tsx", ".js"]
},

Then you can do import {Hello} from "Hello";

I know I do this to resolve file paths in my src/js directory. I am not using typescript though, but I don't think it would affect the result.

Upvotes: -3

Related Questions