ts-loader breaking when it's used with TS Path mappings based module resolution

Created on 27 May 2016  路  29Comments  路  Source: TypeStrong/ts-loader

I'm using Path Mapping based Module resolution feature of TypeScript 2.0 and ts-loader failing with 'can not resolve module' error.

To repro the issue, please follow the following steps:

  • clone my sample
  • npm install
  • in tsconfig.json, change the baseUrl to the local location of the clone.
  • webpack

You will get the following error:

ERROR in ./src/utility/utility.ts
Module not found: Error: Cannot resolve module 'stringExtensions' in d:\tsLoaderFailingWithModuleNodeResolutionsample\src\utility

It's working fine when I'm running the following command:
tsc -p tsconfig.json

Most helpful comment

Is it possible for ts-loader to get my information from paths in tsconfig.json and set them up as resolve.alias entries?

At the moment I have to duplicate my config between tsconfig.json and webpack.config.js, like so:

    "paths": {
      "~data/*": [ "Scripts/data/*" ] 
    } 
    resolve: {
        alias: {
            "~data": path.resolve(__dirname, "./Scripts/data/")
        }
    },

All 29 comments

@bruk1977, is the expected behavior to load these from resolve, would resolve get set by the path mapping options in tsconfig.json, or would they be considered independent?

Is there a convention for this sort of thing @jbrantly?

@DanielRosenwasser, it would be so nice if resolve get populated from tsconfig.json automatically. That way no need to update 2 configs (tsconfig.json & webpack.config.js) at build time.

@jbrantly, @DanielRosenwasser any recommendation? Thanks!

Edit: Don't do this, it won't work. See below.

Until this is fixed, I think you can create a series of ambient module declarations in a file called pathMapper.d.ts that re-exports from the paths you actually need to import from.

declare module "foo" {
    import m = require("../my_dependencies/foo");
    export = m;
}

Thanks for the suggestion @DanielRosenwasser I couldn't make it working. Can you please update my sample and show me how ambient module help? Thanks a lot again!

Scratch that last suggestion. I've found a better fix.

Fix up your resolve property to look something like this:

  resolve: {
    // Add `.ts` and `.tsx` as a resolvable extension.
    extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'],

    // Configure path resolution strategy
    modulesDirectories: [
      ".",
      "src/extension/",
      "src/utility/"
    ]
  },

That should keep it in sync with the path mappings in your tsconfig.json which looked like:

    "paths": {
      "*": [
        "*",
        "src/extension/*",
        "src/utility/*"
      ]
    }

Let me know how that works.

Thanks a lot @DanielRosenwasser -- it works charmingly with your first config.

