dividab / tsconfig-paths-webpack-plugin

Load modules according to tsconfig paths in webpack.
MIT License
597 stars 51 forks source link

Callback was already called #11

Closed eulbot closed 6 years ago

eulbot commented 6 years ago

After updating to 3.0.0, I get an Callback was already called error. I reverted back to 1.4.0 and it runs again without issues.

Here is the callstack:

Error: Callback was already called.
    at throwError ([PATH]\node_modules\neo-async\async.js:14:11)
    at [PATH]\node_modules\neo-async\async.js:6725:13
    at normalResolver.resolve ([PATH]\node_modules\webpack\lib\NormalModuleFactory.js:188:25)
    at doResolve ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:181:12)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn0 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:15:1)
    at resolver.doResolve ([PATH]\node_modules\enhanced-resolve\lib\UnsafeCachePlugin.js:37:5)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn0 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:15:1)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn1 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:24:1)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn43 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:399:1)
    at resolver.doResolve ([PATH]\node_modules\enhanced-resolve\lib\ModuleKindPlugin.js:23:37)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn0 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:15:1)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn0 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:15:1)
    at args ([PATH]\node_modules\enhanced-resolve\lib\forEachBail.js:30:14)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn0 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:15:1)
    at resolver.doResolve ([PATH]\node_modules\enhanced-resolve\lib\UnsafeCachePlugin.js:37:5)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn0 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:15:1)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn1 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:24:1)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn44 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:411:1)
    at hook.callAsync ([PATH]\node_modules\enhanced-resolve\lib\Resolver.js:232:5)
    at _fn1 (eval at create ([PATH]\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:24:1)
jonaskello commented 6 years ago

The example config in this repo works with webpack 4 without issues. In order to help you we would need to know what is different in your config. Could you post a webpack.config.js to reproduce the error you are getting?

jonaskello commented 6 years ago

Also are you using webpack 3 or webpack 4? Version 3.0.0 of this plugin should work with both.

eulbot commented 6 years ago

That's my config (I'm using webpack 4)

const webpack = require('webpack');
const path = require('path');
const tsconfig = path.join(__dirname, '../moduleA/tsconfig.json');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

var config = {
    entry: [
        '../moduleA/app/app.ts'
    ],
    output: {
        filename: 'moduleA.app.js',
        path: path.join(__dirname, '../moduleA/scripts')
    },
    mode: 'development',
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
        plugins: [
            new TsconfigPathsPlugin({
                configFile: tsconfig,
                logLevel: 'info',
                extensions: ['.tsx', '.ts', '.js']
            })
        ]
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                loader: 'ts-loader',
                options: {
                    logInfoToStdOut: true,
                    logLevel: 'info',
                    configFile: tsconfig,
                    compilerOptions: {
                        noEmit: false,
                        sourceMap: true
                    }
                }
            }
        ]
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            ko: "knockout",
            "ko.validation": "knockout.validation"
        })
    ],
    devtool: "source-map"
};

module.exports = config;

And these are my global modules:

