nodejs 模块机制

Posted by XuBaoshi on October 1, 2016

nodejs 模块机制

1.AMD、CMD、CommonJS 模块规范

关于js为什么要引入模块化规范,请参考 https://github.com/seajs/seajs/issues/547

CommonJS规范

CommonJS规范是2009年开始提出的,最初的名字叫ServerJS,后期重命名为CommonJS,CommonJS认为一个单独的文件就是一个模块,每一个模块都是一个单独的作用域。加载模块使用require()方法,该方法读取一个文件并编译执行,最终返回文件内部的exports对象。nodejs就是采用了CommonJS的规范。

//    filename: foo.js
//    dependencies
var $ = require('jquery');

//    methods
function myFunc(){};

//    exposed public method (single)
module.exports = myFunc;

===========================================

// filename: index.js
var foo = require('./foo');
foo.myFunc();

CommonJS加载的方式采用的是同步的方式,只有资源加载完成才能执行后面的操作,nodejs主要用来做服务端的编程,所依赖的文件大多都在本地,因此读取速度快,不需要考虑异步加载。但是如果此方案用在浏览器端,文件加载时间未知可能会导致浏览器页面处于卡死的状态。于是后面就有了AMD规范与CMD规范。

AMD规范

是在requirejs在推广的过程中产出的。AMD异步加载模块,AMD是提前执行的。

define(['jquery','underscore'],function($,_){

})

AMD推崇依赖前置,但AMD依然支持依赖就近,延迟执行(由RequireJS 从 2.0增加)。

// RequireJS 2.0
define(function(require,exports,module)){
	var $ = require('jquery');
	var _ = require('underscore');
}

CMD规范

是在seajs在推广的过程中产出的,cmd是延迟执行的。

define(function(require,exports,module)){
	var $ = require('jquery');
	var _ = require('underscore');
}

CMD推崇依赖就近。

2. Node中引入模块的步骤:

(1)路径分析
(2)文件定位
(3)编译执行

3. Node中模块分为两类:

Node提供的模块(核心模块)、用户编写的模块(文件模块)。
核心模块:
在Node启动时部分核心模块直接被加载到内存中,所以在引入这些模块时,文件定位及编译执行可以省略掉,并且路径分析中优先判断,加载速度最快.
文件模块: 运行时动态加载,需要完整的路径分析、文件定位、编译执行过程,速度比核心模块慢。

4. 优先从缓存加载

Node对引入过的模块进行缓存,以减少引入时的开销。浏览器缓存的仅仅是缓存文件,而Node缓存的是编译执行后的对象。

5. 路径分析与文件定位

5.1 模块标识符分析

node通过require方法引入模块,require方法内接受一个标识符作为参数,node基于此标识符进行模块的分析与定位。

var path = require('path');                                                            // 核心模块  http、fs、path等
var test1 = require('./test1.json');                                                   // . 或 .. 开始的相对路径的文件模块
var test2 = require('../test2.json');
var test3 = require('E:/code/learn/project/js/node/module/test/test3.json');           // 绝对路径
var walk = require('walk');                                                            // 非路径形式的walk模块

核心模块的优先级仅次于缓存加载,由于Node的源码编译过程中已经将其编译为二进制编码,因此其加载过程最快。ps:http(node中的核心模块),如果自定义该模块,引入时如果希望加载成功,必须选择一个不同的标识符或者通过路径引用的方式引用该模块

var http = require('http');          // 核心模块
var http = require('http_custom');   // 更换标识符
var http = require('./http');        // 路径方式引入

路径形式的文件模块.../开始的标识符都会当做文件模块来处理,在分析路径模块时,require()方法会将路径转为真实路径进行编译,编译执行后的结果会存储在缓存中,以使二次加载更快。 由于文件模块给node指明了确切的文件位置,查找过程可以大量的节约时间,其速度仅此于核心模块。
自定义模块指非核心模块同时也不是路径形式的模块,属于一种特殊的文件模块,可能是一个文件或者包的形式,查找最费时,也是最慢的一种。

NODE_PATH
操作系统中都会有一个环境变量,当系统调用一个命令时,就会在PATH变量中寻找,如果注册路径中存在则调用,如果没有就提示命令没有找到。
NODE_PATH就是node中模块所提供的注册路径环境变量。使用;进行分割不同的路径。

module.paths

windows系统下

如果寻找一个文件,nodejs首先会从当前目录下的node_modules、上级目录的node_modules,逐级查找直至到根目录的node_modules。文件目录越深,文件查找耗时越多,如果还没有查找到指定模块的话,就会去 NODE_PATH中注册的路径中查找,因此去NODE_PAtH中首次(找到后会进行缓存)查找也是最慢的。

5.2 文件扩展名分析

如果require() 方法内的参数不添加标识符,Node会按照.js、.json、.node的顺序依次加载。

5.3 目录分析和包

1.通过require()方法进行查找文件所得到的可能不是一个文件而是一个目录,这时node会将这个目录当做一个包进行处理。
首先node会在当前的目录下查找package.json文件,通过JSON.parse()方法将package.json进行解析成描述该包的对象,从该对象main属性指定的文件进行定位。
2.如果没有package.json,node会认为index当做默认的文件名,依次去查找.js、.json、.node文件。
3.如果在此目录分析过程中没有定位到任何文件的话,node会进入下一个模块路径进行查找。如果没有找到则直接抛错查找异常。

6. 模块编译

编译和执行是引用模块的最后一个阶段,定位到文件后node会新建一个模块对象,然后根据路径进行载入并编译。不同的文件扩展名其载入方式也是不同的。

  1. .js文件。通过fs模块同步读取后编译执行。
  2. .node文件。使用c/c++编写的扩展文件,通过内建的process.dlopen()方法加载编译执行。
  3. .json文件。通过fs模块同步读取文件后,用JSON.parse()解析返回结果。
  4. 其余扩展名文件。当做.js文件进行载入。

7. 包与npm

第三方模块中,模块与模块之间是散列在各地的,相互之间不能引用。包和npm则是将模块之间联系起来的一种机制。

7.1 npm

npm其实是node的包管理工具,可以帮助node的使用者发布、安装、依赖第三方包。

7.2 包的结构

通过npm install下载的包实际上是一个目录直接打包成.zip或tar.gz格式的文件,下载后安装解压成目录。符合CommonJS规范的包的结果应该包含以下文件:

  • package.json: 包的描述文件。
  • bin:用来指定各个内部命令对应的可执行文件的位置。
  • lib:用于存放javascript代码的目录。
  • doc:用于存放文档的目录。
  • test:用于存放单元测试代码的目录。

7.3 包的安装

npm install walk
npm install webpack

webpack为例 如果当前目录没有node_modules文件夹 执行命令后npm会在当前目录下创建node_modules,同时在node_modules文件夹内创建webpack文件夹,最后将下载后的压缩文件解压到webpack中。npm install 执行完成。

全局模式安装

npm install walk -g
npm install webapck -g

确切的说将此命令称为全局模式安装是不精确的,这会让人产生很多误解。
全局模式并不是将一个模块包安装为一个全局包的意思,这并不意味这你可以通过require()方法在任何地方来引用它。
-g事实上是将该包安装为全局可用的可执行命令。如:webpack、grunt、gulp等。

// webpack package.json

{
	...
	 "bin": {
		"webpack": "./bin/webpack.js"
	 }
	...
}


// walk package.json 并没有bin属性因此walk是不可能作为全局可用的可执行命令

node在运行时会解析bin属性所对应的文件路径加入到系统的PATH变量中,因此可以在dos命令行中任何位置使用该命令。

// 查看全局安装模式包的位置
npm root -g