resolve: { // Add.tsand.tsxas a resolvable extension. extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'], modulesDirectories: [ ".", "src/extension/", "src/utility/" ] }

It doesn't work if I use the same mapping I've in tsconfig.

resolve: { // Add.tsand.tsxas a resolvable extension. extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'], modulesDirectories: [ ".", "src/extension/*", "src/utility/*" ] },

On the other hand, Typescript compiler working with the second way of mapping... it required '_' at the end. Is there any way 'moduleDirectories' working with '_' at the end? The reason I can't break TS compiler is I've a another build pipeline that required to build individual files using TSC.

+1, would be nice if ts-loader would be able to automatically parse the paths/baseUrl properties from tsconfig.js. Took me a while to figure out that the problem was that I wasn't including the paths in resolve, and not with my tsconfig.js configuration.

Extending the modulesDirectory setting should work for simple mappings, but what about more complex mappings where * is used in the middle? Something that TS allows, but I haven't yet been able to re-create in webpack.

"paths": {
      "*": [
        "*",
        "components/*/src"
      ]
}

If this is not possible, I would have to add a package.json with a typings field of "src/index.ts" to every component folder, but would rather prefer to have this working without dummy files.

@tp @DanielRosenwasser @johnnyreilly @jbrantly

I have whipped up an isolated webpack Resolver plugin (thanks to @s-panferov) that may apply the module resolution strategy over to webpack correctly with support for fallbacks etc.

@bruk1977 @adambuczynski would you please try this out? If you don't use ts-node you can es5 it and implement. https://github.com/angular/angular-cli/blob/webpack/addon/ng2/utilities/ts-path-mappings-webpack-plugin.ts

Usage is found here:
https://github.com/s-panferov/awesome-typescript-loader/issues/156#issuecomment-228567250

@TheLarkInn I don't think having to add some paths to the resolve array weighs up against having to add an additional plugin/dependency.

For me, the following now works and is quite simple to implement/manage once you know how to:

tsconfig.json example (simplified):

{
  "compilerOptions": {
    "target": "es5",
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "*": [
        "app/components/*",
        "app/*"
      ]
    }
  }
}

And corresponding webpack config:

  //Resolve rules
  resolve: {
    extensions: [
      '', '.js', '.ts', '.scss'
    ],
    root: [
      path.join(ROOT, 'app'),
      path.join(ROOT, 'app/components'),
      path.join(ROOT, 'node_modules')
    ]
  },

But as I said, it would be great if webpack could detect this on it's own rather than having to do it by hand or with an extra plugin.

How would handle something like this:

paths": {
      "@angular2-material/core/*": [
        "./core/*",
        "../core/*"
      ],
      "@angular2-material/*": [
        "./components/*",
        "../components/*"
      ]
    }

@TheLarkInn I don't know, I haven't had a use case for that. For now, it's fulfilling all my requirements and needs :)

@TheLarkInn Love trying but when I'm trying to install [email protected] or webpack@next I'm getting the below error.. .any idea?

