nodejs自定义模块查找规则
node使用require标识符进行模块查找的优先级
- 核心模块,如 http、fs、path 等。
- 以 . 或 … 开始的相对路径模块。
- 以 / 开始的绝对路径文件模块。
- 非路径形式的文件模块,如自定义的connect模块。
核心模块
核心模块的优先级仅仅次于缓存加载,它在node的源代码中已经编译成二进制代码,故其加载过程最快。
路径形式的文件模块
以 . / … 和 / 开始的标识符,这里被当作文件模块来处理。在分析路径模块时,require()方法会将路径转为真实路径,并以真实路径作为索引,将编译路径作为索引,将编译执行后的结果存放到缓存中,以使二次加载时更快。
*由于文件模块给node指明了确切的文件位置,所以在查找过程中可节省大量时间,其加载速度慢于核心模块。
自定义模块
自定义模块指的是非核心模块,也不是路径形式的标识符。他是是一种特殊的文件模块,可能是一个文件或者包的形式。这类模块的查找是最费时的,也是所有模块中最慢的一种。
在介绍自定义模块的查找方式之前,需要先介绍一下模块路径这个概念。
模块路径是node在定位文件模块时制定的查找策略,具体表现为一个路径组成的数组。
模块路径的生成规则如下:
- 当前文件目录下的node_modules目录。
- 父目录下的node_modules目录。
- 父目录的父目录下的node_modules目录。
- 沿路径向上级逐级递归,直到根目录下的node_modules目录。
他的生成方式与js的原型链或作用域链的查找方式十分类似。在加载过程中,node会逐个尝试模块路径中的路径,直到找到目标文件为止。可以看出,当前文件的路径越深,模块的查找耗时就会越多,这也是自定义模块的加载速度最慢的原因。
//新建一个index.js
console.log(module.path);
//输出该文件的路径数组
文件定位
从缓存中加载的优化策略使得二次加载使不需要路径分析,文件定位和编译执行的过程,大大提高了再次加载模块的效率。
但在文件定位过程中,还有一些细节需要注意,包括文件扩展名的分析,目录和包的处理。
- 文件扩展名分析
require()分析标识符的过程中,会出现一些不包含扩展名的情况。CommonJS模块规范也允许在标识符中标识符中不包含文件扩展名,这种情况下,node会按 .node 和 .json 文件,在传递给require()的标识符中带上标识符带上扩展名,会加快一点速度。另一个诀窍是:同步配合缓存,可大幅度缓解node单线程中阻塞时调用的缺陷。 - 目录分析和包
在分析标识符的过程中,require()通过分析扩展名之后,可能没有查找到对应的文件,但却得到一个目录,这在引入自定义模块和逐个模块路径进行查找时经常会出现,此时node会将目录当作一个包来处理。
在这个过程中,node对CommmonJS包规范进行了一定程度的支持。首先,node在当前目录下查找package.json(CommonJS包规范定义的包描述文件),通过JSON.parse()解析出包描述对象,从中找出main属性指定的文件名进行定位。如果文件名缺少扩展名,将会进入扩展名分析步骤。
如果main属性指定的文件名错误,或者压根没有package.json文件,node会将index当作默认文件名,然后依次查找index.js 、 index.node 、 index.json 。
如果在目录分析过程中没有定位成功任何文件,则自定义模块进入下一个模块路径进行查找。如果模块路径数组都被遍历完,依旧没找到目标文件,则会抛出失败的异常。