zoukankan      html  css  js  c++  java
  • .35-浅析webpack源码之babel-loader入口文件路径读取

      哈哈,上首页真难,每次都被秒下,心疼自己1秒~

      这里补充一个简要图,方便理解流程:

      在处理./input.js入口文件时,在类型判断被分为普通文件,所以走的文件事件流,最后拼接得到文件的绝对路径。

      但是对应"babel-loader"这个字符串,在如下正则中被判定为模块类型:

    // Resolver.js
    var notModuleRegExp = /^.$|^.[\/]|^..$|^..[/\]|^/|^[A-Z]:[\/]/i;
    Resolver.prototype.isModule = function isModule(path) {
        return !notModuleRegExp.test(path);
    };

      因此,参考第33节的流程图,在ModuleKindPlugin插件中,会走新的事件流,如下:

    ModuleKindPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        resolver.plugin(this.source, function(request, callback) {
            // 这里request.module为true 继续走下面的代码
            if (!request.module) return callback();
            var obj = Object.assign({}, request);
            delete obj.module;
            // target => raw-module
            resolver.doResolve(target, obj, "resolve as module", createInnerCallback(function(err, result) {
                if (arguments.length > 0) return callback(err, result);
    
                // Don't allow other alternatives
                callback(null, null);
            }, callback));
        });
    };

      进入raw-module事件流!

    raw-module事件流

      事件流定义如下:

    // raw-module
    // 默认为空数组
    moduleExtensions.forEach(function(item) {
        plugins.push(new ModuleAppendPlugin("raw-module", item, "module"));
    });
    // 垃圾插件
    if (!enforceModuleExtension)
        plugins.push(new TryNextPlugin("raw-module", null, "module"));

      这里没有任何操作,直接进入module事件流。

    module事件流 => ModulesInHierachicDirectoriesPlugin

      定义地点如下:

    // module
    // modules => [ [ 'node_modules' ] ]
    modules.forEach(function(item) {
        // 进这个分支
        if (Array.isArray(item))
            plugins.push(new ModulesInHierachicDirectoriesPlugin("module", item, "resolve"));
        else
            plugins.push(new ModulesInRootPlugin("module", item, "resolve"));
    });

      这个modules是默认的一个数组,内容为node_modules,后来二次包装变成个二维数组了。

      总之不管那么多,直接看插件内容:

    ModulesInHierachicDirectoriesPlugin.prototype.apply = function(resolver) {
        var directories = this.directories;
        var target = this.target;
        resolver.plugin(this.source, function(request, callback) {
            var fs = this.fileSystem;
            // 早该这样了 一堆callback谁分的清啊
            var topLevelCallback = callback;
            // getPaths获取进程路径各级目录
            // D:workspacedoc => ['d:\workspace\doc','d:\workspace','d:']
            // 这个map+reduce总的来说就是拼接路径
            /* 
                最后会生成
                [ 
                    'D:\workspace\doc\node_modules',
                    'D:\workspace\node_modules',
                    'D:\node_modules' 
                ]
            */
            var addrs = getPaths(request.path).paths.map(function(p) {
                return directories.map(function(d) {
                    return this.join(p, d);
                }, this);
            }, this).reduce(function(array, p) {
                array.push.apply(array, p);
                return array;
            }, []);
            // 开始读取每一个拼接成的数组
            forEachBail(addrs, function(addr, callback) {
                fs.stat(addr, function(err, stat) {
                    // 当读取到一个有效路径时就进入下一个事件流
                    if (!err && stat && stat.isDirectory()) {
                        var obj = Object.assign({}, request, {
                            path: addr,
                            request: "./" + request.request
                        });
                        var message = "looking for modules in " + addr;
                        return resolver.doResolve(target, obj, message, createInnerCallback(callback, topLevelCallback));
                    }
                    if (topLevelCallback.log) topLevelCallback.log(addr + " doesn't exist or is not a directory");
                    if (topLevelCallback.missing) topLevelCallback.missing.push(addr);
                    return callback();
                });
            }, callback);
        });
    };

      这里的方法很简单很暴力,直接拆分当前的目录,然后跟node_modules进行拼接,然后尝试读取每一个路径。

      当读取成功而且该目录是一个文件夹时,就会把信息加入对象并进入下一个事件流。

      这个事件流的名字是resolve……是的,又要进入新的一轮循环,这次会以普通文件的形式读取,所以之前在获取模块类型的第一时间需要删除module属性。

      再次进入doResolve中,这里只用文字描述流程,request变成了./babel-loader,path为node_modules文件夹的路径。

    1、ParsePlugin类型判断

      由于在上面的代码中,request被手动添加了./的前缀,所以在模块判断正则中被判定为非模块。

    2、DescriptionFilePlugin

      由于path修改,所以在读取package.json时会直接读取node_modules/babel-loader/package.json文件。

      但是在第一次读取会失败,因为并不存在node_modules/package.json文件,修正过程看下面。

    3、JoinRequestPlugin

      在这个插件,配置文件读取路径会被修正,代码如下:

    JoinRequestPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        resolver.plugin(this.source, function(request, callback) {
            /* 
                path => D:workspace
    ode_modules
                request => ./babel-loader
                拼接后得到 D:\workspace\node_modules\babel-loader
            */
            var obj = Object.assign({}, request, {
                path: resolver.join(request.path, request.request),
                relativePath: request.relativePath && resolver.join(request.relativePath, request.request),
                request: undefined
            });
            resolver.doResolve(target, obj, null, callback);
        });
    };

     4、FileExistsPlugin

      由于上面的修正,这个插件的读取会有一些问题:

    FileExistsPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        resolver.plugin(this.source, function(request, callback) {
            var fs = this.fileSystem;
            var file = request.path;
            // file => D:\workspace\node_modules\babel-loader
            fs.stat(file, function(err, stat) {
                if (err || !stat) {
                    if (callback.missing) callback.missing.push(file);
                    if (callback.log) callback.log(file + " doesn't exist");
                    return callback();
                }
                // 由于这是一个文件夹 所以会进入这个分支
                if (!stat.isFile()) {
                    if (callback.missing) callback.missing.push(file);
                    if (callback.log) callback.log(file + " is not a file");
                    return callback();
                }
                this.doResolve(target, request, "existing file: " + file, callback, true);
            }.bind(this));
        });
    };

      由于读取到这是一个文件夹,所以不会进入最后的事件流,而是直接调用无参回调函数,再次重新尝试读取。

    文件夹读取 => existing-drectory

      由于这里会读取文件夹类型,所以顺便把文件夹的事件流分支过一下。

      相关的核心事件流只有existing-directory,如下:

    // existing-directory
    // 这个忽略
    plugins.push(new ConcordMainPlugin("existing-directory", {}, "resolve"));
    // 默认 mainFields => ["browser", "module", "main"]
    mainFields.forEach(function(item) {
        plugins.push(new MainFieldPlugin("existing-directory", item, "resolve"));
    });
    // 默认 mainFiles => ["index"]
    mainFiles.forEach(function(item) {
        plugins.push(new UseFilePlugin("existing-directory", item, "undescribed-raw-file"));
    });

      除去第一个垃圾插件,后面两个可以过一下。

    MainFieldPlugin

      这个插件主要获取loader的入口文件相对路径。

    // existing-directory
    // 这个忽略
    plugins.push(new ConcordMainPlugin("existing-directory", {}, "resolve"));
    /*
        默认mainFields => 
        { name: 'loader', forceRelative: true }
        { name: 'main', forceRelative: true }
    */
    mainFields.forEach(function(item) {
        plugins.push(new MainFieldPlugin("existing-directory", item, "resolve"));
    });
    MainFieldPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        // options => ["browser", "module", "main"]
        var options = this.options;
        resolver.plugin(this.source, function mainField(request, callback) {
            if (request.path !== request.descriptionFileRoot) return callback();
            var content = request.descriptionFileData;
            // path.basename => 获取path的最后一部分
            // D:workspace
    ode_modulesabel-loaderpackage.json => package.json
            var filename = path.basename(request.descriptionFilePath);
            var mainModule;
            // loader、main
            var field = options.name;
            if (Array.isArray(field)) {
                var current = content;
                for (var j = 0; j < field.length; j++) {
                    if (current === null || typeof current !== "object") {
                        current = null;
                        break;
                    }
                    current = current[field[j]];
                }
                if (typeof current === "string") {
                    mainModule = current;
                }
            } else {
                // 获取配置文件中对应键的值
                if (typeof content[field] === "string") {
                    mainModule = content[field];
                }
            }
            if (!mainModule) return callback();
            if (options.forceRelative && !/^..?//.test(mainModule))
                mainModule = "./" + mainModule;
            // 定义request的值
            var obj = Object.assign({}, request, {
                request: mainModule
            });
            return resolver.doResolve(target, obj, "use " + mainModule + " from " + options.name + " in " + filename, callback);
        });
    }

      而在babel-loader中,有一个名为main的键,值为'lib/index.js',与目录拼接后,刚好是babel-loader这个模块的入口文件的绝对路径。

      一旦在package.json中找到了对应的值,就会跳过下一个插件,直接进入最终的事件流。

      但是为了完整,还是看一眼,当package.json中没有对入口文件的路径进行定义时,会进入MainFieldPlugin插件,源码如下:

    UseFilePlugin.prototype.apply = function(resolver) {
        var filename = this.filename;
        var target = this.target;
        resolver.plugin(this.source, function(request, callback) {
            // 默认filename => "index"
            // 直接对目录与默认入口文件名进行拼接
            var filePath = resolver.join(request.path, filename);
            // 重定义键
            var obj = Object.assign({}, request, {
                path: filePath,
                relativePath: request.relativePath && resolver.join(request.relativePath, filename)
            });
            resolver.doResolve(target, obj, "using path: " + filePath, callback);
        });
    };

      很简单,直接拼接默认入口文件名与目录,接下来流程如下:

    mainFiles.forEach(function(item) {
        plugins.push(new UseFilePlugin("existing-directory", item, "undescribed-raw-file"));
    });
    
    // undescribed-raw-file
    // 重新走一边文件类型的流程
    plugins.push(new DescriptionFilePlugin("undescribed-raw-file", descriptionFiles, "raw-file"));
    plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));

      可以看到,拼接完后,将以文件类型的形式尝试重新读取新路径。

      至此,模块的入口文件路径读取过程解析完毕,基本上其他loader、框架、库等都是按照这个模式读取到入口文件的。

  • 相关阅读:
    HDU 5188 zhx and contest(带限制条件的 01背包)
    Appium IOS 自己主动化測试初探
    微软自拍应用iOS版公布
    小白学开发(iOS)OC_ block数据类型(2015-08-08)
    从王自如和老罗的论战中我貌似懂得了点神马...
    trie树(字典树)
    分析深圳电信的新型HTTP劫持方式
    主要的约瑟夫环问题
    Linux下的led驱动程序,ok6410
    Android Support 包里到底有什么
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8463155.html
Copyright © 2011-2022 走看看