.37-浅析webpack源码之事件流make,4

  赶紧完结这个系列咯,webpack4都已经出正式版了。

  之前的代码搜索到js文件的对应loader,并添加到了对象中返回,流程如下:

this.plugin("factory", () => (result, callback) => {
    let resolver = this.applyPluginsWaterfall0("resolver", null);

    // Ignored
    if (!resolver) return callback();

    // 这里的data就是返回的大对象
    /*
        data => 
        {
            context: 'd:\\workspace\\doc',
            request: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js!d:\\workspace\\doc\\input.js',
            dependencies: [SingleEntryDependency],,
            userRequest: 'd:\\workspace\\doc\\input.js',
            rawRequest: './input.js',
            loaders: [ { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' } ],
            resource: 'd:\\workspace\\doc\\input.js',
            还有package.json与parser相关的属性
        }
    */
    resolver(result, (err, data) => {
        if (err) return callback(err);

        // Ignored
        if (!data) return callback();

        // direct module
        if (typeof data.source === "function")
            return callback(null, data);

        this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => {
            // ...
        })
    })
})

  这个对象的request将入口文件的路径与loader拼接起来并用!分割,所有的属性基本上都与路径相关。

after-resolve事件流

  这里会触发after-resolve事件流,注入地点如下:

const matchJson = /\.json$/i;
params.normalModuleFactory.plugin("after-resolve", (data, done) => {
    // if this is a json file and there are no loaders active, we use the json-loader in order to avoid parse errors
    // @see https://github.com/webpack/webpack/issues/3363
    if (matchJson.test(data.request) && data.loaders.length === 0) {
        data.loaders.push({
            loader: jsonLoaderPath
        });
    }
    done(null, data);
});

  注释已经写的很明白了,这里是检测待处理文件类型是否是json文件,如果是并且没有对应的loader,就将内置的json-loader作为loader。

callback

  接下来看回调函数内容:

this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => {
    if (err) return callback(err);

    // Ignored
    if (!result) return callback();
    // 无此事件流 返回undefined
    let createdModule = this.applyPluginsBailResult("create-module", result);
    if (!createdModule) {

        if (!result.request) {
            return callback(new Error("Empty dependency (no request)"));
        }
        // 创建
        createdModule = new NormalModule(
            result.request,
            result.userRequest,
            result.rawRequest,
            result.loaders,
            result.resource,
            result.parser
        );
    }
    // 无此事件流
    createdModule = this.applyPluginsWaterfall0("module", createdModule);

    return callback(null, createdModule);
});

  这里的两个事件流都是没有的,所以只需要看那个创建类的过程,然后就直接返回了。

  只看下构造函数:

class NormalModule extends Module {
    /* 
        request => 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js!d:\\workspace\\doc\\input.js'
        userRequest => 'd:\\workspace\\doc\\input.js'
        rawRequest => './input.js'
        loaders => [ { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' } ]
        resource => 'd:\\workspace\\doc\\input.js'
        parser => [Parser]
    */
    constructor(request, userRequest, rawRequest, loaders, resource, parser) {
        super();
        this.request = request;
        this.userRequest = userRequest;
        this.rawRequest = rawRequest;
        this.parser = parser;
        this.resource = resource;
        this.context = getContext(resource);
        this.loaders = loaders;
        this.fileDependencies = [];
        this.contextDependencies = [];
        this.warnings = [];
        this.errors = [];
        this.error = null;
        this._source = null;
        this.assets = {};
        this.built = false;
        this._cachedSource = null;
    }

    // ...原型方法
}

  只有一个getContext方法是执行的,这个方法比较简单,过一下就行:

exports.getContext = function getContext(resource) {
    var splitted = splitQuery(resource);
    return dirname(splitted[0]);
};

// 切割参数
function splitQuery(req) {
    var i = req.indexOf("?");
    if (i < 0) return [req, ""];
    return [req.substr(0, i), req.substr(i)];
}

// 返回目录
// d:\\workspace\\doc\\input.js => d:\\workspace\\doc(反斜杠这里有转义)
function dirname(path) {
    if (path === "/") return "/";
    var i = path.lastIndexOf("/");
    var j = path.lastIndexOf("\\");
    var i2 = path.indexOf("/");
    var j2 = path.indexOf("\\");
    var idx = i > j ? i : j;
    var idx2 = i > j ? i2 : j2;
    if (idx < 0) return path;
    if (idx === idx2) return path.substr(0, idx + 1);
    return path.substr(0, idx);
}

  返回了入口文件路径的目录,也就是上下文。

  这样一路回调返回,回到了最初的原型方法create:

// NormalModuleFactory
const factory = this.applyPluginsWaterfall0("factory", null);

// Ignored
if (!factory) return callback();

factory(result, (err, module) => {
    // 这里的module就是new出来的NormalModule对象
    if (err) return callback(err);
    // this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache);
    // 由于默认情况下unsafeCache为true 所以这个函数默认一直返回true
    if (module && this.cachePredicate(module)) {
        dependencies.forEach(d => d.__NormalModuleFactoryCache = module);
    }

    callback(null, module);
});

  这里对之前处理的入口文件对象做了缓存。

  返回又返回,回到了Compilation对象中:

