博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.35-浅析webpack源码之babel-loader入口文件路径读取
阅读量:4593 次
发布时间:2019-06-09

本文共 9470 字,大约阅读时间需要 31 分钟。

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

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

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

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

// Resolver.jsvar 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:\workspace\doc => ['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\node_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\node_modules\babel-loader\package.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、框架、库等都是按照这个模式读取到入口文件的。

转载于:https://www.cnblogs.com/QH-Jimmy/p/8463155.html

你可能感兴趣的文章
LeetCode 589. N叉树的前序遍历
查看>>
LeetCode 145. 二叉树的后序遍历
查看>>
Java | JDK8下的ConcurrentHashMap#putValue
查看>>
LeetCode 144. 二叉树的前序遍历
查看>>
周总结
查看>>
作业13-网络java
查看>>
Qt加载lib文件
查看>>
element vuex 语音播报
查看>>
tomcat剖析(二)
查看>>
装机摸鱼日志--ubuntu16.04安装网易云音乐客户端
查看>>
eclipse中Android模拟器,DDMS看不到设备
查看>>
Flex 布局教程学习
查看>>
day11_rowid、rownum、表分类
查看>>
软件测试培训第4天
查看>>
Android:网络操作2.3等低版本正常,4.0(ICS)以上出错,换用AsyncTask异步线程get json...
查看>>
单次插入与批量插入时间对比
查看>>
python从excel读取的数据为数字时,自动加上.0转化为浮点型的解决
查看>>
IDEA 如何加上 tomcat
查看>>
g2o使用教程
查看>>
练习题 - 利率
查看>>