Webpack cannot always resolve the TS loader

ideaboxer picture ideaboxer · Jan 21, 2017 · Viewed 9.3k times · Source

This is my original webpack config file, consisting of two different configurations which both are used:

const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = [{
  entry: __dirname + '/src/browser/main',
  output: {
    path: __dirname + '/dist/browser',
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['', '.js', '.ts'] // '' is needed to find modules like "jquery"
  },
  module: {
    loaders: [
      {
        test: /\.ts$/,
        loader: 'ts'
      }
    ]
  }
}, {
  entry: ['babel-polyfill', __dirname + '/src/app/browser/app'],
  output: {
    path: __dirname + '/dist/app/browser',
    filename: 'bundle.js'
  },
  resolve: {
    root: ['./node_modules', './src'],
    extensions: ['', '.js', '.ts'] // '' is needed to find modules like "jquery"
  },
  module: {
    loaders: [
      {
        test: /\.ts$/,
        loader: 'ts'
      },
      {
        test: /\.scss$/,
        loader: ExtractTextPlugin.extract('style', 'css!sass')
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('bundle.css')
  ]
}]

Even though all my imports are accepted by, for instance, Atom (text editor with TypeScript plugin), currently only the following alternative works:

const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = [{
  entry: __dirname + '/src/browser/main',
  output: {
    path: __dirname + '/dist/browser',
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['', '.js', '.ts'] // '' is needed to find modules like "jquery"
  },
  module: {
    loaders: [
      {
        test: /\.ts$/,
        loader: 'ts'
      }
    ]
  }
}, {
  entry: ['babel-polyfill', __dirname + '/src/app/browser/app'],
  output: {
    path: __dirname + '/dist/app/browser',
    filename: 'bundle.js'
  },
  resolve: {
    root: ['./node_modules', './src'],
    extensions: ['', '.js', '.ts'] // '' is needed to find modules like "jquery"
  },
  module: {
    loaders: [
      {
        test: /\.ts$/,
        loader: require.resolve('ts-loader')
      },
      {
        test: /\.scss$/,
        loader: ExtractTextPlugin.extract('style', 'css!sass')
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('bundle.css')
  ]
}]

(note the require.resolve('ts-loader'))

As you can see, in the first config 'ts' is still present (since it still works), while in the second one I needed to replace it by require.resolve('ts-loader'). Now I try to find the reason for this requirement. I can remember that I was forced to use require.resolve in other projects as well, but I never was aware of the reason.

The inconspicuous line of code which makes the difference (second config):

import * as dateTime from 'lib/date-time/date-time'

If I comment it, it works again using 'ts'.

On my file system, the path lib/date-time/date-time.ts is located directly below ./src (which is mentioned in the resolve/root array). And it is not the first import depending on the resolve/root array.

Webpack output with error message:

> webpack

ts-loader: Using [email protected] and /.../src/app/browser/tsconfig.json
ts-loader: Using [email protected] and /.../src/browser/tsconfig.json
Hash: 5058e7029f1c0243a269161aa4ddda242a3d33a0
Version: webpack 1.14.0
Child
    Hash: 5058e7029f1c0243a269
    Version: webpack 1.14.0
    Time: 3165ms
        Asset    Size  Chunks             Chunk Names
    bundle.js  283 kB       0  [emitted]  main
        + 4 hidden modules
Child
    Hash: 161aa4ddda242a3d33a0
    Version: webpack 1.14.0
    Time: 3670ms
         Asset    Size  Chunks             Chunk Names
     bundle.js  617 kB       0  [emitted]  main
    bundle.css   25 kB       0  [emitted]  main
       [0] multi main 40 bytes {0} [built]
        + 329 hidden modules

    ERROR in Cannot find module './node_modules/ts-loader/index.js'
     @ ./src/records/departure/departure.ts 2:15-49
    Child extract-text-webpack-plugin:
            + 2 hidden modules
    Child extract-text-webpack-plugin:
            + 2 hidden modules
    Child extract-text-webpack-plugin:
            + 2 hidden modules
    Child extract-text-webpack-plugin:
            + 2 hidden modules

Answer

Nelson Yeung picture Nelson Yeung · Jan 24, 2017

Use

root: path.resolve('./src'),

Because the documentation on resolve.root states:

The directory (absolute path) that contains your modules. May also be an array of directories. This setting should be used to add individual directories to the search path.

It must be an absolute path! Don’t pass something like ./app/modules.

In addition, since resolve.modulesDirectories already has node_modules as the defaults, we can omit it from resolve.root. In other words, we don't need root: [path.resolve('./node_modules'), path.resolve('./src'),]

I'm not a Webpack developer but my hunch is that at every import, Webpack will change the current directory to that file so that it can resolve relative imports. During the import of the date-time module from ./src/records/departure/departure.ts, it will now use the directory ./src/records/departure and at that time it'll also need to use a loader, but because you've set the root using a relative path (./node_modules), it will look in ./src/records/departure/node_modules instead. Hence giving you this error.

The above is not exactly correct but still kind of apply but here's the link to the docs: module.loaders

IMPORTANT: The loaders here are resolved relative to the resource which they are applied to. This means they are not resolved relative to the configuration file. If you have loaders installed from npm and your node_modules folder is not in a parent folder of all source files, webpack cannot find the loader. You need to add the node_modules folder as an absolute path to the resolveLoader.root option. (resolveLoader: { root: path.join(__dirname, "node_modules") })