_addModuleChain(context, dependency, onModule, callback) {
    // ...

    this.semaphore.acquire(() => {
        // 从这里出来
        moduleFactory.create({
            contextInfo: {
                issuer: "",
                compiler: this.compiler.name
            },
            context: context,
            dependencies: [dependency]
        }, (err, module) => {
            if (err) {
                this.semaphore.release();
                return errorAndCallback(new EntryModuleNotFoundError(err));
            }

            let afterFactory;
            // 不存在的属性
            if (this.profile) {
                if (!module.profile) {
                    module.profile = {};
                }
                afterFactory = Date.now();
                module.profile.factory = afterFactory - start;
            }

            const result = this.addModule(module);
            // ...
        });
    });
}

  常规的错误处理,然后判断了下profile属性,这个属性大概是用计算前面factory整个事件流的触发时间。

  总之,接着调用了addModule原型方法

addModule

// 第二个参数未传
addModule(module, cacheGroup) {
    // 这里调用的是原型方法
    /*
        identifier() {
            return this.request;
        }
    */
    // 愚蠢的方法
    const identifier = module.identifier();
    // 空对象没有的
    if (this._modules[identifier]) {
        return false;
    }
    // 缓存名是'm' + request
    const cacheName = (cacheGroup || "m") + identifier;
    // 如果有缓存的情况下
    if (this.cache && this.cache[cacheName]) {
        // ...
    }
    // 这个方法是在父类上面
    // 作用是清空属性
    // 然而并没有什么属性可以清 跳过
    module.unbuild();
    // 将类分别加入各个对象/数组
    this._modules[identifier] = module;
    if (this.cache) {
        this.cache[cacheName] = module;
    }
    this.modules.push(module);
    return true;
}

  这个方法其实并没有做什么实际的东西,简单来讲只是添加了缓存。

  接下来看下面的代码:

_addModuleChain(context, dependency, onModule, callback) {
    // ...

    this.semaphore.acquire(() => {
        moduleFactory.create({
            contextInfo: {
                issuer: "",
                compiler: this.compiler.name
            },
            context: context,
            dependencies: [dependency]
        }, (err, module) => {
            // ...
            // 返回一个true
            const result = this.addModule(module);
            // 跳过下面两步
            if (!result) {
                // ...
            }

            if (result instanceof Module) {
                // ...
            }
            // 进入这里
            /*
                (module) => {
                    entry.module = module;
                    this.entries.push(module);
                    module.issuer = null;
                }
            */
            onModule(module);

            this.buildModule(module, false, null, null, (err) => {
                if (err) {
                    this.semaphore.release();
                    return errorAndCallback(err);
                }

                if (this.profile) {
                    const afterBuilding = Date.now();
                    module.profile.building = afterBuilding - afterFactory;
                }

                moduleReady.call(this);
            });

            function moduleReady() {
                this.semaphore.release();
                this.processModuleDependencies(module, err => {
                    if (err) {
                        return callback(err);
                    }

                    return callback(null, module);
                });
            }
        });
    });
}

  这个onModule来源于方法的第三个参数,比较简单,没做什么事,然后继续跑调用了原型方法buildModule。

buildModule

// this.buildModule(module, false, null, null, (err) => {})
buildModule(module, optional, origin, dependencies, thisCallback) {
    // 无此事件流
    this.applyPlugins1("build-module", module);
    if (module.building) return module.building.push(thisCallback);
    const building = module.building = [thisCallback];

    function callback(err) {
        module.building = undefined;
        building.forEach(cb => cb(err));
    }
    module.build(this.options, this, this.resolvers.normal, this.inputFileSystem, (error) => {
        // ...
    });
}

  总的来说并没有做什么事,传进来的callback被封装并带入build方法的回调中。

module.build

// options为添加了默认参数的配置对象webpack.config.js
build(options, compilation, resolver, fs, callback) {
    this.buildTimestamp = Date.now();
    this.built = true;
    this._source = null;
    this.error = null;
    this.errors.length = 0;
    this.warnings.length = 0;
    this.meta = {};

    return this.doBuild(options, compilation, resolver, fs, (err) => {
        // ...
    });
}

  一串串的赋值,又是doBuild方法。

module.doBuild

doBuild(options, compilation, resolver, fs, callback) {
    this.cacheable = false;
    const loaderContext = this.createLoaderContext(resolver, options, compilation, fs);

    runLoaders({
        resource: this.resource,
        loaders: this.loaders,
        context: loaderContext,
        readResource: fs.readFile.bind(fs)
    }, (err, result) => {
        // ...
    });
}

  呃,已经连续调用好多个函数了。

createLoaderContext

createLoaderContext(resolver, options, compilation, fs) {
    const loaderContext = {
        // ..
    };
    compilation.applyPlugins("normal-module-loader", loaderContext, this);
    // 无此值
    if (options.loader)
        Object.assign(loaderContext, options.loader);

    return loaderContext;
}

  这里先不关心这个loaderContext里面到底有什么,因为后面会调用的,直接看事件流'normal-module-loader'。

// LoaderTargetPlugin.js
compilation.plugin("normal-module-loader", (loaderContext) => loaderContext.target = this.target);

  这里由于配置对象没有target参数,所以这个没有用。

// LoaderPlugin.js
compilation.plugin("normal-module-loader", (loaderContext, module) => {
    loaderContext.loadModule = function loadModule(request, callback) {
        // ...
    };
});

  这个仅仅是加了个属性方法,跳过。

  最后返回了一个包含很多方法的对象loaderContext。

  第二步是调用runLoaders方法,将之前生成的对象传进去,这里终于要调用loader对文件进行处理了!!!!

  下节可以有干货,这节流水账先这样了。