碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

在NodeJS项目开发过程中,我们经常使用公共JS库。


比较常用的做法就是通过npm去install目标js库,然后这个库会被放在node_modules目录下。


接着,我们自己写的JS文件中,使用require("目标js")库来使用他人共享的代码,这样很容易事半功倍。


下面谈谈require函数

本文会使用Node REPL:这是一个交互式的NodeJS代码执行终端,更多参考: http://nodejs.cn/learn/how-to-use-the-nodejs-repl


先搞清楚是什么

首先,直接说require的函数功能:用来加载目标js库,并返回当前库公开的属性成员函数/变量。


我们打开terminal终端/Command,输入: node //打开Node REPL


然后输入下面内容:


require


this.require === require


碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

所以这里得到结论:require是Node引擎上下文(context)的内置对象属性,也就是全局对象的require属性,可调用或者使用this.require也行。


require能用来干什么?

初学NodeJS会了解到它有内置模块,比如fs,http 等。好,这里我们试着require('fs') //加载文件系统模块


require('fs')


碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

非内置的模块,也想用require来加载怎么做?

在当前目录下,我们编写一个product.js(内容如下)。然后,试着用require来加载看看。

const products = {data:[]}
 
function getData(){
  return products.data;
}

Node REPL终端输入:require('./product.js')

碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

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相当于

const products = {data:[]}
 
function getData(){
  return products.data;
}
 
//默认情况,module.exports 这个对象没有任何属性,如下代码。
module.exports = {}

好,修改product.js文件,继续在终端跑require('./product')看看(输出仍旧为:{} )。

这里需要退出当前node终端,重新进入(请读者带着一个思考题:为什么需要重新打开一个node REPL终端)。

碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

那么在npm registry上的库,怎么进行加载?

比如输入require('lodash') 马上发现错误了,“Cannot find module 'lodash'", 这个错误经常容易见到(有时候拿到一个NodeJS项目忘记跑npm install了)。


碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

也可以输入require('restify'), require加载一些常见的模块试试。


通常我们需要使用命令安装JS库:npm install 目标JS库名,再来使用它共享的功能。


我们试试看,安装完lodash库之后,继续在Node终端输入require('lodash') 可以使用了,这里不需要重启(想想为什么)。


碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

好了,前面提了几个围绕了是否重新开一个NodeREPL终端来require JS库的问题

为何node终端能够加载到product.js, export内部函数之后又需要重启,引入外部JS库又不需要重开一个Node REPL终端。。。


这里需要讲讲require的另一个伙伴,module函数。


它跟require函数一样都挂载在上下文中,也是全局对象的一个属性,它的作用是管理整个项目的模块。


碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

上面所示,在一个有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函数,可是我们加载到的仍旧是:{}//无任何函数导出。


碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

解答为何npm install lodash之后为何能够直接在node终端直接require

这个很简单,因为node启动,默认会查找到当前目录下的node_modules目录(不管目录存在不存在)。


当我们require一个不存在的js模块的时候,module对象找不到模块,它的children属性并不会有任何变动。


所以只需要安装了,就可以require加载。

碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

构建代码共享,开源文化

这算是个题外话,前面有文章写过关于实现商品的增删查改,https://blog.csdn.net/geeklevin/article/details/109403172


读者可以下载这个JS:https://codechina.csdn.net/geeklevin/nodejs-api-002-crud/-/blob/master/product.js,使用require调用就能获得对商品对增删查改的功能。


通过对代码进行封装,对外开放几个函数,隐藏了细节,也简化了对功能的使用。


var pm = require('./product')
 
//调用库开放函数
pm.getData()
 
//其他调用
pm.其他函数()

好用的代码像诗,越简洁,越好用,那些垃圾代码只会慢慢沉寂下来无人问津。



npm registry (https://www.npmjs.com/

在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上的包的解析,可以自行阅读它的代码,这一块还是比较好玩的。


参考:


https://nodejs.org/en/knowledge/getting-started/what-is-require/


https://nodejs.org/docs/v0.4.2/api/modules.html (这里的模块加载方式已经过时,不过刚好没有找到module的解析,可以看看,原理类似)



上一篇:物联网风潮驱动 传感器专利布局动作频频


下一篇:分享25个很棒的网页设计教程和资源网站