silly fetchPackageMetaData Error: No compatible version found: [email protected]
26 silly fetchPackageMetaData Valid install targets:
26 silly fetchPackageMetaData 2.1.0-beta.15, 2.1.0-beta.14, 2.1.0-beta.13, 2.1.0-beta.12, 2.1.0-beta.11, 2.1.0-beta.10, 2.1.0-beta.9, 2.1.0-beta.8, 2.1.0-beta.7, 2.1.0-beta.6, 2.1.0-beta.5, 2.1.0-beta.4, 2.1.0-beta.3, 2.1.0-beta.2, 2.1.0-beta.1, 2.1.0-beta.0, 2.0.7-beta, 2.0.6-beta, 2.0.5-beta, 2.0.4-beta, 2.0.2-beta, 2.0.1-beta, 2.0.0-beta, 1.13.1, 1.13.0, 1.12.15, 1.12.14, 1.12.13, 1.12.12, 1.12.11, 1.12.10, 1.12.9, 1.12.8, 1.12.7, 1.12.6, 1.12.5, 1.12.4, 1.12.3, 1.12.2, 1.12.1, 1.12.0, 1.11.0, 1.10.5, 1.10.4, 1.10.3, 1.10.2, 1.10.1, 1.10.0, 1.9.13, 1.9.12, 1.9.11, 1.9.10, 1.9.9, 1.9.8, 1.9.7, 1.9.6, 1.9.5, 1.9.4, 1.9.3, 1.9.2, 1.9.1, 1.9.0, 1.8.11, 1.8.10, 1.8.9, 1.8.8, 1.8.7, 1.8.6, 1.8.5, 1.8.4, 1.8.3, 1.8.2, 1.8.1, 1.8.0, 1.7.3, 1.7.2, 1.7.1, 1.7.0, 1.6.0, 1.5.3, 1.5.2, 1.5.1, 1.5.0, 1.4.15, 1.4.14, 1.4.13, 1.4.12, 1.4.11, 1.4.10, 1.4.9, 1.4.8, 1.4.7, 1.4.6, 1.4.5, 1.4.4, 1.4.3, 1.4.2, 1.4.1-beta1, 1.4.0-beta9, 1.4.0-beta8, 1.4.0-beta7, 1.4.0-beta6, 1.4.0-beta5, 1.4.0-beta4, 1.4.0-beta3, 1.4.0-beta2, 1.4.0-beta10, 1.4.0-beta1, 1.3.7, 1.3.6, 1.3.5, 1.3.4, 1.3.3-beta2, 1.3.3-beta1, 1.3.2-beta9, 1.3.2-beta8, 1.3.2-beta7, 1.3.2-beta6, 1.3.2-beta5, 1.3.2-beta4, 1.3.2-beta3, 1.3.2-beta2, 1.3.2-beta1, 1.3.1-beta9, 1.3.1-beta8, 1.3.1-beta7, 1.3.1-beta6, 1.3.1-beta5, 1.3.1-beta4, 1.3.1-beta3, 1.3.1-beta2, 1.3.1-beta1, 1.3.0-beta9, 1.3.0-beta8, 1.3.0-beta7, 1.3.0-beta6, 1.3.0-beta5, 1.3.0-beta4, 1.3.0-beta3, 1.3.0-beta2, 1.3.0-beta1, 1.2.0-beta6, 1.2.0-beta5, 1.2.0-beta4, 1.2.0-beta2, 1.2.0-beta1, 1.1.11, 1.1.10, 1.1.9, 1.1.8, 1.1.7, 1.1.6, 1.1.5, 1.1.4, 1.1.3, 1.1.2, 1.1.1, 1.1.0, 1.1.0-beta9, 1.1.0-beta8, 1.1.0-beta7, 1.1.0-beta6, 1.1.0-beta5, 1.1.0-beta4, 1.1.0-beta3, 1.1.0-beta2, 1.1.0-beta12, 1.1.0-beta10, 1.1.0-beta1, 1.0.5, 1.0.4, 1.0.3, 1.0.1, 1.0.0, 1.0.0-rc9, 1.0.0-rc8, 1.0.0-rc7, 1.0.0-rc5, 1.0.0-rc4, 1.0.0-rc3, 1.0.0-rc2, 1.0.0-rc12, 1.0.0-rc11, 1.0.0-rc1, 1.0.0-beta9, 1.0.0-beta8, 1.0.0-beta7, 1.0.0-beta6, 1.0.0-beta5, 1.0.0-beta4, 1.0.0-beta3, 1.0.0-beta2, 1.0.0-beta1, 0.11.18, 0.11.17, 0.11.16, 0.11.15, 0.11.14, 0.11.13, 0.11.12, 0.11.11, 0.11.10, 0.11.9, 0.11.8, 0.11.7, 0.11.6, 0.11.5, 0.11.4, 0.11.3, 0.11.2, 0.11.1, 0.11.0, 0.11.0-beta9, 0.11.0-beta8, 0.11.0-beta7, 0.11.0-beta6, 0.11.0-beta5, 0.11.0-beta4, 0.11.0-beta3, 0.11.0-beta29, 0.11.0-beta28, 0.11.0-beta27, 0.11.0-beta26, 0.11.0-beta25, 0.11.0-beta24, 0.11.0-beta23, 0.11.0-beta22, 0.11.0-beta21, 0.11.0-beta20, 0.11.0-beta2, 0.11.0-beta19, 0.11.0-beta18, 0.11.0-beta17, 0.11.0-beta16, 0.11.0-beta15, 0.11.0-beta14, 0.11.0-beta13, 0.11.0-beta12, 0.11.0-beta11, 0.11.0-beta10, 0.11.0-beta1, 0.10.0, 0.10.0-beta9, 0.10.0-beta8, 0.10.0-beta7, 0.10.0-beta6, 0.10.0-beta5, 0.10.0-beta3, 0.10.0-beta25, 0.10.0-beta24, 0.10.0-beta23, 0.10.0-beta22, 0.10.0-beta21, 0.10.0-beta20, 0.10.0-beta2, 0.10.0-beta19, 0.10.0-beta18, 0.10.0-beta17, 0.10.0-beta16, 0.10.0-beta15, 0.10.0-beta14, 0.10.0-beta13, 0.10.0-beta12, 0.10.0-beta11, 0.10.0-beta10, 0.9.3, 0.9.2, 0.9.1, 0.9.0, 0.9.0-beta9, 0.9.0-beta8, 0.9.0-beta7, 0.9.0-beta6, 0.9.0-beta5, 0.9.0-beta4, 0.9.0-beta38, 0.9.0-beta37, 0.9.0-beta36, 0.9.0-beta35, 0.9.0-beta34, 0.9.0-beta33, 0.9.0-beta32, 0.9.0-beta31, 0.9.0-beta30, 0.9.0-beta29, 0.9.0-beta28, 0.9.0-beta27, 0.9.0-beta26, 0.9.0-beta25, 0.9.0-beta24, 0.9.0-beta23, 0.9.0-beta22, 0.9.0-beta21, 0.9.0-beta20, 0.9.0-beta2, 0.9.0-beta19, 0.9.0-beta18, 0.9.0-beta17, 0.9.0-beta16, 0.9.0-beta15, 0.9.0-beta14, 0.9.0-beta13, 0.9.0-beta12, 0.9.0-beta11, 0.9.0-beta10, 0.9.0-beta1, 0.8.3, 0.8.2, 0.8.0, 0.8.0-beta4, 0.8.0-beta3, 0.8.0-beta2, 0.8.0-beta1, 0.7.17, 0.7.16, 0.7.15, 0.7.14, 0.7.13, 0.7.12, 0.7.11, 0.7.9, 0.7.8, 0.7.7, 0.7.6, 0.7.5, 0.7.4, 0.7.3, 0.7.2, 0.7.1, 0.7.0, 0.7.0-beta8, 0.7.0-beta7, 0.7.0-beta6, 0.7.0-beta5, 0.7.0-beta4, 0.7.0-beta3, 0.7.0-beta2, 0.7.0-beta, 0.6.2, 0.6.1, 0.6.0, 0.5.10, 0.5.8, 0.5.7, 0.5.6, 0.5.5, 0.5.4, 0.5.3, 0.5.2, 0.5.1, 0.5.0, 0.4.25, 0.4.24, 0.4.23, 0.4.21, 0.4.20, 0.4.19, 0.4.18, 0.4.17, 0.4.16, 0.4.15, 0.4.14, 0.4.13, 0.4.12, 0.4.11, 0.4.10, 0.4.9, 0.4.8, 0.4.7, 0.4.6, 0.4.5, 0.4.4, 0.4.3, 0.4.2, 0.4.1, 0.4.0, 0.3.20, 0.3.19, 0.3.18, 0.3.17, 0.3.16, 0.3.15, 0.3.14, 0.3.13, 0.3.12, 0.3.11, 0.3.10, 0.3.9, 0.3.8, 0.3.7, 0.3.6, 0.3.4, 0.3.3, 0.3.2, 0.3.1, 0.3.0, 0.2.8, 0.2.7, 0.2.6, 0.2.4, 0.2.3, 0.2.2, 0.2.1, 0.2.0, 0.1.6, 0.1.5, 0.1.4, 0.1.3, 0.1.2, 0.1.1, 0.1.0

