menu:
- 模块化是什么
- 模块化解决的问题
- NodeJS 模块化
- 模块加载顺序
- 调试 CommonJS
- 实现 CommonJS 规范?
模块化是什么?
模块化是一种将事务按照规则拆分为多个独立模块, 并在需要时灵活组合的设计思想 每个模块都是独立存在的, 并不依赖于其他模块
模块化解决什么问题?
模块化主要解决一下问题: 分享, 命名冲突, 复用, 按需加载
- 方便团队分享代码
- 解决命名冲突
- 方便代码 按需引入 并 复用 模块规范主要有:
- AMD(Asynchronous Module Definition 异步模块定义 适用于 浏览器)
- CMD(Common Module Definition 公共模块定义)
- CommonJS(服务端模块规范)
- UMD(Universal Module Definition 通用模块定义, 适用于 浏览器与 NodeJS)
- ESModule(ESM)
NodeJS 模块化
NodeJS 的模块化主要是用 CommonJS 规范, 可以发现 NodeJS 的模块化规范渐渐向前端靠拢因为 NodeJS 在 package.json 中对 type: "module" 即可以使用 ESM 模块规范 同时很多库也开始使用 ESM 来替代 CommonJS 模块规范.虽然已经有趋势向ESM模块靠拢, 但现在主要使用的还是 CommonJS
每个模块都是一个模块, NodeJS 使用 module.exports || exports 来进行导出, 使用 require 进行导入模块
const fs = require('fs');
module.exports = () => {
console.log('module');
}
模块加载顺序
模块加载顺序如下: 内置模块优先 -> 第三方模块/项目依赖, 自定义模块(因为自定义模块一般使用相对路径进行加载) 自定义模块加载规则:
- 会添加
.js|.json|.node后缀 - 高版本 Node 中 文件夹与文件重名 优先查找 文件
- 高版本 Node 中 先查找
package.json中的 main 字段, 若是没有 main 字段则查找 index.js 我们总结一下: 内置模块 | 自定义模块 > 第三方模块
调试 原生 Module
准备工具:
- NodeJS 编译环境
- VSCode
- 两个 JS 文件
创建两个文件
// a.js
module.exports = 123;
// index.js
const a = require('./a');
console.log(a); // 123
添加调试文件
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [],
// 假设文件目录: D:\\article\\node\\module\\index.js, ${workspaceFolder} 就是 D:\\article
"program": "${workspaceFolder}\\node\\module\\index.js" // 需要改变为自己的文件夹
}
]
}
点击紫色的位置, 即可添加断点
进行断点
进入函数
生成 require 函数 将 path 传入
验证路径
查看是否有缓存并加载模块, 若没有缓存则进行缓存
初始化paths并获取扩展名称, 在通过 策略模式 处理不同类型文件
处理
.js 文件 并 执行 JS 文件
处理完JS 文件后将
module.exports 抛出
require 导入文件步骤总结
- 生成 require 函数
- 获取 引入文件的 绝对路径
- 查看是否有模块缓存
- 加载模块
- 初始化文件名称与 paths
- 获取扩展名称(.js/.json/.node)
- 使用策略模式加载文件
- 设置模块已经加载过 并 缓存
- 获取 exports
- 将 module.exports 抛出
实现 CommonJS 规范?
// 1. 生成 require 函数
// 2. 获取 引入文件的 绝对路径
// 3. 查看是否有模块缓存
// 4. 加载模块
// 5. 初始化文件名称与 paths
// 6. 获取扩展名称(.js/.json/.node)
// 7. 使用策略模式加载文件
// 8. 设置模块已经加载过 并 缓存
// 9. 获取 exports
// 10. 将 module.exports 抛出
const path = require('path');
const fs = require('fs');
const vm = require('vm');
// 判断是否以 某个 字符串开头
function validateStartString(start, str) {
return str.startsWith(start);
}
// 裁剪开头
function stringSplice(id, len) {
return id.slice(len);
}
// 验证字符串
function validateString(id, name) {
if (typeof id !== 'string') {
throw new TypeError(`${name}: ${id} type isn\'t string`);
}
}
// 创建 require
const moduleRequire = markRequireFunction();
// 生成 requrie
function markRequireFunction() {
return function require(id) {
return Module.require(id);
}
}
const modules = {};
function Module(id) {
this.id = id;
this.exports = {};
this.load = false;
}
// 策略模式解决 对不同文件处理
Module._extensions = {};
Module._extensions['.js'] = function (module, filename) {
const exports = module.exports,
thisArgs = exports,
_filename = module.id,
_dirname = filename;
const jsCode = fs.readFileSync(filename, 'utf-8');
const args = [
'exports', 'require', 'module', '__filename', '__dirname'
];
const anonymous = vm.compileFunction(jsCode, args);
Reflect.apply(anonymous, thisArgs, [
exports, moduleRequire, module, _filename, _dirname
]);
// 加载完毕
this.load = true;
return module.exports;
}
Module._extensions['.json'] = function (module, filename) {
const jsonCode = fs.readFileSync(filename, 'utf-8');
module.load = true;
module.exports = JSON.parse(jsonCode);
}
// .node 为 C++ 编写的扩展模块, 所以我们不进行处理
Module._extensions['.node'] = function () {
const nodeFile = fs.readFileSync(filename, 'utf-8');
module.load = true;
module.exports = nodeFile;
}
// 加载模块
Module.prototype.loaded = function (filename) {
// 判断文件类型
const _filename = Module._resolveFile(filename);
if (!_filename) {
throw new Error('Cannot find module ' + this.id);
}
const ext = path.extname(_filename);
Module._extensions[ext](this, _filename);
// 返回最终结果
return this.exports;
}
Module._resolveFile = function (filename) {
// 文件存在则直接返回
if (fs.existsSync(filename)) {
return filename;
}
// ['.js', '.json', '.node']
const exts = Reflect.ownKeys(Module._extensions);
for (let i = 0; i < exts.length; i++) {
const ext = exts[i];
const filenamePath = `${filename}${ext}`;
if (fs.existsSync(filenamePath)) {
return filenamePath;
}
}
}
// 引入模块
Module.require = function (id) {
// 验证是否是字符串
validateString(id, 'id');
return Module._load(id, this);
}
// 加载模块
Module._load = function (id, module) {
if (validateStartString('node:', id)) {
id = stringSplice(id, 5);
}
// 获取绝对路径
const filename = path.resolve(path.dirname(id), id);
const cacheModules = modules[filename];
// 查看是否有模块缓存
if (cacheModules && cacheModules.load) {
return cacheModules.exports;
}
// 创建模块
const cacheRequire = new Module(id);
// 缓存
modules[filename] = cacheRequire;
// 加载模块
return cacheRequire.loaded(filename);
}
module.exports = moduleRequire;
总结
- 模块化是什么: 模块化就是对一个事务进行拆分, 拆分为 若干个模块, 待需要使用时在进行组装
- 模块化解决的问题: 代码重名, 代码共享, 按需引入
- NodeJS 模块化: 使用 CommonJS 规范 同时NodeJS也向ESModule靠拢
- 模块加载顺序: 查找
.js/.json/.node若是没有则查找文件夹 在按照查找文件规则进行查找, 若还没有找到则按照 package.json 文件中配置的main/exports字段进行查找 - 模块加载步骤: 创建 reqiure -> 获取文件绝对路径 -> 查看是否有缓存 -> 初始化文件名称与 paths -> 策略模式加载文件 -> 将以加载过的模块缓存 -> 导出 exports