最近在试图加载npm全局下的express
模块的时候,出现Error: Cannot find module 'express'
的报错,按理说我已经将express安装到了全局的node_modules目录,为什么还会加载不到这个模块呢?
本文将以该问题作为切入点,记录一下node的module
加载机制,算是自我学习吧。
NPM
NPM(node package manager)是NODE下的一款包管理工具,它的主要功能就是管理node包,包括:安装、卸载、更新、查看、搜索、发布等。它将开发者从繁琐的包管理工作中解放出来,极大的便利了我们的开发。
NPM有两种安装方式:
- npm install –global xxx 属于全局安装(示例:npm install -g express)
- npm install xxx 属于本地安装
全局安装是把包安装到了(npm config get prefix)lib/node_modules
这个全局目录下
本地安装则是把包安装到了当前目录下的./node_modules
目录下。
NODE_PATH
NODE_PATH是NODE中用来寻找模块所提供的路径注册环境变量,像操作系统中都会有一个PATH环境变量,当系统调用一个命令的时候,就会在PATH变量中注册的路径中寻找,如果注册的路径中有就调用,否则就提示命令没找到,NODE_PATH类似如此。
# 将 /usr/bin 追加到 PATH 变量中
export PATH=$PATH:
# 指定 NODE_PATH 变量,并用:分隔了多个不同的目录
export NODE_PATH="/usr/lib/node_modules:/usr/local/lib/node_modules"
通过上面的方法指定NODE_PATH环境变量后,文章开始遇到的问题就迎刃而解了,哦,原来是没有设置NODE_PATH环境变量的缘故(在Windows中,NODE_PATH用分号而不是冒号分隔)。
在unix系统下,可以通过如下方式添加NODE_PATH环境变量
vi ~/.bash_profile
如果在用户目录下没有.bash_profile文件,则新建一个,然后填写如下内容
export NODE_PATH="/usr/lib/node_modules:/usr/local/lib/node_modules"
最后,通过 source .bash_profile
命令使其立即生效。
模块缓存
模块可以多次加载,但只会在第一次加载的时候运行一次,然后运行结果(module.exports)就被缓存了,以后再加载,就直接读取缓存结果。
模块加载
NODE模块加载(查找)是从项目的根位置递归搜寻 node_modules
目录,直到文件系统根目录的 node_modules
,如果还没有查找到指定模块的话,就会去NODE_PATH
中注册的路径中查找。
在Node.js中,对于每一个被加载的文件模块,创建这个模块对象的时候,这个模块便会有一个paths属性,其值根据当前文件的路径计算得到。我们创建modulepath.js这样一个文件,其内容为:
console.log(module.paths);
执行 node modulepath.js, 将得到如下输出结果:
[ '/Users/sobird/node_modules',
'/Users/node_modules',
'/node_modules' ]
如上,Node.js的模块加载正式按照上面给出的目录集合按照顺序进行加载。
如果require是绝对路径,则直接加载,无需查找,速度最快。
具体如下:
- 从module.paths数组中取出第一个目录作为查找基准
- 直接从目录中查找该文件,如果文件存在则结束查找,否则进入下一条查找
- 尝试加载.js,.node, .json后缀之后查找,如果文件存在则结束查找,否则进入下一条查找
- 尝试将require的参数作为一个包来进行查找,读取目录下的package.json文件,取得main参数指定的文件
- 尝试查找该文件,如果文件存在则结束查找,否则进行第三条查找
- 如果继续查找失败,则取出module.paths数组中的下一目录作为基准查找,循环第1-5步骤
- 如果继续查找失败,循环第1-6步骤,直到module.paths中的最后一个值
- 如果继续查找失败,则抛出异常
动态更改NODE_PATH
明白了上面module.paths的作用,我们在代码编写的时候,就可以随意指定模块加载路径,比如,现在有模块a.js 和 b.js,他们在磁盘中的路径分别是 /path/a/a.js, /path/b/b.js。
如果我们想在a模块中加载b模块的依赖c,可以通过下面的写法实现:
// 加载模块绝对路径
var b = require('/path/b/node_modules/c.js');
那么,如果通过相对路径的方式如何加载?
// 在a模块中这方式是加载不到b模块的
// 修改module.paths的目录集合,将b模块的node_modules目录push进来
module.paths = module.paths.push('/path/b/node_modules');
var b = require('c.js');
如上,这样就可以加载到b模块的依赖c模块了~
这篇文章目前没有评论