碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载
目录
在NodeJS项目开发过程中,我们经常使用公共JS库。
比较常用的做法就是通过npm去install目标js库,然后这个库会被放在node_modules目录下。
接着,我们自己写的JS文件中,使用require("目标js")库来使用他人共享的代码,这样很容易事半功倍。
下面谈谈require函数
本文会使用Node REPL:这是一个交互式的NodeJS代码执行终端,更多参考:
先搞清楚是什么
首先,直接说require的函数功能:用来加载目标js库,并返回当前库公开的属性成员函数/变量。
我们打开terminal终端/Command,输入: node //打开Node REPL
然后输入下面内容:
require
this.require === require
所以这里得到结论:require是Node引擎上下文(context)的内置对象属性,也就是全局对象的require属性,可调用或者使用this.require也行。
require能用来干什么?
初学NodeJS会了解到它有内置模块,比如fs,http 等。好,这里我们试着require('fs') //加载文件系统模块
require('fs')
非内置的模块,也想用require来加载怎么做?
在当前目录下,我们编写一个product.js(内容如下)。然后,试着用require来加载看看。
Node REPL终端输入:require('./product.js')
require函数能够加载这个product.js,不过不像内置模块一样,需要通过给路径来定位到js文件,如:require('./product') 或者 require('./product.js')
这里我们看到打印的对象没有任何属性,require返回值为 :{} //没有任何属性。
require函数加载原理
由于NodeJS模块都遵循了CommonJS规范,根据CommonJS规范,JS库的开发者如果需要开发某些函数对外部模块使用,需要使用module.exports或者exports
具体如下:
module.exports.属性名 = 函数引用 //这里将当前JS内的某个函数赋给module.exports
或者:
exports.属性名 = 函数引用
像前面一个版本的product.js相当于
好,修改product.js文件,继续在终端跑require('./product')看看(输出仍旧为:{} )。
这里需要退出当前node终端,重新进入(请读者带着一个思考题:为什么需要重新打开一个node REPL终端)。
那么在npm registry上的库,怎么进行加载?
比如输入require('lodash') 马上发现错误了,“Cannot find module 'lodash'", 这个错误经常容易见到(有时候拿到一个NodeJS项目忘记跑npm install了)。
也可以输入require('restify'), require加载一些常见的模块试试。
通常我们需要使用命令安装JS库:npm install 目标JS库名,再来使用它共享的功能。
我们试试看,安装完lodash库之后,继续在Node终端输入require('lodash') 可以使用了,这里不需要重启(想想为什么)。
好了,前面提了几个围绕了是否重新开一个NodeREPL终端来require JS库的问题
为何node终端能够加载到product.js, export内部函数之后又需要重启,引入外部JS库又不需要重开一个Node REPL终端。。。
这里需要讲讲require的另一个伙伴,module函数。
它跟require函数一样都挂载在上下文中,也是全局对象的一个属性,它的作用是管理整个项目的模块。
上面所示,在一个有product.js 和node_modules/lodash这个模块,进入node终端,打印module显示的了模块加载的细节,这里稍微留意一下children(目前是一个空的数组)。
解答”Cannot find module"问题
但是paths非空,我们使用require加载函数的时候,node引擎会从内置模块和paths对应的路径去查找模块,找不到才会抛出类似异常:“Cannot find module 'lodash'"
当我们跑了npm install 库名, 对应模块被下载到node_module目录,加载的时候才能定位到库,正常使用该库功能。
在含有package.json的目录中,执行npm install命令,可以一次性下载dependencies属性声明的全部依赖库,在我们写的js文件中能够正常使用。
解答是否需要重启Node REPL 或者修改代码是否需要重启正在的NodeJS进程的问题
继续在终端输入require('./product') ,然后输入 module, 再次输出module对象,它的children已经多了一个Module对象(id对应到了product.js)。
当我们修改了product.js的时候,node引擎发现module对象已经记录加载过product.js了,不会重新进行加载。所以,虽然最新代码导出了getData函数,可是我们加载到的仍旧是:{}//无任何函数导出。
解答为何npm install lodash之后为何能够直接在node终端直接require
这个很简单,因为node启动,默认会查找到当前目录下的node_modules目录(不管目录存在不存在)。
当我们require一个不存在的js模块的时候,module对象找不到模块,它的children属性并不会有任何变动。
所以只需要安装了,就可以require加载。
构建代码共享,开源文化
这算是个题外话,前面有文章写过关于实现商品的增删查改,
。读者可以下载这个JS:
,使用require调用就能获得对商品对增删查改的功能。通过对代码进行封装,对外开放几个函数,隐藏了细节,也简化了对功能的使用。
好用的代码像诗,越简洁,越好用,那些垃圾代码只会慢慢沉寂下来无人问津。
npm registry (
)在npm registry上面有很多开发者发布的库,当我们想要使用一个功能,或者实现一个功能之前,不妨先上去找找有没有别人写好的库。
如果没有,那么遵循CommonJS(前面说的module.exports)来组织我们的代码发布共享,解决问题同时帮助他人,微薄之力能够推动社区进步呢。
npm社区就这样通过一个个普通开发者,分享一个个的小的库,培养了一个丰富多彩的技术生态。
Atwood’s Law是Jeff Atwood在2007年提出的:“any application that can be written in JavaScript, will eventually be written in JavaScript.”
这里我们在简单大胆的总结,require和module互相协作产生的模块加载机制,是整个NodeJS开源文化的基石之一;而CommonJS就是一个脱离了框架的协议。
这也在很多语言中反复出现,像python/java的import包,CommonJS就像一个包协议约定了库的共享的标准格式,npm对标maven central/python libs。
这套协议加上加载模式相关的接口模式,很值得借鉴。
总结
本文简单的介绍了require和module函数,Node引擎内使用module来管理模块,使用require加载模块。
基于这个技术和协议延伸了:如何做代码分享,构建JS模块分享的生态。
本篇从使用函数反向思考简单涉猎,更多细节可以自行阅读NodeJS源码。
篇幅有限,后续会发表更多技术补充。细心的读者可以发现,本文对于加载lodash这个模块没有更多深入解析,这种发布在npm registry上的包的解析,可以自行阅读它的代码,这一块还是比较好玩的。
参考:
(这里的模块加载方式已经过时,不过刚好没有找到module的解析,可以看看,原理类似)
- 点赞
- 收藏
- 关注作者
评论(0)