Last active
June 18, 2016 09:55
-
-
Save sota1235/12f41aad0b6682751a5c945a06876488 to your computer and use it in GitHub Desktop.
proxyquire.jsを理解するためにコピペ&コメント加えて理解する
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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