用户模块
用户模块是node开发者编写的javaScript文件或者json文件。它以文件的形式去组织。通过require()
函数去获取其他模块的导出对象。和通过exports
对象或者module.exports
对象导出函数或者变量给其它模块使用。在编写node代码的时候。开发者并不需要去处理模块加载的事情。这些node的模块化系统已经为我们处理好。
const http = require("http");
const utisl = require('./utils');
const app = http.createServer();
app.on("request", (req, res) => {
res.end(utils.getResponse(req,res));
});
app.listen(4000);
当我们以上面的例子作为启动模块。模块名为man.js。下面是工程目录。在工程目录路径下执行 node main.js
|- project |- nodele_module |- package.json |- main.js |- utils.js
在上面node的启动流程我们知道。node通过internal\modules\run_main.js
的executeUserEntryPoint
启动用户主模块。在这里我们只讨论commonjs的启动方式。
function executeUserEntryPoint(main = process.argv[1]) {
const resolvedMain = resolveMainPath(main);
const useESMLoader = shouldUseESMLoader(resolvedMain);
if (useESMLoader) {
runMainESM(resolvedMain || main);
} else {
// 启动主模块
Module._load(main, null, true);
}
}
通过 Module._load(main, null, true);
启动了用户主模块。Module对象的定义在 internal\modules\cjs\loader.js
下。
function Module(id = '', parent) {
this.id = id;
this.path = path.dirname(id);
this.exports = {};
// 设置更新夫模块引用
moduleParentCache.set(this, parent);
// 更新主模块引用
updateChildren(parent, this, false);
this.filename = null;
this.loaded = false;
this.children = [];
}
Module为一个构造函数。_load()
在后面进行动态挂载。_load()
函数有三个参数。(先吐槽一下,node的这样设计,真的太他妈恶心了)
/*
* @params request{string}模块的绝对路地址。
* @params parent{Object}父模块,为当前Module构造函数的实例。在加载主模块的时候为null,因为主模块没有夫模块。
* @params isMain{Boolean} 是否为主模块。在加载主模块的时候为true
*/
Module._load = function(request, parent, isMain) {
let relResolveCacheIdentifier;
// 模块是否存在夫模块
if (parent) {
relResolveCacheIdentifier = `${parent.path}\x00${request}`;
const filename = relativeResolveCache[relResolveCacheIdentifier];
if (filename !== undefined) {
const cachedModule = Module._cache[filename];
if (cachedModule !== undefined) {
updateChildren(parent, cachedModule, true);
if (!cachedModule.loaded)
return getExportsForCircularRequire(cachedModule);
return cachedModule.exports;
}
delete relativeResolveCache[relResolveCacheIdentifier];
}
}
// 获取模块的名称。当在以目录作为启动模块,node会默认以该目录下的index.js作为模块加载
const filename = Module._resolveFilename(request, parent, isMain);
// 判断文件名是否以node:开头。在内建javaScript的javaScript模块中,最终的内建模块被重新命名为例如fs的的别名node:fs
// 所以我们能通过Module._load("node:fs")的方式去价值内建javaScript模块
if (StringPrototypeStartsWith(filename, 'node:')) {
// 获取内建模块的id例如 node:fs。获取fs
const id = StringPrototypeSlice(filename, 5);
// 加载内建javaScript模块
const module = loadNativeModule(id, request);
// 如果模块没有找到,或者改模块是一个不能被用户模块引用的模块。就抛出异常。
if (!module?.canBeRequiredByUsers) {
throw new ERR_UNKNOWN_BUILTIN_MODULE(filename);
}
return module.exports;
}
// 通过文件名作为key, 模块的导出对象 exports作为value缓存在Module构造函数的_cache对象上。
const cachedModule = Module._cache[filename];
// 如果有缓存
if (cachedModule !== undefined) {
// 更新子模块的引用
updateChildren(parent, cachedModule, true);
if (!cachedModule.loaded) {
const parseCachedModule = cjsParseCache.get(cachedModule);
if (!parseCachedModule || parseCachedModule.loaded)
return getExportsForCircularRequire(cachedModule);
parseCachedModule.loaded = true;
} else {
return cachedModule.exports;
}
}
// 获取 内建javaScript模块
const mod = loadNativeModule(filename, request);
// 判断模块是否能做在用户模块中引用
if (mod?.canBeRequiredByUsers) return mod.exports;
// 构建一个模块对象
const module = cachedModule || new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
// 缓存起来
Module._cache[filename] = module;
if (parent !== undefined) {
relativeResolveCache[relResolveCacheIdentifier] = filename;
}
let threw = true;
try {
if (getSourceMapsEnabled()) {
try {
// 加载主模块
module.load(filename);
} catch (err) {
rekeySourceMap(Module._cache[filename], err);
throw err; /* node-do-not-add-exception-line */
}
} else {
module.load(filename);
}
threw = false;
} finally {
if (threw) {
delete Module._cache[filename];
if (parent !== undefined) {
delete relativeResolveCache[relResolveCacheIdentifier];
const children = parent?.children;
if (ArrayIsArray(children)) {
const index = ArrayPrototypeIndexOf(children, module);
if (index !== -1) {
ArrayPrototypeSplice(children, index, 1);
}
}
}
} else if (module.exports &&
!isProxy(module.exports) &&
ObjectGetPrototypeOf(module.exports) ===
CircularRequirePrototypeWarningProxy) {
ObjectSetPrototypeOf(module.exports, ObjectPrototype);
}
}
return module.exports;
};
// 执行模块加载的真正逻辑
Module.prototype.load = function(filename) {
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
// 获取模块的扩展处理函数
const extension = findLongestRegisteredExtension(filename);
// 如果模块以mjs为后缀并且没有mjs的处理函数
if (StringPrototypeEndsWith(filename, '.mjs') && !Module._extensions['.mjs'])
throw new ERR_REQUIRE_ESM(filename);
// 调用模块的处理函数。在这里为Module._extensions[js]函数
Module._extensions[extension](this, filename);
this.loaded = true;
const ESMLoader = asyncESM.ESMLoader;
const exports = this.exports;
if ((module?.module === undefined ||
module.module.getStatus() < kEvaluated) &&
!ESMLoader.cjsCache.has(this))
ESMLoader.cjsCache.set(this, exports);
};
Module._extensions['.js'] = function(module, filename) {
// 如果文件以js为后缀,并却package.json文件中存在tye = "module"将抛出异常
if (StringPrototypeEndsWith(filename, '.js')) {
const pkg = readPackageScope(filename);
if (pkg?.data?.type === 'module') {
const parent = moduleParentCache.get(module);
const parentPath = parent?.filename;
const packageJsonPath = path.resolve(pkg.path, 'package.json');
throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
}
}
// 获取文件的内容缓存
const cached = cjsParseCache.get(module);
let content;
// 如果没有缓存
if (cached?.source) {
content = cached.source;
cached.source = undefined;
} else {
// 使用fs模块同步读取模块
content = fs.readFileSync(filename, 'utf8');
}
// 最终交给module._compile函数编译执行
module._compile(content, filename);
};
// 处理json文件
Module._extensions['.json'] = function(module, filename) {
const content = fs.readFileSync(filename, 'utf8');
if (policy?.manifest) {
const moduleURL = pathToFileURL(filename);
policy.manifest.assertIntegrity(moduleURL, content);
}
try {
module.exports = JSONParse(stripBOM(content));
} catch (err) {
err.message = filename + ': ' + err.message;
throw err;
}
};
//处理.node文件
Module._extensions['.node'] = function(module, filename) {
if (policy?.manifest) {
const content = fs.readFileSync(filename);
const moduleURL = pathToFileURL(filename);
policy.manifest.assertIntegrity(moduleURL, content);
}
// Be aware this doesn't use `content`
return process.dlopen(module, path.toNamespacedPath(filename));
};
Module.prototype._compile = function(content, filename) {
let moduleURL;
let redirects;
// node的安全策略,这里不做解析
if (policy?.manifest) {
moduleURL = pathToFileURL(filename);
redirects = policy.manifest.getDependencyMapper(moduleURL);
policy.manifest.assertIntegrity(moduleURL, content);
}
maybeCacheSourceMap(filename, content, this);
// 包装成一个函数,解析在下面
const compiledWrapper = wrapSafe(filename, content, this);
let inspectorWrapper = null;
// 断点调式相关,这里不做解析
if (getOptionValue('--inspect-brk') && process._eval == null) {
if (!resolvedArgv) {
if (process.argv[1]) {
try {
resolvedArgv = Module._resolveFilename(process.argv[1], null, false);
} catch {
assert(ArrayIsArray(getOptionValue('--require')));
}
} else {
resolvedArgv = 'repl';
}
}
if (resolvedArgv && !hasPausedEntry && filename === resolvedArgv) {
hasPausedEntry = true;
inspectorWrapper = internalBinding('inspector').callAndPauseOnStart;
}
}
// 获取文件的目录
const dirname = path.dirname(filename);
// 构建一个require函数
const require = makeRequireFunction(this, redirects);
let result;
const exports = this.exports;
const thisValue = exports;
const module = this;
if (requireDepth === 0) statCache = new SafeMap();
if (inspectorWrapper) {
result = inspectorWrapper(compiledWrapper, thisValue, exports,
require, module, filename, dirname);
} else {
// 执行模块
result = ReflectApply(compiledWrapper, thisValue,
[exports, require, module, filename, dirname]);
}
hasLoadedAnyUserCJSModule = true;
if (requireDepth === 0) statCache = null;
return result;
}
// 对文件的内容进行一层包装
function wrapSafe(filename, content, cjsModuleInstance) {
if (patched) {
// 进行模块的包装
const wrapper = Module.wrap(content);
return vm.runInThisContext(wrapper, {
filename,
lineOffset: 0,
displayErrors: true,
importModuleDynamically: async (specifier) => {
const loader = asyncESM.ESMLoader;
return loader.import(specifier, normalizeReferrerURL(filename));
},
});
}
try {
return vm.compileFunction(content, [
'exports',
'require',
'module',
'__filename',
'__dirname',
], {
filename,
importModuleDynamically(specifier) {
const loader = asyncESM.ESMLoader;
return loader.import(specifier, normalizeReferrerURL(filename));
},
});
} catch (err) {
if (process.mainModule === cjsModuleInstance)
enrichCJSError(err);
throw err;
}
}
let patched = false;
// eslint-disable-next-line func-style
let wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
const wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});',
];
let wrapperProxy = new Proxy(wrapper, {
set(target, property, value, receiver) {
patched = true;
return ReflectSet(target, property, value, receiver);
},
defineProperty(target, property, descriptor) {
patched = true;
return ObjectDefineProperty(target, property, descriptor);
}
});
ObjectDefineProperty(Module, 'wrap', {
get() {
return wrap;
},
set(value) {
patched = true;
wrap = value;
}
});
ObjectDefineProperty(Module, 'wrapper', {
get() {
return wrapperProxy;
},
set(value) {
patched = true;
wrapperProxy = value;
}
});
在Module.prototype._compile
通过makeRequireFunction(this, redirects);
创建了一个require函数。makeRequireFunction
函数位于internal\modules\cjs\helpers.js文件内。
function makeRequireFunction(mod, redirects) {
const Module = mod.constructor;
let require;
// 是否直接获取。例如可以require("node:fs")
if (redirects) {
const id = mod.filename || mod.id;
const conditions = cjsConditions;
const { resolve, reaction } = redirects;
require = function require(specifier) {
let missing = true;
const destination = resolve(specifier, conditions);
if (destination === true) {
missing = false;
} else if (destination) {
const href = destination.href;
if (destination.protocol === 'node:') {
const specifier = destination.pathname;
const mod = loadNativeModule(specifier, href);
if (mod && mod.canBeRequiredByUsers) {
return mod.exports;
}
throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier);
} else if (destination.protocol === 'file:') {
let filepath;
if (urlToFileCache.has(href)) {
filepath = urlToFileCache.get(href);
} else {
filepath = fileURLToPath(destination);
urlToFileCache.set(href, filepath);
}
return mod.require(filepath);
}
}
if (missing) {
reaction(new ERR_MANIFEST_DEPENDENCY_MISSING(
id,
specifier,
ArrayPrototypeJoin([...conditions], ', ')
));
}
return mod.require(specifier);
};
} else {
require = function require(path) {
return mod.require(path);
};
}
function resolve(request, options) {
validateString(request, 'request');
return Module._resolveFilename(request, mod, false, options);
}
require.resolve = resolve;
function paths(request) {
validateString(request, 'request');
return Module._resolveLookupPaths(request, mod);
}
resolve.paths = paths;
require.main = process.mainModule;
require.extensions = Module._extensions;
require.cache = Module._cache;
return require;
}
所以一个完整的用户模块的加载流程为。