NodeのModuleソース分析
18220 ワード
nodeプロジェクトでは、requireとmodule.exportsの使用が非常に一般的で、jsモジュール化による効率が大幅に向上しました.requireの背後にはどうやって実行されているのか気になりましたが、最近はこの部分のソースコードをよく見て、他の人の文章を参考にしました.幸いなことに、nodeのModuleはJavaScriptで書いてあります.
Common Js仕様
common Js仕様はjsモジュール化の一里塚と言えます.現在npm上のカバンは基本的にこの規格をサポートしています.Common Jsにおいて:ファイルは一つのモジュールであり、単独のスコープを持っています. 一般的に定義された変数、関数、オブジェクトはこのモジュール内に属します. は、requireによってモジュールをロードする. は、exportとmodule.exportsを通じてモジュールの内容を暴露する. 一つ挙げますかa.js
考え方を考える
今はソースコードを見ていません.使用経験によって、requireとmoduleの特徴を整理できます. requireおよびmoduleは、露出されたグローバルオブジェクトである. requireは、1つのパラメータ(path)を受け取り、このパラメータは相対パスであってもよく、モジュールを持参するか、またはpackage.jsonのプラグインであってもよい. このパラメータは、完全なファイル名でない場合、あいまいなマッチングを実現することができる.(拡張子マッチングとパスマッチング) はファイルにマッチングした後、ファイルをコンパイルする. ファイルのexportからの変数は、require方法の戻り値として作用します. これはかなり小さいプロジェクトで、需要は全部並べました.とりあえず自分で実践を急ぎません.条件が足りないので書けません.大神さんのコードはどう書きますか?
ソースの入手
ソースを見る前に、公式文書を準備してください.中には多くの原生アプリが使われています.ModuleソースはNodeプロジェクトの中で、一つだけModuleファイルがあります.全部含まれています.このファイルは他の依存性もいくつか紹介しています.
依存導入
モジュール id''''124124;ファイルの絶対パス exportファイル暴露の変数は、デフォルトは{}です. parentファイルのスケジューラのmodule例 filenameファイルの絶対パス loadedがロードされているかどうか . children呼び出しファイルの関係セット その後、いくつかの変数を初期化しました.以下に使います.
require方法 relove Filenameメソッドは、入力された相対パスまたはパケット名からファイルの絶対パスを算出する. .キャッシュに直接キャッシュ結果があれば、 モジュールを持参した場合、直接結果を返します. そうでなければ、moduleのインスタンスを作成してキャッシュし、ファイルをロードしようと試みた後、ファイルの露出結果を返します. これはrequire方法のワークフローです.ここで重要なポイントはファイルがロードされてキャッシュされた結果です.
この方法で重要な2つのステップ: Module.ursoveFilename()処理ファイルパス module.load()ロードファイル 私たちは一人ずつ見に来ます.
処理パスモジュールの中にあればファイル名に戻ります. は、このファイルの可能なパスを算出する .は、可能な経路の中から真の経路を探し出し、引き返す. がないとエラーが発生します. なぜ可能なパスを算出するかというと、ファイルの出所が多いからです.相対的なパスの下のファイル、システムはモジュールを持っています.また、nodemumodulesの中のカバンかもしれません.第三のnodemumodulesフォルダの位置が確定できないなら、パスの計算をします.
パス計算
パス計算の二つの方法:モジュールを持参する場合、可能な経路は[]です. パスは相対パスではありません.おそらく経路はnode環境の持ち込みパケット(グローバル実装のnpm i XXX-g)です. 调节者がいないと、プロジェクトのnodemoduleのカバンかもしれません. そうでなければ、使用者の経路から絶対パスを算出します. 二つ目の方法はModule.unode ModulePathsと推測項目の中のnodemoodulesフォルダのパスです.
この計算方法はとても面白いです.多く見てみる価値があります.
正確にマッチ
その後、可能なすべての経路において、唯一の結果を見つけるにはどうすればいいですか?ファイルの拡張子があいまいです.js、json、node 現在のパスがキャッシュにあるなら、そのままキャッシュに戻ります. はすべてのパスを順次巡回します. ファイルがパスと直接一致しているかどうか パスと拡張子名が に一致するかどうかは、package.jsonのカバンかどうか です.ディレクトリ名+index+拡張子名 が存在するかどうかは、見つかったファイルパスをキャッシュに戻し、 に戻る.そうでなければ、404 上の処理によって、ロードが必要なファイルの絶対パスを計算できます.次はこのファイルをコンパイルすればいいです.
ファイルをコンパイルする
module.loadという方法に来ました.
その中のjsとjsonのファイルの処理方法は注意深く見ます.
ファイルエンコード処理
なぜヘッダ情報を処理しますか?ここを見てください.
サンドボックスのコンパイル
ok、ここに準備して完成しました.本当のコンパイルの一環になります.
初期化
最後に現在のモジュールを初期化します.
Moduleモジュールの特徴
Moduleモジュールの特徴を整理します.は、comonjs仕様ではモジュールごとにModuleの例です. requireメソッドを呼び出してモジュールファイルをロードします. .ファイルの絶対パスを解析する. .レノベLookupaths解析ファイルケンの絶対パス _findPathマッチングは、ファイルの絶対パス を見つけることを試みる.
ロード解析ファイル Module.uextension[extension]は、拡張機能が異なる場合は、ロードを試みることができます. _comple沙箱コンパイル requireの戻り値はmodule.exportian{}; である.
使用上の注意点
そして、ソースの読み取りを通して、私もいくつかの点に気づいていませんでした. Moduleはグローバルオブジェクトです.requireは違います. require方法によって許容される経路は、次のようにすることができる. システムのモジュール(fs、path) グローバル実装モジュール プロジェクトのインストールモジュール は、モジュールファイル に相対/絶対パスを介してマッチングすることができる.パスファイルは、拡張子を書かなくてもいいです.デフォルトサポート(.js,json,.node) パスはindex(./components=]./components/index) を書かなくてもいいです.
は、サンドボックスのコンパイルによってモジュール化され、リキッドとモディはサンドボックスに注入されたオブジェクトである. モジュールをロードするとキャッシュされます.二次コンパイルはできません. requireの戻り値はmodule.eports{}である. export=module.exports; 最後に私はソースに対して中国語の注釈を行いました.
参照 require()ソースコードの解読 NodeJSモジュールロード方法requireソース分析 ソースコード解析Node.jsの中の一つのファイルがrequireされた後に発生した物語 【NodeJS】浅分析負荷モジュールの機構
Common Js仕様
common Js仕様はjsモジュール化の一里塚と言えます.現在npm上のカバンは基本的にこの規格をサポートしています.Common Jsにおいて:
var aParam = 23;
exports.value = aParam;
module.exports = {
calculate: function(param){
return aParam + param;
},
getA: function(){
return aParam;
},
value: aParam
};
入り口のファイル:index.jsvar a = require('./a');
console.log(a);
// {calculate: [Function], getA: [Function], value: 23}
console.log(a.calculate(2));
// 25
console.log(a.value);
// 23
ok簡単な例です.ファイルの実行結果としてexportsから出てくる変数が、require方法によってjsファイルをロードできることを知りました.考え方を考える
今はソースコードを見ていません.使用経験によって、requireとmoduleの特徴を整理できます.
ソースの入手
ソースを見る前に、公式文書を準備してください.中には多くの原生アプリが使われています.ModuleソースはNodeプロジェクトの中で、一つだけModuleファイルがあります.全部含まれています.このファイルは他の依存性もいくつか紹介しています.
依存導入
//
var NativeModule = require('native_module');
var util = require('util');
// vm
var runInThisContext = require('vm').runInThisContext;
var runInNewContext = require('vm').runInNewContext;
var assert = require('assert').ok;
var fs = require('fs');
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
ファイルの先頭にはツールやメソッドが紹介されていますが、ここのファイルの導入は直接にrequireキーワードを使って導入されています.モジュール
/**
* Module module
* @param id
* @param parent module
* @constructor
*/
function Module(id, parent) {
this.id = id; // ,
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false;
this.children = [];
}
module.exports = Module;
//
Module._contextLoad = (+process.env['NODE_MODULE_CONTEXTS'] > 0);
Module._cache = {};
Module._pathCache = {};
Module._extensions = {};
// node_module
var modulePaths = [];
Module.globalPaths = [];
Module.wrapper = NativeModule.wrapper;
Module.wrap = NativeModule.wrap;
var path = require('path');
そしてModule方法を定義し、Moduleは工場方法であり、moduleは工場の例である.Moduleの主な属性:(ロードされたファイルはここでファイルと略称する)require方法
/**
* require
* @param path
*/
Module.prototype.require = function(path) {
return Module._load(path, this);
};
require方法は実際にModuleのプライベートメソッドを呼び出したものです./**
*
* @param request
* @param parent
* @param isMain
* @private
*/
Module._load = function(request, parent, isMain) {
// filename
var filename = Module._resolveFilename(request, parent);
// Module
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
//
if (NativeModule.exists(filename)) {
// repl
if (filename == 'repl') {
//
var replModule = new Module('repl');
replModule._compile(NativeModule.getSource('repl'), 'repl.js');
NativeModule._cache.repl = replModule;
return replModule.exports;
}
//
return NativeModule.require(filename);
}
//
var module = new Module(filename, parent);
//
if (isMain) {
process.mainModule = module;
module.id = '.';
}
//
Module._cache[filename] = module;
// module, module
var hadException = true;
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}
//
return module.exports;
};
_.ロード方法はファイルの主な流れをロードする方法です.この方法で重要な2つのステップ:
処理パス
/**
* (filename)
* @param request
* @param parent Module
* @returns filename
* @private
*/
Module._resolveFilename = function(request, parent) {
// filename request
if (NativeModule.exists(request)) {
return request;
}
// request
var resolvedModule = Module._resolveLookupPaths(request, parent);
var id = resolvedModule[0];
var paths = resolvedModule[1];
var filename = Module._findPath(request, paths);
if (!filename) {
var err = new Error("Cannot find module '" + request + "'");
err.code = 'MODULE_NOT_FOUND';
throw err;
}
//
return filename;
};
この方法の主な流れ:パス計算
パス計算の二つの方法:
/**
*
* @param request
* @param parent Module
* @returns [request, paths] [ , (Array)]
* @private
*/
Module._resolveLookupPaths = function(request, parent) {
// request
if (NativeModule.exists(request)) {
return [request, []];
}
// ( )
var start = request.substring(0, 2);
if (start !== './' && start !== '..') {
var paths = modulePaths;
if (parent) {
if (!parent.paths) parent.paths = [];
paths = parent.paths.concat(paths);
}
return [request, paths];
}
//
if (!parent || !parent.id || !parent.filename) {
var mainPaths = ['.'].concat(modulePaths);
mainPaths = Module._nodeModulePaths('.').concat(mainPaths);
return [request, mainPaths];
}
// index
var isIndex = /^index\.\w+?$/.test(path.basename(parent.filename));
// (parent)
var parentIdPath = isIndex ? parent.id : path.dirname(parent.id);
var id = path.resolve(parentIdPath, request);
if (parentIdPath === '.' && id.indexOf('/') === -1) {
id = './' + id;
}
return [id, [path.dirname(parent.filename)]];
};
/**
* node_modules
* @param from
* @returns [Array]
* @private
*/
Module._nodeModulePaths = function(from) {
// from
from = path.resolve(from);
//
var splitRe = process.platform === 'win32' ? /[\/\\]/ : /\//;
var paths = [];
var parts = from.split(splitRe);
/**
*
* form :
* /Users/aus/Documents/node
* node_module :
* /Users/aus/Documents/node/node_modules
* /Users/aus/Documents/node_modules
* /Users/aus/node_modules
* /Users/node_modules
* /node_modules
*/
for (var tip = parts.length - 1; tip >= 0; tip--) {
// don't search in .../node_modules/node_modules
if (parts[tip] === 'node_modules') continue;
var dir = parts.slice(0, tip + 1).concat('node_modules').join(path.sep);
paths.push(dir);
}
return paths;
};
この中でどうやって可能なパスを計算しますか?この計算方法はとても面白いです.多く見てみる価値があります.
正確にマッチ
その後、可能なすべての経路において、唯一の結果を見つけるにはどうすればいいですか?
/**
*
* @param request
* @param paths
* @returns filename
* @private
*/
Module._findPath = function(request, paths) {
var exts = Object.keys(Module._extensions);
// ,
if (request.charAt(0) === '/') {
paths = [''];
}
//
var trailingSlash = (request.slice(-1) === '/');
// ,
var cacheKey = JSON.stringify({request: request, paths: paths});
if (Module._pathCache[cacheKey]) {
return Module._pathCache[cacheKey];
}
// For each path
for (var i = 0, PL = paths.length; i < PL; i++) {
var basePath = path.resolve(paths[i], request);
var filename;
if (!trailingSlash) {
// try to join the request to the path
filename = tryFile(basePath);
if (!filename && !trailingSlash) {
// try it with each of the extensions
filename = tryExtensions(basePath, exts);
}
}
// package.json
if (!filename) {
filename = tryPackage(basePath, exts);
}
// + index +
if (!filename) {
// try it with each of the extensions at "index"
filename = tryExtensions(path.resolve(basePath, 'index'), exts);
}
//
if (filename) {
Module._pathCache[cacheKey] = filename;
return filename;
}
}
// 404
return false;
};
正確なパスマッチングの過程は比較的簡単で、すべてのテストです.ファイルをコンパイルする
module.loadという方法に来ました.
/**
*
* @param filename
*/
Module.prototype.load = function(filename) {
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
//
this.loaded = true;
};
// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename);
};
// Native extension for .json
Module._extensions['.json'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
try {
module.exports = JSON.parse(stripBOM(content));
} catch (err) {
err.message = filename + ': ' + err.message;
throw err;
}
};
//Native extension for .node
Module._extensions['.node'] = process.dlopen;
ファイルを見つけたら、ファイルの拡張子によって処理します.その中のjsとjsonのファイルの処理方法は注意深く見ます.
ファイルエンコード処理
/**
* utf8 BOM
* @param content
* @returns content
*/
function stripBOM(content) {
// Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
// because the buffer-to-string conversion in `fs.readFileSync()`
// translates it to FEFF, the UTF-16 BOM.
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
return content;
}
ファイルを取得して、ファイルをバイナリストリームに変換してヘッダ情報を処理します.なぜヘッダ情報を処理しますか?ここを見てください.
サンドボックスのコンパイル
ok、ここに準備して完成しました.本当のコンパイルの一環になります.
/**
*
* @param content
* @param filename
* @returns {*}
* @private
*/
Module.prototype._compile = function(content, filename) {
var self = this;
// remove shebang
content = content.replace(/^\#\!.*/, '');
function require(path) {
return self.require(path);
}
require.resolve = function(request) {
return Module._resolveFilename(request, self);
};
Object.defineProperty(require, 'paths', { get: function() {
throw new Error('require.paths is removed. Use ' +
'node_modules folders, or the NODE_PATH ' +
'environment variable instead.');
}});
require.main = process.mainModule;
// Enable support to add extra extension types
require.extensions = Module._extensions;
require.registerExtension = function() {
throw new Error('require.registerExtension() removed. Use ' +
'require.extensions instead.');
};
require.cache = Module._cache;
var dirname = path.dirname(filename);
if (Module._contextLoad) {
if (self.id !== '.') {
debug('load submodule');
// not root module
var sandbox = {};
for (var k in global) {
sandbox[k] = global[k];
}
sandbox.require = require;
sandbox.exports = self.exports;
sandbox.__filename = filename;
sandbox.__dirname = dirname;
sandbox.module = self;
sandbox.global = sandbox;
sandbox.root = root;
return runInNewContext(content, sandbox, { filename: filename });
}
debug('load root module');
// root module
global.require = require;
global.exports = self.exports;
global.__filename = filename;
global.__dirname = dirname;
global.module = self;
return runInThisContext(content, { filename: filename });
}
// create wrapper function
var wrapper = Module.wrap(content);
var compiledWrapper = runInThisContext(wrapper, { filename: filename });
if (global.v8debug) {
if (!resolvedArgv) {
// we enter the repl if we're not given a filename argument.
if (process.argv[1]) {
resolvedArgv = Module._resolveFilename(process.argv[1], null);
} else {
resolvedArgv = 'repl';
}
}
// Set breakpoint on module start
if (filename === resolvedArgv) {
global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0);
}
}
var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);
};
ここでコンパイルするのは主にサンドボックスのコンパイルです.ファイルの内容はクローズドボックスに埋め込まれています.箱の中の運行結果は外部環境に影響を与えません.module.export通信を通じて、例えば:(function (exports, require, module, __filename, __dirname) {
//
});
このように、jsファイルでは、requireとmoduleは、実際のグローバル変数ではなく、注入された変数であることが分かりました.このように砂の箱を閉じてモジュール化されています.初期化
最後に現在のモジュールを初期化します.
// bootstrap main module.
Module.runMain = function() {
// Load the main module--the command line argument.
Module._load(process.argv[1], null, true);
// Handle any nextTicks added in the first tick of the program
process._tickCallback();
};
/**
* node
* @private
*/
Module._initPaths = function() {
var isWindows = process.platform === 'win32';
if (isWindows) {
var homeDir = process.env.USERPROFILE;
} else {
var homeDir = process.env.HOME;
}
var paths = [path.resolve(process.execPath, '..', '..', 'lib', 'node')];
if (homeDir) {
paths.unshift(path.resolve(homeDir, '.node_libraries'));
paths.unshift(path.resolve(homeDir, '.node_modules'));
}
var nodePath = process.env['NODE_PATH'];
if (nodePath) {
paths = nodePath.split(path.delimiter).concat(paths);
}
modulePaths = paths;
// clone as a read-only copy, for introspection.
Module.globalPaths = modulePaths.slice(0);
};
// bootstrap repl
Module.requireRepl = function() {
return Module._load('repl', '.');
};
Module._initPaths();
// backwards compatibility
Module.Module = Module;
締め括りをつけるModuleモジュールの特徴
Moduleモジュールの特徴を整理します.
使用上の注意点
そして、ソースの読み取りを通して、私もいくつかの点に気づいていませんでした.
参照