[email protected] give that a try

Thanks @TheLarkInn -- 2.1.0-beta.15 install working. However, I'm getting the below error. I updated my sample and you can repro. Is there a bug in the plugin or do I miss anything?

Error:
RangeError: Maximum call stack size exceeded
at Array.filter (native)
at D:tempsamplenode_modules\tapable\lib\Tapable.js:204:18
at D:tempsamplenode_modules\enhanced-resolve\lib\UnsafeCachePlugin.js:37:19
at loggingCallbackWrapper (D:tempsamplenode_modules\enhanced-resolve\lib\createInnerCallback.js:31:19)
at innerCallback (D:tempsamplenode_modules\enhanced-resolve\lib\Resolver.js:119:19)
at loggingCallbackWrapper (D:tempsamplenode_modules\enhanced-resolve\lib\createInnerCallback.js:31:19)
at D:tempsamplenode_modules\tapable\lib\Tapable.js:210:15
at innerCallback (D:tempsamplenode_modules\enhanced-resolve\lib\Resolver.js:119:19)
at loggingCallbackWrapper (D:tempsamplenode_modules\enhanced-resolve\lib\createInnerCallback.js:31:19)
at D:tempsamplenode_modules\tapable\lib\Tapable.js:210:15
at D:tempsamplenode_modules\enhanced-resolve\lib\DescriptionFilePlugin.js:41:20
at loggingCallbackWrapper (D:tempsamplenode_modules\enhanced-resolve\lib\createInnerCallback.js:31:19)
at innerCallback (D:tempsamplenode_modules\enhanced-resolve\lib\Resolver.js:119:19)
at loggingCallbackWrapper (D:tempsamplenode_modules\enhanced-resolve\lib\createInnerCallback.js:31:19)
at D:tempsamplenode_modules\tapable\lib\Tapable.js:210:15
at D:tempsamplenode_modulesawesome-typescript-loader\src\paths-plugin.ts:147:36
at loggingCallbackWrapper (D:tempsamplenode_modules\enhanced-resolve\lib\createInnerCallback.js:31:19)
at Tapable.doResolve (D:tempsamplenode_modules\enhanced-resolve\lib\Resolver.js:93:11)
at Tapable. (D:tempsamplenode_modulesawesome-typescript-loader\src\paths-plugin.ts:140:29)
at Tapable.applyPluginsParallelBailResult1 (D:tempsamplenode_modules\tapable\lib\Tapable.js:215:14)
at beforeInnerCallback (D:tempsamplenode_modules\enhanced-resolve\lib\Resolver.js:110:19)
at loggingCallbackWrapper (D:tempsamplenode_modules\enhanced-resolve\lib\createInnerCallback.js:31:19)
at Tapable.applyPluginsAsyncSeriesBailResult1 (D:tempsamplenode_modules\tapable\lib\Tapable.js:108:46)
at Tapable.doResolve (D:tempsamplenode_modules\enhanced-resolve\lib\Resolver.js:98:11)
at D:tempsamplenode_modules\enhanced-resolve\lib\DescriptionFilePlugin.js:40:13
at D:tempsamplenode_modules\enhanced-resolve\lib\DescriptionFileUtils.js:49:12
at D:tempsamplenode_modules\enhanced-resolve\lib\forEachBail.js:29:14
at onJson (D:tempsamplenode_modules\enhanced-resolve\lib\DescriptionFileUtils.js:40:5)
at D:tempsamplenode_modules\enhanced-resolve\lib\DescriptionFileUtils.js:18:6
at Storage.provide (D:tempsamplenode_modules\enhanced-resolve\libCachedInputFileSystem.js:53:20)

