From fdd513d58c9600c6d9f50b60f97ffdd624f4cea7 Mon Sep 17 00:00:00 2001 From: John Hiesey Date: Sat, 17 May 2014 12:13:56 -0700 Subject: [PATCH 1/8] Fixed entry point in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 86a289d..5fe02c1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "proxyquireify", "version": "0.4.0", "description": "Proxies browserify's require in order to allow overriding dependencies during testing.", - "main": "proxyquireify.js", + "main": "index.js", "scripts": { "test": "tap test/*.js && echo 'TODO: node test/clientside/run.js'" }, From 7c248f1e7a9875d8fbbc412d5300a8f6399ffbdf Mon Sep 17 00:00:00 2001 From: John Hiesey Date: Sat, 17 May 2014 12:14:14 -0700 Subject: [PATCH 2/8] Don't transform non .js files --- lib/transform.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/transform.js b/lib/transform.js index cfce0f3..06ca2ed 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -17,6 +17,7 @@ function requireDependencies(src) { module.exports = function (file) { if (file === require.resolve('../index')) return through(); + if (!/\.js$/.test(file)) return through(); var data = ''; return through(write, end); From 2c85193b6eaba03f0c995800e88291bb21718a36 Mon Sep 17 00:00:00 2001 From: John Hiesey Date: Sat, 17 May 2014 12:14:23 -0700 Subject: [PATCH 3/8] Update to newest prelude from browser-pack --- lib/prelude.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/prelude.js b/lib/prelude.js index 12da683..66270ad 100644 --- a/lib/prelude.js +++ b/lib/prelude.js @@ -6,7 +6,7 @@ // anything defined in a previous bundle is accessed via the // orig method which is the requireuire for previous bundles -(function(modules, cache, entry) { +(function outer (modules, cache, entry) { // Save the require from previous bundle to this closure if any var previousRequire = typeof require == "function" && require; @@ -42,10 +42,10 @@ throw new Error('Cannot find module \'' + name + '\''); } m = cache[name] = {exports:{}}; - modules[name][0](function(x){ + modules[name][0].call(m.exports, function(x){ var id = modules[name][1][x]; return newRequire(id ? id : x); - },m,m.exports); + },m,m.exports,outer,modules,cache,entry); } return cache[name].exports; } From b12745c7f8e7159a412f5593103642de36a6d0f9 Mon Sep 17 00:00:00 2001 From: John Hiesey Date: Sat, 17 May 2014 12:14:28 -0700 Subject: [PATCH 4/8] Use a more elegant approach to overriding the prelude --- index.js | 6 ++++-- lib/hack-prelude.js | 32 +++++++++++++++++--------------- plugin.js | 3 +++ 3 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 plugin.js diff --git a/index.js b/index.js index 7cc8dd1..c654ba3 100644 --- a/index.js +++ b/index.js @@ -85,6 +85,8 @@ proxyquire.proxy = function (require_) { if (require.cache) { // only used during build, so prevent browserify from including it - var hackPrelude = './lib/hack-prelude'; - proxyquire.browserify = require(hackPrelude).browserify; + var hackPreludePath = './lib/hack-prelude'; + var hackPrelude = require(hackPreludePath); + proxyquire.browserify = hackPrelude.browserify; + proxyquire.plugin = hackPrelude.plugin; } diff --git a/lib/hack-prelude.js b/lib/hack-prelude.js index 837ab4b..06c97ec 100644 --- a/lib/hack-prelude.js +++ b/lib/hack-prelude.js @@ -1,23 +1,25 @@ 'use strict'; -var fs = require('fs') - , preludePath = require.resolve('browserify/node_modules/browser-pack/_prelude') - , preludeHackPath = require.resolve('./prelude') - , hack = fs.readFileSync(preludeHackPath, 'utf-8'); +var fs = require('fs'); +var path = require('path'); -// browser-pack reads the prelude via readfile sync so we'll override it here -// this way we can swap out what prelude it gets with this little hack -var fs_readFileSync = fs.readFileSync; -fs.readFileSync = function (path) { - if (path === preludePath) return hack; +var preludePath = path.join(__dirname, 'prelude.js'); +var prelude = fs.readFileSync(preludePath, 'utf8'); - var args = [].slice.call(arguments); - return fs_readFileSync.apply(null, args); +// This plugin replaces the prelude and adds a transform +var plugin = exports.plugin = function (bfy, opts) { + var oldBrowserPack = bfy._browserPack; + + bfy._browserPack = function (params) { + params.preludePath = preludePath; + params.prelude = prelude; + return oldBrowserPack(params); + } + + bfy.transform(require('./transform')); }; +// Maintain support for the old interface exports.browserify = function (files) { - delete require.cache[require.resolve('browserify')]; - delete require.cache[require.resolve('browserify/node_modules/browser-pack')]; - - return require('browserify')(files).transform(require('./transform')); + return require('browserify')(files).plugin(plugin); }; diff --git a/plugin.js b/plugin.js new file mode 100644 index 0000000..48e768d --- /dev/null +++ b/plugin.js @@ -0,0 +1,3 @@ +// This file allows using proxyquireify as a browserify plugin named +// 'proxyquireify/plugin' +module.exports = require('./lib/hack-prelude').plugin; From 910b1c0d370c2d00c515f204d4379dc607e16b19 Mon Sep 17 00:00:00 2001 From: John Hiesey Date: Sat, 17 May 2014 12:14:37 -0700 Subject: [PATCH 5/8] Use a separate cache when proxyquireify is active Instead of entirely disabling the browserify cache, just switch to a different cache each time `proxyquire` is called. This allows modules like `tape`, which have global state and/or circular dependencies, to work correctly. Also, move the require monkeypatching from the transform to the prelude, wich makes it possible to build code with proxyquireify even if proxyquireify is not used and is not built into the bundle. This allows test code to be always built with proxyquireify even if the module is not included as a dependency. --- index.js | 38 +++++++++++++++++++++--------------- lib/prelude.js | 51 ++++++++++++++++++++++++++++++++++++------------ lib/transform.js | 5 ----- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/index.js b/index.js index c654ba3..9176a28 100644 --- a/index.js +++ b/index.js @@ -26,12 +26,17 @@ function validateArguments(request, stubs) { var stubs; -function stub(stubs_) { - stubs = stubs_; +function stub(stubs_) { + stubs = stubs_; + // This cache is used by the prelude as an alternative to the regular cache. + // It is not read or written here, except to set it to an empty object when + // adding stubs and to reset it to null when clearing stubs. + module.exports._cache = {}; } -function reset() { - stubs = undefined; +function reset() { + stubs = undefined; + module.exports._cache = null; } function fillMissingKeys(mdl, original) { @@ -65,22 +70,23 @@ var proxyquire = module.exports = function (require_) { }; }; -proxyquire.proxy = function (require_) { - return function (request) { - function original() { - return require_(request); - } +// Start with the default cache +proxyquire._cache = null; - if (!stubs) return original(); +proxyquire._proxy = function (require_, request) { + function original() { + return require_(request); + } - var stub = stubs[request]; + if (!stubs) return original(); - if (!stub) return original(); + var stub = stubs[request]; - var stubWideNoCallThru = !!stubs['@noCallThru'] && stub['@noCallThru'] !== false; - var noCallThru = stubWideNoCallThru || !!stub['@noCallThru']; - return noCallThru ? stub : fillMissingKeys(stub, original()); - }; + if (!stub) return original(); + + var stubWideNoCallThru = !!stubs['@noCallThru'] && stub['@noCallThru'] !== false; + var noCallThru = stubWideNoCallThru || !!stub['@noCallThru']; + return noCallThru ? stub : fillMissingKeys(stub, original()); }; if (require.cache) { diff --git a/lib/prelude.js b/lib/prelude.js index 66270ad..0a0855d 100644 --- a/lib/prelude.js +++ b/lib/prelude.js @@ -11,22 +11,32 @@ var previousRequire = typeof require == "function" && require; function findProxyquireifyName() { - var deps = Object.keys(modules) - .map(function (k) { return modules[k][1]; }); + var deps = Object.keys(modules) + .map(function (k) { return modules[k][1]; }); - for (var i = 0; i < deps.length; i++) { - var pq = deps[i]['proxyquireify']; - if (pq) return pq; - } + for (var i = 0; i < deps.length; i++) { + var pq = deps[i]['proxyquireify']; + if (pq) return pq; + } } var proxyquireifyName = findProxyquireifyName(); function newRequire(name, jumped){ - var m = cache[name]; - var dontcache = name !== proxyquireifyName; + // Find the proxyquireify module, if present + var pqify = (proxyquireifyName != null) && cache[proxyquireifyName]; + + // Proxyquireify provides a separate cache that is used when inside + // a proxyquire call, and is set to null outside a proxyquire call. + // This allows the regular caching semantics to work correctly both + // inside and outside proxyquire calls while keeping the cached + // modules isolated. + // When switching from one proxyquire call to another, it clears + // the cache to prevent contamination between different sets + // of stubs. + var currentCache = (pqify && pqify.exports._cache) || cache; - if(!m || dontcache) { + if(!currentCache[name]) { if(!modules[name]) { // if we cannot find the the module within our internal map or // cache jump to the current global require ie. the last bundle @@ -41,13 +51,28 @@ if (previousRequire) return previousRequire(name, true); throw new Error('Cannot find module \'' + name + '\''); } - m = cache[name] = {exports:{}}; - modules[name][0].call(m.exports, function(x){ + var m = currentCache[name] = {exports:{}}; + + // The normal browserify require function + var req = function(x){ var id = modules[name][1][x]; return newRequire(id ? id : x); - },m,m.exports,outer,modules,cache,entry); + }; + + // The require function substituted for proxyquireify + var moduleRequire = function(x){ + var pqify = (proxyquireifyName != null) && cache[proxyquireifyName]; + // Only try to use the proxyquireify version if it has been `require`d + if (pqify && pqify.exports._proxy) { + return pqify.exports._proxy(req, x); + } else { + return req(x); + } + }; + + modules[name][0].call(m.exports,moduleRequire,m,m.exports,outer,modules,currentCache,entry); } - return cache[name].exports; + return currentCache[name].exports; } for(var i=0;i Date: Sat, 17 May 2014 12:15:11 -0700 Subject: [PATCH 6/8] Enable clientside tests now that they are working --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5fe02c1..ecbc3a1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Proxies browserify's require in order to allow overriding dependencies during testing.", "main": "index.js", "scripts": { - "test": "tap test/*.js && echo 'TODO: node test/clientside/run.js'" + "test": "tap test/*.js && node test/clientside/run.js" }, "repository": { "type": "git", @@ -19,7 +19,7 @@ "browserify": "~3.44.2", "mold-source-map": "~0.2.0", "tap": "~0.4.8", - "tape": "~0.3.2" + "tape": "~2.12.3" }, "keywords": [ "require", From da14871f743e0aff035a04b89347421cc43132cd Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Thu, 1 May 2014 13:11:47 -0700 Subject: [PATCH 7/8] special case for exported functions --- index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 9176a28..4789bbc 100644 --- a/index.js +++ b/index.js @@ -41,15 +41,20 @@ function reset() { function fillMissingKeys(mdl, original) { Object.keys(original).forEach(function (key) { - if (!mdl[key]) mdl[key] = original[key]; + if (!mdl[key]) mdl[key] = original[key]; }); + if (typeof mdl === 'function' && typeof original === 'function') { + Object.keys(original.prototype).forEach(function (key) { + if (!mdl.prototype[key]) mdl.prototype[key] = original.prototype[key]; + }); + } return mdl; } var proxyquire = module.exports = function (require_) { if (typeof require_ != 'function') - throw new ProxyquireifyError( + throw new ProxyquireifyError( 'It seems like you didn\'t initialize proxyquireify with the require in your test.\n' + 'Make sure to correct this, i.e.: "var proxyquire = require(\'proxyquireify\')(require);"' ); From b7807ea234ad5e2e0baf52549a3c2e0bd8a0d696 Mon Sep 17 00:00:00 2001 From: John Hiesey Date: Sat, 17 May 2014 12:17:15 -0700 Subject: [PATCH 8/8] Readme update to mention plugin functionality --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index bf69ea9..173bec4 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,28 @@ proxyquire.browserify() .pipe(fs.createWriteStream(__dirname + '/bundle.js')); ``` +### proxyquire.plugin() + +Instead of being used instead of `browserify()`, proxyquireify can also be used as a browserify plugin. + +```js +var browserify = require('browserify'); +var proxyquire = require('proxyquireify'); + +browserify() + .plugin(proxyquire.plugin) + .require(require.resolve('./test'), { entry: true }) + .bundle() + .pipe(fs.createWriteStream(__dirname + '/bundle.js')); +``` + +The plugin is also exported from the file plugin.js so that you can use proxyquireify when running browserify +from the command line. + +```sh +browserify -p proxyquireify/plugin test.js > bundle.js +``` + ### proxyquire(request: String, stubs: Object) - **request**: path to the module to be tested e.g., `../lib/foo`