+-- gulp@3.9.1
+-- gulp-concat@2.6.1
+-- gulp-json-modify@1.0.2
+-- gulp-plumber@1.2.0
+-- gulp-util@3.0.8
+-- lodash@4.17.5
+-- ts-loader@4.0.1
+-- tsconfig-paths-webpack-plugin@1.4.0
+-- uglify-js@2.8.29
+-- webpack@4.1.1
`-- webpack-cli@2.0.10

And here is the tsconfig that's used for the build:

{
  "compileOnSave": true,
  "compilerOptions": {
    "baseUrl": ".",
    "experimentalDecorators": true,
    "lib": [ "es2016", "dom", "es2015.promise" ],
    "moduleResolution": "node",
    "noEmit": true,
    "noImplicitAny": false,
    "paths": {
      "moduleA/*": [ "../moduleA/*" ],
      "moduleC/*": [ "../moduleC/*" ],
      "moduleD/*": [ "../moduleD/*" ],
      "moduleE/*": [ "../moduleE/*" ],
      "moduleF/*": [ "../moduleF/*" ],
      "*": [ "../ModuleB/node_modules/*" ]
    },
    "sourceMap": true,
    "target": "es6",
    "typeRoots": [
      "../ModuleB/node_modules/@types"
    ]
  },
  "exclude": [
    "node_modules",
    "dist",
    "Release",
    "obj"
  ],
  "include": [
    "**/*.ts",
    "../ModuleB/node_modules/@types",
    "../ModuleB/node_modules/"
  ]
}
Nayni commented 6 years ago

Maybe it's because this https://github.com/dividab/tsconfig-paths-webpack-plugin/blob/master/src/plugin.ts#L281 isn't 100% correct anymore with Webpack 4.

This seems like the most likely reason why the callback was already called.

Might have to-do something like this:

        return (resolver.doResolve as doResolve)(
          hook,
          newRequest,
          `Resolved request '${innerRequest}' to '${foundMatch}' using tsconfig.json paths mapping`,
          createInnerContext({ ...resolveContext }),
          (err2: Error, result2: string): void => {
            if (arguments.length > 0) {
              return callback(err2, result2);
            } else {
              return callback();
            }
          }
        );

That's how enhanced-resolve seems to be doing it too. Probably worth seeing if that fixed the issue. Maybe a good idea for @eulbot to try this out on your setup and see if that solves the issue?

jonaskello commented 6 years ago

@Nayni I think you may be on to something. The code for this plugin was mostly setup from the path plugin in awsome typescript loader, and I think the code in that plugin mostly came from the alias plugin. Both of which was using the old plugin system.

I looked at the current code for the alias plugin and it seems the same line still exists (at least it is the same comment :-)). However I'm not sure if that code is updated to the new plugin system.

Nayni commented 6 years ago

In the code you linked, you can also see that it only invokes 1 callback. While the code in this plugin actually has a chance to fire the callback twice, which I think is not allowed anymore in the new plugin system (while it might've worked in the old system before Webpack 4).

I think it's best to adjust the code to how I posted and only callback with nothing (or null twice) when we don't have any error or result. That way the callback will never fire twice.

I wasn't aware of this either, sadly the new API is not documented well yet.

jonaskello commented 6 years ago

@Nayni Aha, ok I think I understand what you mean now. Would you like to do a PR for the changes you proposed?

Nayni commented 6 years ago

I'm currently at my day job, but I'd be happy to see if I can reproduce the callback firing twice and drop a PR later today.

Nayni commented 6 years ago

Hopefully my PR can solve the issue.

I also see that https://github.com/dividab/tsconfig-paths/blob/master/src/match-path-async.ts#L123 is not returning after doing it's doneCallback which could also be the cause of this issue as the plugin is using the Async version to match path, and by not returning it will recurse further and call the same callback later (which is what I also fixed in my PR but just for the plugin itself).

And knowing that with a previous version it just works, this is actually more likely.

EDIT: Also made a PR for that in tsconfig-paths: https://github.com/dividab/tsconfig-paths/pull/29 Probably best to combine both of them.

jonaskello commented 6 years ago

The fix for this is now released in v3.0.1. @eulbot Please give it a try.

Nayni commented 6 years ago

Probably going to need https://github.com/dividab/tsconfig-paths/pull/30 to be merged in. Async code is a b*tch :)

I acutally found a reproduction path with the example from the repo as well: https://github.com/Nayni/tsconfig-paths-webpack-plugin/blob/example-with-node-modules-fallback/example/ The catch was to have a * fallthrough that should be resolved by node_modules

jonaskello commented 6 years ago

I released 3.0.2 that includes the above fix from tsconfig-paths.

jonaskello commented 6 years ago

I'll re-open this until @eulbot confirms it is working for his case.

eulbot commented 6 years ago
Build completed in 78.928s

Hash: ee6746c13f9feb58029c
Version: webpack 4.1.1
Child
    Hash: ee6746c13f9feb58029c
    Time: 78935ms
    Built at: 2018-3-14 11:47:23

I can confirm that it's working with 3.0.2. Thank you all for your quick help.