Is it possible for ts-loader to get my information from paths in tsconfig.json and set them up as resolve.alias entries?

At the moment I have to duplicate my config between tsconfig.json and webpack.config.js, like so:

    "paths": {
      "~data/*": [ "Scripts/data/*" ] 
    } 
    resolve: {
        alias: {
            "~data": path.resolve(__dirname, "./Scripts/data/")
        }
    },

The Angular compiler solves this with a plugin.

It's possible to use the plugin in any non-Angular webpack configuration. Just have to install @ngtools/webpack, then add this to the webpack plugins list:

{
  apply(compiler) {
    compiler.plugin('normal-module-factory', nmf => {
      const PathsPlugin = require('@ngtools/webpack').PathsPlugin;

      compiler.resolvers.normal.apply(new PathsPlugin({
        nmf,
        tsConfigPath: path.resolve(__dirname, 'tsconfig.json'),
      }));
    });
  }
}

@merott - thanks for sharing! I don't suppose you'd fancy submitting a docs PR to aid discoverability of this solution? Also, I'm slightly curious about your usage example above.

I'd anticipated usage that looked something like this in your webpack.config.js:

const PathsPlugin = require('@ngtools/webpack').PathsPlugin;

// .... 

new TsConfigPathsPlugin({
   tsconfig: path.resolve(__dirname, 'tsconfig.json'),
})

