Search code examples
javascriptwebpackwebpack-plugin

Webpack plugins - when to call doResolve and when callback?


Every webpack resolve plugin follows the following structure:

resolver.plugin(this.source, function(request, callback) {
    if (something) {
         resolver.doResolve(target, obj, "aliased with mapping '" + name + "': '" + ...)
    } else {
         callback(...);
    }

Can anyone please explain when I should call doResolve and when callback. I've found this phrase in the webpack docs:

To pass the request to other resolving plugins, use the this.doResolve(types: String|String[], request: Request, callback) method

However, I don't know what to make of it. It seems that doResolve starts the process from the start. Here is how the stack looks like in doResolve:

enter image description here

You can see that the stages started from the beginning. Why?


Solution

  • Webpack runs all plugins for the resolver using applyPluginsAsyncSeriesBailResult1 method. This method runs all plugins in the succession - the next plugin is run only after the current plugin has finished executing. The bail here means that the sequence is interrupted as soon as one plugin returns an error. This approach is also known as "fail-fast". But BailResult means that the sequence is also interrupted as soon as one plugin returns a result. You can see it from the sources:

    applyPluginsAsyncSeriesBailResult1 = function applyPluginsAsyncSeriesBailResult1(name, param, callback) {
        var plugins = this._plugins[name];
        if(!plugins || plugins.length === 0) return callback();
        var i = 0;
        var _this = this;
        var innerCallback = function next(err, result) {
            // if the plugin returned an error or a result - break
            if(arguments.length > 0) return callback(err, result);
            i++;
            // if all plugins have run - break
            if(i >= plugins.length) {
                return callback();
            }
            // trigger next plugin - continue
            plugins[i].call(_this, param, innerCallback);
        });
        plugins[0].call(this, param, innerCallback);
    };
    

    So from this code you can see that as soon as you call callback inside the plugin with parameters you interrupt the sequence. The first parameter is the error, the second parameter is the result. This is in line with how Node.js expects the callbacks signature. If you call the callback without parameters, the sequence is continued.

    Now, you can also call doResolve which will run the entire sequences of plugins again. This is usually done when you apply some changes to the request and so you want to give a chance to all other plugins again to react to the new request. As your current plugin will be called again on the next doResolve round make sure to construct it so that it prevents recursion. Webpack guards again recursion but only if path, request, query directory and module match as can be seen from the sources:

    Resolver.prototype.doResolve = function doResolve(type, request, message, callback) {
        var resolver = this;
        var stackLine = type + ": (" + request.path + ") " +
            (request.request || "") + (request.query || "") +
            (request.directory ? " directory" : "") +
            (request.module ? " module" : "");
        var newStack = [stackLine];
        if(callback.stack) {
            newStack = callback.stack.concat(newStack);
            if(callback.stack.indexOf(stackLine) >= 0) {
                // Prevent recursion
                var recursionError = new Error("Recursion in resolving\nStack:\n  " + newStack.join("\n  "));
    

    And inside the callback to the doResolve you usually call callback() to break from the current round of plugins since they have had their chance to react to the updated request:

    resolver.plugin(this.source, function(request, callback) {
        if (something) {
             resolver.doResolve(target, obj, 
                   "aliased with mapping '" + name + "': '" + ..., 
                   function() { callback(error, result) }
             )