Skip to content

Instantly share code, notes, and snippets.

@sota1235
Last active June 18, 2016 09:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sota1235/12f41aad0b6682751a5c945a06876488 to your computer and use it in GitHub Desktop.
Save sota1235/12f41aad0b6682751a5c945a06876488 to your computer and use it in GitHub Desktop.
proxyquire.jsを理解するためにコピペ&コメント加えて理解する
'use strict';
/*jshint laxbreak:true, loopfunc:true*/
// このコードはこちらのコードをコピペし、コメントを追記したものです
// https://github.com/thlorenz/proxyquire/blob/master/lib/proxyquire.js
var path = require('path')
, Module = require('module')
, resolve = require('resolve')
, dirname = require('path').dirname
, ProxyquireError = require('./proxyquire-error')
, is = require('./is')
, assert = require('assert')
, fillMissingKeys = require('fill-keys')
, moduleNotFoundError = require('module-not-found-error')
, hasOwnProperty = Object.prototype.hasOwnProperty
;
// 関数に渡される引数をバリデーションしてるだけ
function validateArguments(request, stubs) {
var msg = (function getMessage() {
if (!request)
return 'Missing argument: "request". Need it to resolve desired module.';
if (!stubs)
return 'Missing argument: "stubs". If no stubbing is needed, use regular require instead.';
if (!is.String(request))
return 'Invalid argument: "request". Needs to be a requirable string that is the module to load.';
if (!is.Object(stubs))
return 'Invalid argument: "stubs". Needs to be an object containing overrides e.g., {"path": { extname: function () { ... } } }.';
})();
if (msg) throw new ProxyquireError(msg);
}
function Proxyquire(parent) {
var self = this
, fn = self.load.bind(self) // this.load関数をthis.fnに割当
, proto = Proxyquire.prototype // prototypeのalias
;
this._parent = parent; // parentに来るのが何なのかピンと来てない
this._preserveCache = true;
Object.keys(proto)
.forEach(function (key) {
if (is.Function(proto[key])) fn[key] = proto[key].bind(self); // 全ての自関数のthisをconstructorにbind
});
self.fn = fn;
return fn; // this.loadを返してる…?
}
/**
* Disables call thru, which determines if keys of original modules will be used
* when they weren't stubbed out.
* @name noCallThru
* @function
* @private
* @return {object} The proxyquire function to allow chaining
*/
Proxyquire.prototype.noCallThru = function () {
this._noCallThru = true;
return this.fn;
};
/**
* Enables call thru, which determines if keys of original modules will be used
* when they weren't stubbed out.
* @name callThru
* @function
* @private
* @return {object} The proxyquire function to allow chaining
*/
Proxyquire.prototype.callThru = function () {
this._noCallThru = false;
return this.fn;
};
/**
* Will make proxyquire remove the requested modules from the `require.cache` in order to force
* them to be reloaded the next time they are proxyquired.
* This behavior differs from the way nodejs `require` works, but for some tests this maybe useful.
*
* @name noPreserveCache
* @function
* @private
* @return {object} The proxyquire function to allow chaining
*/
Proxyquire.prototype.noPreserveCache = function() {
this._preserveCache = false;
return this.fn;
};
/**
* Restores proxyquire caching behavior to match the one of nodejs `require`
*
* @name preserveCache
* @function
* @private
* @return {object} The proxyquire function to allow chaining
*/
Proxyquire.prototype.preserveCache = function() {
this._preserveCache = true;
return this.fn;
};
/**
* Loads a module using the given stubs instead of their normally resolved required modules.
* ここが本処理っぽい
* @param request The requirable module path to load.
* @param stubs The stubs to use. e.g., { "path": { extname: function () { ... } } }
* @return {*} A newly resolved module with the given stubs.
*/
Proxyquire.prototype.load = function (request, stubs) {
// バリデーション
validateArguments(request, stubs);
// Find out if any of the passed stubs are global overrides
// ここはstubに特定のオプション(@global or @runtimeGlobal)が渡されてないかチェックしてる
for (var key in stubs) {
var stub = stubs[key];
// stubがnullならスルーしてる
if (stub === null) continue;
if (typeof stub === 'undefined') {
throw new ProxyquireError('Invalid stub: "' + key + '" cannot be undefined');
}
// @global指定するとオプションが変わるっぽい
if (hasOwnProperty.call(stub, '@global')) {
this._containsGlobal = true;
}
if (hasOwnProperty.call(stub, '@runtimeGlobal')) {
this._containsGlobal = true;
this._containsRuntimeGlobal = true;
}
}
// Ignore the module cache when return the requested module
return this._withoutCache(this._parent, stubs, request, this._parent.require.bind(this._parent, request));
};
// This replaces a module's require function
// ここでrequireの動きを再現してるっぽい?
Proxyquire.prototype._require = function(module, stubs, path) {
// バリデーション
// ここのコピペだと思われ
// https://github.com/nodejs/node/blob/master/lib/module.js#L466-L467
assert(typeof path === 'string', 'path must be a string');
assert(path, 'missing path');
// stubにpathが含まれていたら
if (hasOwnProperty.call(stubs, path)) {
var stub = stubs[path];
if (stub === null) {
// Mimic the module-not-found exception thrown by node.js.
// 本物のrequireに動きを寄せてる
throw moduleNotFoundError(path);
}
// noCallThruの対象かどうかチェックしてる
// 文字列がなければProxyquire自身の設定を見てる
// 何もしないとtrueっぽい…?
if (hasOwnProperty.call(stub, '@noCallThru') ? !stub['@noCallThru'] : !this._noCallThru) {
// stubに足りないプロパティのみ補充してる
// モック以外はリアルでloadしてる
fillMissingKeys(stub, Module._load(path, module));
}
// We are top level or this stub is marked as global
if (module.parent == this._parent || hasOwnProperty.call(stub, '@global') || hasOwnProperty.call(stub, '@runtimeGlobal')) {
// モック内でrequireが実行されるとここでmockを返してる
return stub;
}
}
// Only ignore the cache if we have global stubs
if (this._containsRuntimeGlobal) {
return this._withoutCache(module, stubs, path, Module._load.bind(Module, path, module));
} else {
return Module._load(path, module);
}
};
// 本処理では以下の引数を渡されている
// this._parent, stubs, request, this._parent.require.bind(this._parent, request)
// モック内でスタブがrequireされるとコイツが返る
Proxyquire.prototype._withoutCache = function(module, stubs, path, func) {
// moduleにModule.parent(これなんだろうねぇ)
// pathにモックしたいモジュール
// stubsにモックに突っ込むスタブ
// funcにthis._parent.require(request) (モックしたいモジュールをrequireってことかな)
// Temporarily disable the cache - either per-module or globally if we have global stubs
// おそらくrequireのキャッシュクリア
var restoreCache = this._disableCache(module, path);
// Override all require extension handlers
// ここでstubsにあるモジュールのrequire文を上書きしてる疑惑
// そうでした!!!!!!!!
var restoreExtensionHandlers = this._overrideExtensionHandlers(module, stubs);
try {
// Execute the function that needs the module cache disabled
return func();
} finally {
// Restore the cache if we are preserving it
if (this._preserveCache) {
// ここはチェーンメソッドで指定しないと実行されなそうなので一旦スルー
restoreCache();
} else {
// importに渡される文字列からファイルを検索してる
// のでidというよりファイルパスのはず。。。
var id = Module._resolveFilename(path, module);
// stubにしたいmoduleのファイルパスを取得してるっぽい
var stubIds = Object.keys(stubs).map(function (stubPath) {
return resolve.sync(stubPath, {
basedir: dirname(id),
extensions: Object.keys(require.extensions),
paths: Module.globalPaths
})
});
var ids = [id].concat(stubIds);
// モック化するものとモックに突っ込むスタブのファイルパスをrequire.cacheから消してる
ids.forEach(function (id) {
delete require.cache[id];
});
}
// Finally restore the original extension handlers
// 上書きしたrequireセンパイを元に戻す
restoreExtensionHandlers();
}
};
Proxyquire.prototype._disableCache = function(module, path) {
if (this._containsGlobal) {
// empty the require cache because if we are stubbing C but requiring A,
// and if A requires B and B requires C, then B and C might be cached already
// and we'll never get the chance to return our stub
return this._disableGlobalCache();
}
// Temporarily delete the SUT from the require cache
return this._disableModuleCache(path, module);
};
Proxyquire.prototype._disableGlobalCache = function() {
var cache = require.cache;
require.cache = Module._cache = {};
// Return a function that will undo what we just did
return function() {
require.cache = Module._cache = cache;
};
};
Proxyquire.prototype._disableModuleCache = function(path, module) {
// Find the ID (location) of the SUT, relative to the parent
var id = Module._resolveFilename(path, module);
var cached = Module._cache[id];
delete Module._cache[id];
// Return a function that will undo what we just did
return function() {
if (cached) {
Module._cache[id] = cached;
}
};
};
// moduleにはModule.parent, stubsにはモックに突っ込みたいstubたち
Proxyquire.prototype._overrideExtensionHandlers = function(module, stubs) {
var originalExtensions = {};
var self = this;
Object.keys(require.extensions).forEach(function(extension) {
// Store the original so we can restore it later
// require.extensionsを保存してる
if (!originalExtensions[extension]) {
originalExtensions[extension] = require.extensions[extension];
}
// Override the default handler for the requested file extension
// require.extensionsを上書きしてる
require.extensions[extension] = function(module, filename) {
// Override the require method for this module
// require()をthis._require(Module.parent, stubs)に置き換えてる
// ここヤーーーーーーーーーー!!!!
module.require = self._require.bind(self, module, stubs);
return originalExtensions[extension](module, filename);
};
});
// Return a function that will undo what we just did
return function() {
Object.keys(originalExtensions).forEach(function(extension) {
require.extensions[extension] = originalExtensions[extension];
});
};
};
module.exports = Proxyquire;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment