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.