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` diff --git a/index.js b/index.js index 7cc8dd1..4789bbc 100644 --- a/index.js +++ b/index.js @@ -26,25 +26,35 @@ 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) { 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);"' ); @@ -65,26 +75,29 @@ 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) { // 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/lib/prelude.js b/lib/prelude.js index 12da683..0a0855d 100644 --- a/lib/prelude.js +++ b/lib/prelude.js @@ -6,27 +6,37 @@ // 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; 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](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); + }; + + // 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