29 - Analysis of Resolver.prototype.resolve of webpack source code

Keywords: Javascript Vue Webpack JSON

In the previous section, we finally returned a resolver, essentially a Resolver object:

resolver = new Resolver(fileSystem);

The constructor of this object is very simple. It simply inherits Tapable and receives the fileSystem parameter:

function Resolver(fileSystem) {
    Tapable.call(this);
    this.fileSystem = fileSystem;
}
module.exports = Resolver;

 

resolve

In the make event flow, the prototype method resolve of this class is called, and now you can take a look at it:

/* 
    context => { issuer: '', compiler: undefined }
    path => 'd:\\workspace\\doc'
    request => './input.js'
    callback => [Function]
*/
Resolver.prototype.resolve = function resolve(context, path, request, callback) {
    if (arguments.length === 3) {
        throw new Error("Signature changed: context parameter added");
    }
    var resolver = this;
    // Packaging parameters
    var obj = {
        context: context,
        path: path,
        request: request
    };

    var localMissing;
    var log;
    // message => resolve './input.js' in 'd:\\workspace\\doc'
    var message = "resolve '" + request + "' in '" + path + "'";

    function writeLog(msg) {
        log.push(msg);
    }

    function logAsString() {
        return log.join("\n");
    }

    function onError(err, result) { /**/ }

    function onResolve(err, result) { /**/ }
    // These two do not exist.
    onResolve.missing = callback.missing;
    onResolve.stack = callback.stack;
    // Call another prototype method
    return this.doResolve("resolve", obj, message, onResolve);
};

It should be noted that this method will be called many times during webpack compilation, and the parameters here are only the first time it is called.

 

doResolve

Simply put, after the resolve method wraps the parameters twice, another prototype method, doResolve, is called. The source code is as follows:

/*
    type => 'resolve'
    request => 
        { 
            context: { issuer: '', compiler: undefined }, 
            path: 'd:\\workspace\\doc', 
            request: './input.js' 
        }
    message => resolve './input.js' in 'd:\\workspace\\doc'
    callback => doResolve()
*/
Resolver.prototype.doResolve = function doResolve(type, request, message, callback) {
    var resolver = this;
    // stackLine => resolve: (d:\workspace\doc) ./input.js
    var stackLine = type + ": (" + request.path + ") " +
        (request.request || "") + (request.query || "") +
        (request.directory ? " directory" : "") +
        (request.module ? " module" : "");
    var newStack = [stackLine];
    // No time
    if (callback.stack) { /**/ }
    // No stream of events.
    resolver.applyPlugins("resolve-step", type, request);
    // before-resolve
    var beforePluginName = "before-" + type;
    // Detecting whether there is a corresponding before event flow
    if (resolver.hasPlugins(beforePluginName)) { /**/ }
    // Follow the normal process
    else {
        runNormal();
    }
}

Because the missing and stack attributes of callback are undefined, the if judgment is skipped directly.

Event streams resolve-step and before-resolution do not exist, so they go directly to the last else and enter the runNormal method.

doResolve is described in detail here. There are five functions inside the method, named beforeInnerCallback, runNormal, innerCallback, runAfter and afterInnerCallback, all of which are responsible for wrapping callback functions for the corresponding event flow.

The source code is as follows:

// Judge first whether it exists before-type Event stream
if (resolver.hasPlugins(beforePluginName)) {
    // Triggered call-back
    resolver.applyPluginsAsyncSeriesBailResult1(beforePluginName, request, createInnerCallback(beforeInnerCallback, {
        log: callback.log,
        missing: callback.missing,
        stack: newStack
    }, message && ("before " + message), true));
}
// There is no skip direct trigger type Event stream 
else {
    runNormal();
}

function beforeInnerCallback(err, result) {
    if (arguments.length > 0) {
        if (err) return callback(err);
        if (result) return callback(null, result);
        return callback();
    }
    // Here we go to the next stage.
    runNormal();
}

// trigger type Event stream
function runNormal() {
    if (resolver.hasPlugins(type)) { /**/ } else {
        runAfter();
    }
}

function innerCallback(err, result) { /**/ }
// trigger after-type
function runAfter() {
    var afterPluginName = "after-" + type;
    // Here is the direct call callback 了
    if (resolver.hasPlugins(afterPluginName)) { /**/ } else {
        callback();
    }
}

function afterInnerCallback(err, result) { /**/ }

