购物网站下载,网站建设培训,长沙网页制作模板,wordpress设置上传文件大小Nodejs 模块化规范遵循两套一 套CommonJS规范另一套esm规范
CommonJS 规范
引入模块#xff08;require#xff09;支持四种格式
支持引入内置模块例如 http os fs child_process 等nodejs内置模块支持引入第三方模块express md5 koa 等支持引入自己编写的模块 ./ …/ 等支…Nodejs 模块化规范遵循两套一 套CommonJS规范另一套esm规范
CommonJS 规范
引入模块require支持四种格式
支持引入内置模块例如 http os fs child_process 等nodejs内置模块支持引入第三方模块express md5 koa 等支持引入自己编写的模块 ./ …/ 等支持引入addon C扩展模块 .node文件
const fs require(node:fs); // 导入核心模块
const express require(express); // 导入 node_modules 目录下的模块
const myModule require(./myModule.js); // 导入相对路径下的模块
const nodeModule require(./myModule.node); // 导入扩展模块导出模块exports 和 module.exports
module.exports {hello: function() {console.log(Hello, world!);}
};如果不想导出对象直接导出值
module.exports 123ESM模块规范
引入模块 import 必须写在头部 注意使用ESM模块的时候必须开启一个选项 打开package.json 设置 type:module import fs from node:fs如果要引入json文件需要特殊处理 需要增加断言并且指定类型json node低版本不支持 import data from ./data.json assert { type: json };
console.log(data);加载模块的整体对象
import * as all from xxx.js动态导入模块
import静态加载不支持掺杂在逻辑中如果想动态加载请使用import函数模式
if(true){import(./test.js).then()
}模块导出
导出一个默认对象 default只能有一个不可重复export default
export default {name: test,
}导出变量
export const a 1Cjs 和 ESM 的区别
Cjs是基于运行时的同步加载esm是基于编译时的异步加载Cjs是可以修改值的esm值并且不可修改可读的Cjs不可以tree shakingesm支持tree shakingcommonjs中顶层的this指向这个模块本身而ES6中顶层this指向undefined
nodejs部分源码解析
.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 {setOwnProperty(module, exports, JSONParse(stripBOM(content)));} catch (err) {err.message filename : err.message;throw err;}
};
使用fs读取json文件读取完成之后是个字符串 然后JSON.parse变成对象返回
.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 doesnt use contentreturn process.dlopen(module, path.toNamespacedPath(filename));
};发现是通过process.dlopen 方法处理.node文件
.js文件如何处理
Module._extensions[.js] function(module, filename) {// If already analyzed the source, then it will be cached.//首先尝试从cjsParseCache中获取已经解析过的模块源代码如果已经缓存则直接使用缓存中的源代码const cached cjsParseCache.get(module);let content;if (cached?.source) {content cached.source; //有缓存就直接用cached.source undefined;} else {content fs.readFileSync(filename, utf8); //否则从文件系统读取源代码}//是不是.js结尾的文件if (StringPrototypeEndsWith(filename, .js)) {//读取package.json文件const pkg readPackageScope(filename);// Function require shouldnt be used in ES modules.//如果package.json文件中有type字段并且type字段的值为module并且你使用了require //则抛出一个错误提示不能在ES模块中使用require函数if (pkg?.data?.type module) {const parent moduleParentCache.get(module);const parentPath parent?.filename;const packageJsonPath path.resolve(pkg.path, package.json);const usesEsm hasEsmSyntax(content);const err new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,packageJsonPath);// Attempt to reconstruct the parent require frame.//如果抛出了错误它还会尝试重构父模块的 require 调用堆栈//以提供更详细的错误信息。它会读取父模块的源代码并根据错误的行号和列号//在源代码中找到相应位置的代码行并将其作为错误信息的一部分展示出来。if (Module._cache[parentPath]) {let parentSource;try {parentSource fs.readFileSync(parentPath, utf8);} catch {// Continue regardless of error.}if (parentSource) {const errLine StringPrototypeSplit(StringPrototypeSlice(err.stack, StringPrototypeIndexOf(err.stack, at )), \n, 1)[0];const { 1: line, 2: col } RegExpPrototypeExec(/(\d):(\d)\)/, errLine) || [];if (line col) {const srcLine StringPrototypeSplit(parentSource, \n)[line - 1];const frame ${parentPath}:${line}\n${srcLine}\n${StringPrototypeRepeat( , col - 1)}^\n;setArrowMessage(err, frame);}}}throw err;}}module._compile(content, filename);
};如果缓存过这个模块就直接从缓存中读取如果没有缓存就从fs读取文件并且判断如果是cjs但是type为module就报错并且从父模块读取详细的行号进行报错如果没问题就调用 compile
Module.prototype._compile function(content, filename) {let moduleURL;let redirects;const manifest policy?.manifest;if (manifest) {moduleURL pathToFileURL(filename);//函数将模块文件名转换为URL格式redirects manifest.getDependencyMapper(moduleURL);//redirects是一个URL映射表用于处理模块依赖关系manifest.assertIntegrity(moduleURL, content); //manifest则是一个安全策略对象用于检测模块的完整性和安全性}/*** filename {string} 文件名* content {string} 文件内容*/const compiledWrapper wrapSafe(filename, content, this);let inspectorWrapper null;if (getOptionValue(--inspect-brk) process._eval null) {if (!resolvedArgv) {// We enter the repl if were not given a filename argument.if (process.argv[1]) {try {resolvedArgv Module._resolveFilename(process.argv[1], null, false);} catch {// We only expect this codepath to be reached in the case of a// preloaded module (it will fail earlier with the main entry)assert(ArrayIsArray(getOptionValue(--require)));}} else {resolvedArgv repl;}}// Set breakpoint on module startif (resolvedArgv !hasPausedEntry filename resolvedArgv) {hasPausedEntry true;inspectorWrapper internalBinding(inspector).callAndPauseOnStart;}}const dirname path.dirname(filename);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;
};首先它检查是否存在安全策略对象 policy.manifest。如果存在表示有安全策略限制需要处理 将函数将模块文件名转换为URL格式redirects是一个URL映射表用于处理模块依赖关系manifest则是一个安全策略对象用于检测模块的完整性和安全性然后调用wrapSafe
function wrapSafe(filename, content, cjsModuleInstance) {if (patched) {const wrapper Module.wrap(content);//支持esm的模块 //import { a } from ./a.js; 类似于eval//import()函数模式动态加载模块const script new Script(wrapper, {filename,lineOffset: 0,importModuleDynamically: async (specifier, _, importAssertions) {const loader asyncESM.esmLoader;return loader.import(specifier, normalizeReferrerURL(filename),importAssertions);},});// Cache the source map for the module if present.if (script.sourceMapURL) {maybeCacheSourceMap(filename, content, this, false, undefined, script.sourceMapURL);}//返回一个可执行的全局上下文函数return script.runInThisContext({displayErrors: true,});}wrapSafe调用了wrap方法
let wrap function(script) {return Module.wrapper[0] script Module.wrapper[1];
};
//(function (exports, require, module, __filename, __dirname) {//const xm 18
//\n});
const wrapper [(function (exports, require, module, __filename, __dirname) { ,\n}),
];wrap方法 发现就是把我们的代码包装到一个函数里面
//(function (exports, require, module, __filename, __dirname) {
//const xm 18 我们的代码
//\n});
然后继续看wrapSafe函数发现把返回的字符串也就是包装之后的代码放入nodejs虚拟机里面Script看有没有动态import去加载最后返回执行后的结果然后继续看_compile获取到wrapSafe返回的函数通过Reflect.apply调用因为要填充五个参数[exports, require, module, filename, dirname],最后返回执行完的结果。