1.解析模块步骤

参考 nodejs
参考 tslang

相对 vs. 非相对模块导入

根据模块引用是相对的还是非相对的,模块导入会以不同的方式解析。

相对导入是以/./../开头的。 下面是一些例子:

import Entry from "./components/Entry";
import { DefaultHeaders } from "../constants/http";
import "/mod";

所有其它形式的导入被当作非相对的。 下面是一些例子:

import * as $ from "jQuery";
import { Component } from "@angular/core";
Node.js如何解析模块(相对路径)

相对路径很简单。 例如,假设有一个文件路径为 /root/src/moduleA.js,包含了一个导入var x = require("./moduleB"); Node.js以下面的顺序解析这个导入:

  1. 检查/root/src/moduleB.js文件是否存在。

  2. 检查/root/src/moduleB目录是否包含一个package.json文件,且package.json文件指定了一个main模块。 在我们的例子里,如果Node.js发现文件/root/src/moduleB/package.json包含了{ "main": "lib/mainModule.js" },那么Node.js会引用/root/src/moduleB/lib/mainModule.js

  3. 检查/root/src/moduleB目录是否包含一个index.js文件。 这个文件会被隐式地当作那个文件夹下的main模块。

Node.js如何解析模块(非相对模块名)

Node会在一个特殊的文件夹node_modules里查找你的模块。 node_modules可能与当前文件在同一级目录下,或者在上层目录里。 Node会向上级目录遍历,查找每个 node_modules直到它找到要加载的模块。

还是用上面例子,但假设/root/src/moduleA.js里使用的是非相对路径导入var x = require("moduleB");。 Node则会以下面的顺序去解析 moduleB,直到有一个匹配上。

  1. [第一轮] /root/src/node_modules/moduleB.js

  2. [第一轮] /root/src/node_modules/moduleB/package.json (如果指定了”main”属性)

  3. [第一轮] /root/src/node_modules/moduleB/index.js

  4. [第二轮] /root/node_modules/moduleB.js

  5. [第二轮] /root/node_modules/moduleB/package.json (如果指定了"main"属性)

  6. [第二轮] /root/node_modules/moduleB/index.js

  7. [第三轮] /node_modules/moduleB.js

  8. [第三轮] /node_modules/moduleB/package.json (如果指定了"main"属性)

  9. [第三轮] /node_modules/moduleB/index.js

注意Node.js在步骤(4)和(7)会向上跳一级目录。

CommonJS规范

参考:
https://javascript.ruanyifeng.com/nodejs/module.html
http://nodejs.cn/api/modules.html
https://segmentfault.com/a/1190000010576927

全局变量
module    module变量代表当前模块
exports   就是module.exports
require   require用于加载其他模块
global    共享的全局变量
module实例成员(module变量代表当前模块)
module.id           模块的识别符,通常是带有绝对路径的模块文件名。
module.filename     模块的完全解析后的文件名,带有绝对路径。
module.loaded       模块是否已经加载完成,或正在加载中。
module.parent       返回一个对象,最先引用该模块的模块。
module.paths        模块的搜索路径
module.children     被该模块引用的模块对象
module.exports      表示模块对外输出的值。
module.require(id)  module.require() 方法提供了一种加载模块的方法,就像从原始模块调用 require() 一样
require成员
require():             加载外部模块
require.resolve():    将模块名解析到一个绝对路径
require.main:         指向主模块
require.cache:        指向所有缓存的模块
require.extensions:   根据文件的后缀名,调用不同的执行函数
require命令
// 删除指定模块的缓存
delete require.cache[moduleName];

// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key){
  delete require.cache[key];
})
模块的加载机制

CommonJS模块的加载机制是,输入的是被输出的值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值

require的内部处理流程

require命令是CommonJS规范之中,用来加载其他模块的命令,它其实不是一个全局命令,而是指向当前模块的module.require命令,而后者又调用Node的内部命令Module._load

Module._load = function(request, parent, isMain) {
  // 1. 检查 Module._cache,是否缓存之中有指定模块
  // 2. 如果缓存之中没有,就创建一个新的Module实例
  // 3. 将它保存到缓存
  // 4. 使用 module.load() 加载指定的模块文件,读取文件内容之后,使用 module.compile() 执行文件代码
  // 5. 如果加载/解析过程报错,就从缓存删除该模块
  // 6. 返回该模块的 module.exports
};

上面的第4步,采用module.compile()执行指定模块的脚本,逻辑如下

Module.prototype._compile = function(content, filename) {
  // 1. 生成一个require函数,指向module.require
  // 2. 加载其他辅助方法到require
  // 3. 将文件内容放到一个函数之中,该函数可调用 require
  // 4. 执行该函数
};

上面的第1步和第2步,require函数及其辅助方法主要如下

require(): 加载外部模块
require.resolve():将模块名解析到一个绝对路径
require.main:指向主模块
require.cache:指向所有缓存的模块
require.extensions:根据文件的后缀名,调用不同的执行函数

一旦require函数准备完毕,整个所要加载的脚本内容,就被放到一个新的函数之中,这样可以避免污染全局环境。该函数的参数包括require、module、exports,以及其他一些参数。

(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});

Module.compile方法是同步执行的,所以Module.load要等它执行完成,才会向用户返回module.exports的值。

模块作用域
__dirname       当前模块的目录名
__filename      当前模块的文件名。 这是当前的模块文件的绝对路径(符号链接会被解析)
exports         这是一个对于 module.exports 的更简短的引用形式
module          对当前模块的引用, 查看关于 module 对象的章节
require(id)     用于引入模块、 JSON、或本地文件
require.cache   被引入的模块将被缓存在这个对象中。从此对象中删除键值对将会导致下一次 require 重新加载被删除的模块
require.main    Module 对象,表示当 Node.js 进程启动时加载的入口脚本
require.resolve(request[, options]) 使用内部的 require() 机制查询模块的位置,此操作只返回解析后的文件名,不会加载该模块
require.resolve.paths(request)      返回一个数组,其中包含解析 request 过程中被查询的路径,如果 request 字符串指向核心模块(例如 http 或 fs)则返回 null
1
文档更新时间: 2020-06-20 16:06   作者:lizw