As you can see, the logic is simple. Each event flow type has three types: before-type, after-type, and doResolve tries to trigger the event flow at each stage in turn.

In the above example, because there is no before-resolve event flow, the runNormal method is called to trigger the resolve event flow.

If it exists, trigger the corresponding event flow and trigger the next phase of the event flow in the callback function.

So the call here can be summarized in one sentence: after trying to trigger the before-resolve, resolve, after-resolve event stream, call back.

 

unsafeCache

The resolve event flow originates from the beginning of the injection in the third part of the previous section, as follows:

// resolve
if (unsafeCache) {
    plugins.push(new UnsafeCachePlugin("resolve", cachePredicate, unsafeCache, cacheWithContext, "new-resolve"));
    plugins.push(new ParsePlugin("new-resolve", "parsed-resolve"));
} else {
    plugins.push(new ParsePlugin("resolve", "parsed-resolve"));
}

 

UnsafeCachePlugin

Although this unsafeCache does not know what it is, it will not be set up by default, which is true, so enter the Unsafe Cache Plugin plug-in. The constructor is as follows:

/*
    source => resolve
    filterPredicate => function(){return true}
    cache => {}
    withContext => false
    target => new-resolve
 */
function UnsafeCachePlugin(source, filterPredicate, cache, withContext, target) {
    this.source = source;
    this.filterPredicate = filterPredicate;
    this.withContext = withContext;
    this.cache = cache || {};
    this.target = target;
}

Basically, it is just the acquisition of the incoming parameters, looking directly at the content of the event flow:

function getCacheId(request, withContext) {
    // Cache objects directly in the form of strings of configuration objects key
    // seemingly vue Source code compile It's the same.
    return JSON.stringify({
        context: withContext ? request.context : "",
        path: request.path,
        query: request.query,
        request: request.request
    });
}
UnsafeCachePlugin.prototype.apply = function(resolver) {
    var filterPredicate = this.filterPredicate;
    var cache = this.cache;
    var target = this.target;
    var withContext = this.withContext;
    // Inject here resolve Event stream
    /* 
        request => 
        { 
            context: { issuer: '', compiler: undefined }, 
            path: 'd:\\workspace\\doc', 
            request: './input.js' 
        }
        callback => createInnerCallback(innerCallback,{...})
    */
    resolver.plugin(this.source, function(request, callback) {
        // It's always here. true
        if (!filterPredicate(request)) return callback();
        // Attempt to get the cache
        var cacheId = getCacheId(request, withContext);
        var cacheEntry = cache[cacheId];
        if (cacheEntry) {
            return callback(null, cacheEntry);
        }
        // This is called again. doResolve function
        // target => new-resolve
        resolver.doResolve(target, request, null, createInnerCallback(function(err, result) {
            if (err) return callback(err);
            if (result) return callback(null, cache[cacheId] = result);
            callback();
        }, callback));
    });
};

Obviously, the resolve event is just to get the cache. If there is no cache, call the doResolve method again. This time, the type passed in is new-resolve.

 

ParsePlugin

There is no before-xxx or after-xxx case in the new-resolve event flow, so look directly at the event flow itself. The injection site is behind the Unsafe Cache Plugin plug-in.

As can be seen from the above if/else, the plug-in will be invoked anyway, but the cache will be fetched based on the unsafeCache value.

The content of this plug-in is simple and violent.

// source => new-resolve
// target => parsed-resolve
function ParsePlugin(source, target) {
    this.source = source;
    this.target = target;
}
module.exports = ParsePlugin;

ParsePlugin.prototype.apply = function(resolver) {
    var target = this.target;
    resolver.plugin(this.source, function(request, callback) {
        // analysis
        var parsed = resolver.parse(request.request);
        // Merge object
        var obj = Object.assign({}, request, parsed);
        if (request.query && !parsed.query) {
            obj.query = request.query;
        }
        if (parsed && callback.log) {
            if (parsed.module)
                callback.log("Parsed request is a module");
            if (parsed.directory)
                callback.log("Parsed request is a directory");
        }
        // trigger target Of doResolve
        resolver.doResolve(target, obj, null, callback);
    });
};

It's basically a routine, triggering the flow of events, doing something, and finally calling doResolve to trigger the next round.

The core here is the parse method, which is estimated to be similar to the parse of vue source code. It's more troublesome. I'll talk about it in the next section.

Posted by hubbardude on Tue, 08 Jan 2019 16:33:09 -0800