// .... 

But this is different to what you have; I'm guessing there's a reason your setup is different but I wonder if you could elaborate?

@johnnyreilly - the usage has to do with the constructor API for PathsPlugin in @ngtools/webpack.

The constructor expects an options object, with the tsConfigPath and nmf as minimum. That's why I've had to wrap it with my own inline plugin definition. I guess we could do this too:

const PathsPlugin = require('@ngtools/webpack').PathsPlugin;

function TsConfigPathsPlugin(tsConfigPath) {
  this._tsConfigPath = tsConfigPath;
}

TsConfigPathsPlugin.prototype.apply = function (compiler) {
  compiler.plugin('normal-module-factory', normalModuleFactory => {
    compiler.resolvers.normal.apply(new PathsPlugin({
      nmf: normalModuleFactory,
      tsConfigPath: this._tsConfigPath,
    }));
  });
};

module.exports = {
  // ... webpack configuration
  plugins: [
    new TsConfigPathsPlugin(path.resolve(__dirname, 'tsconfig.json')),
  ]
}

That's the best I can come up with, though I'm not a webpack expert, so tell me if there's a better way or an API that can help.

As this seems a little bit tedious, I'm not sure if it would be good as an official solution to appear in the docs.

Thanks for the clarification. Might be good to lift the existing plugin and create a standalone one. I think that's what awesome-typescript-loader has done. I wonder if we could just use that as is? That'd make things easier.

@Merott,

I don't suppose you could try something for me could you? Would you be able to add awesome-typescript-loader as a dev dependency of your project and follow the instructions here for using the Path's plugin. I'm guessing your code would look something like this:

const { TsConfigPathsPlugin } = require('awesome-typescript-loader');

resolve: {
    plugins: [
        new TsConfigPathsPlugin( ... )
    ]
}

If that works I wonder if they'd be up for publishing this as a standalone package that could be used by ts-loader and awesome-typescript-loader alike?

@johnnyreilly I can confirm that the solution from the post above works great for me:

I have the following code:

webpack.config.js:

const {TsConfigPathsPlugin} = require('awesome-typescript-loader');

resolve: {
  plugins: [
    new TsConfigPathsPlugin()
  ]
}

tsconfig.json:

"baseUrl": ".",
"paths": {
  "shared-services/*": ["./app/shared/services/*"]
}

That's awesome @zuzusik! Would you be able to share a minimal that demonstrates this? I'd like to document this usage and also add a test to our testpack that uses the paths plugin.

With a minimal repo I could probably do that. I don't use this functionality myself and so I don't have any example code of my own I could use.

You could also use the tsconfig-paths-webpack-plugin package together with ts-loader in order to resolve baseUrl and paths from tsconfig.json. This is now mentioned here in the readme so I guess this can be closed?

Yeah - works for me. I still should really get round to creating an execution test for our test pack that covers using ts-loader with another plugin providing the path functionality. Haven't yet though. If anyone feels motivated to provide that I'd appreciate it.

I'll close this though.

tsconfig-paths-webpack-plugin is not working with Webpack 4 yet...
A simple temporary solution based on @Jameskmonger post above:

const tsConfig = require("./tsconfig.json");
const alias = {};
for (let key of Object.keys(tsConfig.compilerOptions.paths)) {
    alias[key.replace(/\/\*$/, "")] = path.resolve(__dirname, tsConfig.compilerOptions.paths[key][0].replace(/[\/]\*$/, ""));
}

....

resolve: {
    ....
    alias: alias
}

@cime Could you please report the problems you are experiencing with webpack 4 and tsconfig-paths-webpack-plugin in this issue.

Maybe this tool I made for resolving paths might be handy (to someone)?
https://www.npmjs.com/package/tspath

Was this page helpful?
0 / 5 - 0 ratings