一、es6Module和CommonJS规范
1) 静态和动态
es6Module是静态模块,可以在编译的时候分析依赖,支持tree-shaking。
CommonJS是动态模块,在代码执行的时候才知道依赖,不支持tree-shaking。
2)esModule输入的是值的引用。CommonJS输出的是值得拷贝。
3)esModule模块中顶层的this指向undefined。CommonJS模块中顶层this指向当前模块。
二、node中的模块
1. 内置模块
1) fs模块
let r = fs.readFileSync('./1.js');
let exists = fs.existsSync('./2.js');
2) path模块
path.resolve(__dirname, 'a.js'); # 当前模块文件被解析过后的所在文件夹的路径(绝对路径)
path.resolve(__filename, 'b.js'); # 当前模块文件被解析过后的文件路径(绝对路径)
path.resolve(__dirname, 'a', 'b', '/'); # c:\ 如果有`/`,会返回根目录。
path.join(__dirname, 'a', 'b', '/'); #/Users/Desktop/demo/a/b/ 路径拼接,遇到`/`也会拼在一起
// 取扩展名
path.extname('a.min.js'); // .js
// 根据后缀取文件名
path.basename('a.js', '.js') // a
// 获取相对路径
path.relative('a/b/c/1.js', 'a'); // ../../..
// 获取目录名
path.dirname('a/b/c'); // a/b
3) vm模块
// new Function() 执行代码
global.a = 100;
new Function('b', 'console.log(a, b)')('b'); // 100 b
// 在当前上下文中执行
const vm = require('vm');
global.a = 100;
vm.runInThisContext('console.log(a)'); // 100
2. 文件模块
文件模块查找规范:
- 默认先查找同名文件,尝试添加.js和.json文件。
- 找不到同名文件,就找同名文件夹(把文件夹当成包)。如果文件夹中有package.json,则根据package.json中的main指定的文件查找。
- 如果没有package.json,在找同名文件夹下的index.js。
3. 第三方模块
第三方模块引用,分为全局模块安装引用和代码中引用。
第三方模块查找规范:
- 默认沿着当前目录向上查找,查找node_modules下的同名文件夹。根据package.json中的main查找,如果找不到,则找index.js。
- 如果没找到,一直向上级的node_modules查找。找到根路径仍旧没找到,说明没有这个包,报错。
三、node中require的实现原理
CommonJS的require的核心思路是:引入进来的模块包装一层自执行函数,实现模块隔离。
看下面的例子,帮助理解怎么包装的:
// a.js
var a = 100;
module.exports = '1';
// b.js
// 引入a.js
let a = require('./a');
// 相当于
let a = (function(exports, module, require, __dirname, __filename) {
var a = 100;
module.exports = '1';
return module.exports;
})(exports, module, require, __dirname, __filename);
实现流程:
Module.resolveFilename根据用户输入的相对引用路径,生成绝对路径。new Module根据文件路径创建模块。module.load调用模块的load事件,加载模块,执行脚本给module.exports赋值。return module.exports返回exports对象, 用户会拿到module.exports的返回结果。cacheModule = Module._cache[filename]根据文件名缓存模块。
// 1.js
var a = 100;
console.log(this === module.exports) // true
module.exports = a;
// require.js
const fs = require('fs');
const path = require('path');
const vm = require('vm');
function Module(id) {
this.id = id;
this.exports = {};
}
Module._cache = [];
Module.prototype.load = function() {
// 获取文件后缀名, 走策略模式
let ext = path.extname(this.id);
Module._extensions[ext](this);
}
// 策略模式,不同后缀
Module._extensions = {
'.js'(module) {
// 读取脚本
let script = fs.readFileSync(module.id, 'utf8');
// 包装成函数,runInThisContext类似于 new Function(),将templateFn字符串转成函数
let templateFn = `(function(exports, module, require, __dirname, __filename){${script}})`;
let fn = vm.runInThisContext(templateFn);
// console.log(fn.toString());
// this = module.exports = exports
let exports = module.exports;
let thisValue = exports;
let filename = module.id;
let dirname = path.dirname(filename);
// 函数调用,调用完后,module.exports = 100
fn.call(thisValue, exports, module, req, dirname, filename);
},
'.json'(module) {
let script = fs.readFileSync(module.id, 'utf8');
module.exports = JSON.parse(script);
}
}
// 把路径变为绝对路径,添加后缀名
Module._resolveFilename = function(id) {
let filePath = path.resolve(__dirname, id);
let isExists = fs.existsSync(filePath);
if (isExists) return filePath;
// 尝试添加后缀
let keys = Reflect.ownKeys(Module._extensions); // ['.js', '.json']
for (let i = 0; i < keys.length; i++) {
let newPath = filePath + keys[i];
if (fs.existsSync(newPath)) return newPath;
}
throw new Error('module not found');
}
function req(filename) {
// 1. 创建一个绝对引用路径,方便后续读取
filename = Module._resolveFilename(filename);
// 缓存中有,直接取缓存
let cacheModule = Module._cache[filename];
if (cacheModule) return cacheModule.exports;
// 2. 根据路径创建一个模块
const module = new Module(filename);
// 根据文件名缓存模块
Module._cache[filename] = module;
// 3. 让用户给module.exports赋值
module.load();
return module.exports;
}
// let a = req('a.json');
let a = req('./1.js');
console.log(a);
四、第三方模块的安装及发布
1)全局安装
全局安装,只能在命令行中使用,全局安装的模块会安装到npm目录下。
自己实现全局安装包,需要:
package.json中配置bin命令。- 添加执行方式
#!/usr/bin/env node。 - 将此包放到npm下,可以全局安装,也可以临时
sudo npm link。
2)代码安装
npm install 模块 --save --save-dev
3)依赖关系
- 开发依赖:--save-dev
- 生产依赖:--save
- 同等依赖:peerDependencies,一个第三方模块依赖于另外一个模块,安装时提示需要你安装同等依赖。
- 可选依赖:optionalDependencies。
- 打包依赖:bundledDependencies。
npm pack打压缩包时,希望把某个包打进去就在这里配。(默认npm pack打压缩包不会打node_modules。)
4)npm run xxx 为什么可以执行?
使用npm run xxx,默认在命令执行之前,会将.bin环境变量添加到全局环境变量下,这时候命令就可以执行了。待命令执行完毕后,会删掉对应的path。
npx和npm run类似,npx如果模块不存在会先安装再使用,使用后可以自动删除。
5)版本管理
版本管理方式:semver (major 、minor 、 patch);
^2.2.4 => 第一位只能是2。
~2.2.4 => 第二位不能低于2,大版本不能超过2版本,即 2.2.x。
`>=` => 大于等于某个版本
`<=` => 小于等于某个版本
指定版本:npm install jquery@2.2.4
指定版本:npm install jquery@2 不低于2的版本
6)包发布
- 切换源
nrm use npm
- 登录npm
npm addUser
- 发布
npm publish
更新版本
npm version major # 大版本号加 1,其余版本号归 0
npm version minor # 小版本号加 1,修订号归 0
npm version patch # 修订号加 1
npm publish