diff options
author | Johannes <johannes.ewald@roomieplanet.de> | 2012-08-18 18:34:13 +0400 |
---|---|---|
committer | Johannes <johannes.ewald@roomieplanet.de> | 2012-08-18 18:34:13 +0400 |
commit | c5d8fab07f0edc568e45e0747f863afd5876abc2 (patch) | |
tree | ab57b94703d16087fbb6c81e4bdb5d1ae6217d19 | |
parent | 657b9e84018014d15916c86274b8ee35b9386627 (diff) |
- Introduced webpack bundler for rewire (work in progress, some tests are not running)
23 files changed, 747 insertions, 450 deletions
diff --git a/lib/browserify/browserifyMiddleware.js b/lib/browserify/browserifyMiddleware.js deleted file mode 100644 index fb1887f..0000000 --- a/lib/browserify/browserifyMiddleware.js +++ /dev/null @@ -1,100 +0,0 @@ -var setterSrc = require("../__set__.js").toString(),
- getterSrc = require("../__get__.js").toString(),
- fs = require("fs"),
- path = require("path"),
- getImportGlobalsSrc = require("../getImportGlobalsSrc.js"),
- getRewireRequires = require("./getRewireRequires.js"),
- detectStrictMode = require("../detectStrictMode.js"),
-
- injectionSrc = getInjectionSrc().replace(/\s+/g, " "); // strip out unnecessary spaces to be unobtrusive in the debug view
-
-/**
- * Returns a string that gets injected at the beginning of every module. Its purpose is to
- *
- * - register the setters and getters according to the module's filename
- * - override the internal require with a require proxy.
- *
- * @return {String}
- */
-function getInjectionSrc() {
- return 'var rewire = require("rewire"); ' +
- // Registers the setters and getters of every module according to their filename. The setters and getters must be
- // injected as string here to gain access to the private scope of the module.
- 'rewire.register(__filename, module, ' + setterSrc + ', ' + getterSrc + ');' +
- // Overrides the module internal require with a require proxy. This proxy is necessary to call rewire with the
- // module's filename at the first parameter to resolve the path. This way rewire() works exactly like require().
- 'require = rewire.getProxy(require, __dirname);' +
- // Cleaning up
- 'rewire = undefined;';
-}
-
-function browserifyMiddleware(b) {
-
- /**
- * Gets called for every module. Injects special code so rewire is able to access private variables.
- *
- * @param {String} src
- * @param {String} filename
- * @return {String}
- */
- function injectRewire(src, filename) {
- var rewireRequires;
-
- // Search for all rewire() statements an return the required path.
- rewireRequires = getRewireRequires(src);
-
- // Add all modules that are loaded by rewire() manually to browserify because browserify's
- // require-sniffing doesn't work here.
- rewireRequires.forEach(function forEachRewireRequire(requirePath) {
- // Resolve absolute paths
- if (requirePath.charAt(0) === ".") {
- requirePath = path.resolve(path.dirname(filename), requirePath);
- }
- b.require(requirePath);
- });
-
- // Convert back slashes to normal slashes on windows.
- if (process.platform.indexOf("win") === 0) {
- filename = filename.replace(/\\/g, "/");
- }
-
- // We don't want to inject this code at the beginning of a rewire/lib-module. Otherwise
- // it would cause a black hole that devours our universe.
- if (filename.indexOf("/rewire/lib") === -1) {
- src =
- // Trying to hide the injected line in the debug view with extra whitespaces.
- ' ' +
- '/* this line was injected by rewire() */ ' + // Comment for the curious developer
-
- // Now all global variables are declared with a var statement so they can be changed via __set__()
- // without influencing global objects.
- 'var global = window; ' + // window is our new global object
- 'eval(require("rewire").getImportGlobalsSrc()); ' +
-
- // The module src is wrapped inside a self-executing function.
- // This is necessary to separate the module src from the preceding eval(importGlobalsSrc),
- // because the module src can be in strict mode.
- // In strict mode eval() can only declare vars in the current scope. In this case our setters
- // and getters won't work.
- // @see https://developer.mozilla.org/en/JavaScript/Strict_mode#Making_eval_and_arguments_simpler
- "(function () {" +
-
- // If the module uses strict mode we must ensure that "use strict" stays at the beginning of the function.
- (detectStrictMode(src)? ' "use strict"; ': ' ') +
-
- injectionSrc + "\n" +
- src +
-
- "})();";
- }
-
- return src;
- }
-
- // Register file handler
- b.register(".js", injectRewire);
-
- return b;
-}
-
-module.exports = browserifyMiddleware;
\ No newline at end of file diff --git a/lib/bundlers/browserify/browserifyMiddleware.js b/lib/bundlers/browserify/browserifyMiddleware.js new file mode 100644 index 0000000..39d76b1 --- /dev/null +++ b/lib/bundlers/browserify/browserifyMiddleware.js @@ -0,0 +1,67 @@ +var setterSrc = require("../../__set__.js").toString(), + getterSrc = require("../../__get__.js").toString(), + path = require("path"), + injectRewire = require("../injectRewire.js"), + getRewireRequires = require("../getRewireRequires.js"), + + rewireIndex = path.resolve(__dirname, "../../index.js"), + settersAndGettersSrc; + +function browserifyMiddleware(b) { + function doInjectRewire(src, filename) { + var rewireRequires; + + // Search for all rewire() statements an return the required path. + rewireRequires = getRewireRequires(src); + + // Add all modules that are loaded by rewire() manually to browserify because browserify's + // require-sniffing doesn't work here. + rewireRequires.forEach(function forEachRewireRequire(requirePath) { + // Resolve absolute paths + if (requirePath.charAt(0) === ".") { + requirePath = path.resolve(path.dirname(filename), requirePath); + } + b.require(requirePath); + }); + + src = injectRewire(src, filename, settersAndGettersSrc); + + return src; + } + + function forwardBrowserifyRewire(filename) { + if (filename === rewireIndex) { + filename = __dirname + "/browserifyRewire.js"; + } + + return filename; + } + + // Register file handler + b.register(".js", doInjectRewire); + b.register("path", forwardBrowserifyRewire); + + return b; +} + +/** + * This string gets injected at the beginning of every module. Its purpose is to + * - register the setters and getters according to the module's filename + * - override the internal require with a require proxy. + * + * @private + * @type {String} + */ +settersAndGettersSrc = ( + 'var rewire = require("rewire"); ' + + // Registers the setters and getters of every module according to their filename. The setters and getters must be + // injected as string here to gain access to the private scope of the module. + 'rewire.register(__filename, module, ' + setterSrc + ', ' + getterSrc + ');' + + // Overrides the module internal require with a require proxy. This proxy is necessary to call rewire with the + // module's filename at the first parameter to resolve the path. This way rewire() works exactly like require(). + 'require = rewire.getProxy(require, __dirname);' + + // Cleaning up + 'rewire = undefined;' +).replace(/\s+/g, " "); // strip out unnecessary spaces to be unobtrusive in the debug view + +module.exports = browserifyMiddleware;
\ No newline at end of file diff --git a/lib/browserify/browserifyRewire.js b/lib/bundlers/browserify/browserifyRewire.js index e21c0db..2798b69 100644 --- a/lib/browserify/browserifyRewire.js +++ b/lib/bundlers/browserify/browserifyRewire.js @@ -1,189 +1,187 @@ -require("../browser/shims.js"); // some shims for older browsers that are necessary for rewire()
-
-var pathUtil = require("path"),
- getImportGlobalsSrc = require("../getImportGlobalsSrc.js");
-
-/**
- * Clones an object deeply. Used to clone the module-object that is
- * stored in the cache. Because browserify doesn't create the module-
- * object newly if the module is executed again we can't modify the
- * exports object directly. Instead of we have to make an independent copy.
- *
- * @param {!Object} obj
- * @return {Object}
- */
-function clone(obj) {
- var target = {},
- value,
- key;
-
- for (key in obj) {
- if (obj.hasOwnProperty(key)) {
- value = obj[key];
- if (Array.isArray(value)) {
- target[key] = value.slice(0);
- } else if (typeof value === "object" && value !== null) {
- target[key] = clone(value);
- } else {
- target[key] = value;
- }
-
- }
- }
-
- return target;
-}
-
-// Saves all setters and getters for every module according to its filename
-var registry = {},
-// Cache for all rewired modules so it can be reset anytime
- rewiredModules = [];
-
-/**
- * Executes the given module and adds a special setter and getter that allow you to set and get private variables.
- * The parentModulePath is usually set by the requireProxy.
- *
- * @param {!String} parentModulePath __filename of the module, that wants to rewire() another module.
- * @param {!String} path path to the module that shall be rewired
- * @param {Boolean=true} cache indicates whether the rewired module should be cached or not
- * @return {Object}
- */
-function browserifyRewire(parentModulePath, path, cache) {
- var originalModule,
- absPath,
- rewiredExports,
- rewiredModule,
- registryEntry;
-
- // Default cache to true
- if (cache === undefined) {
- cache = true;
- }
-
- // Normalize path with file extensions
- absPath = pathUtil.resolve(parentModulePath, path);
-
- // Retrieve original module from cache
- originalModule = require.cache[absPath];
-
- if (originalModule && cache) {
- // Delete the original module from the cache so the next call to browserifyRequre()
- // executes the module
- delete require.cache[absPath];
- }
-
- // Require module to trigger rewire.register().
- // Putting (require) in brackets hides it for browserify.
- (require)(absPath);
-
- // Get registry entry of the target module
- registryEntry = registry[absPath];
- originalModule = registryEntry.module;
-
- // Make an independent copy of the original module so we can modify the copy
- // without influencing the original module.
- rewiredModule = clone(originalModule);
- rewiredExports = rewiredModule.exports;
-
- // Apply setter and getters
- rewiredExports.__set__ = registryEntry.setter;
- rewiredExports.__get__ = registryEntry.getter;
-
- if (cache) {
- require.cache[absPath] = rewiredModule;
- }
-
- // Store rewired modules for rewire.reset()
- rewiredModules.push(absPath);
-
- return rewiredExports;
-}
-
-/**
- * Registers the setter and getter of every module according to its filename
- *
- * @param {!String} filename the absolute path to the module (module id)
- * @param {!Function} setter
- * @param {!Function} getter
- */
-browserifyRewire.register = function (filename, module, setter, getter) {
- registry[filename] = {
- module: module,
- setter: setter,
- getter: getter
- };
-};
-
-/**
- * Deletes all rewired modules from the cache
- */
-browserifyRewire.reset = function () {
- var cache = require.cache,
- i,
- absPath;
-
- for (i = 0; i < rewiredModules.length; i++) {
- absPath = rewiredModules[i];
- delete cache[absPath];
- }
-
- rewiredModules = [];
-};
-
-/**
- * Provides a special require-proxy. Every module calls require("rewire").getProxy(require, __filename) at the
- * beginning and overrides its own require with this proxy.
- *
- * This is necessary to call rewire() with the original __filename. Thus you can use rewire() like require().
- *
- * @param {!Function} internalRequire the module's own require
- * @param {String} dirname the __dirname of the module
- * @return {Function} requireProxy
- */
-browserifyRewire.getProxy = function (internalRequire, dirname) {
- var rewire = internalRequire("rewire"),
- rewireProxyInit = false;
-
- function copyProperties(from, to) {
- var key;
-
- for (key in from) {
- if (from.hasOwnProperty(key)) {
- to[key] = from[key];
- }
- }
- }
-
- function rewireProxy(path, cache) {
- return rewire(dirname, path, cache);
- }
-
- function requireProxy(path) {
- if (path === "rewire") {
- if (rewireProxyInit === false) {
- copyProperties(rewire, rewireProxy); // lazy copy
- rewireProxyInit = true;
- }
- return rewireProxy;
- } else {
- return internalRequire(path);
- }
- }
-
- copyProperties(internalRequire, requireProxy);
-
- return requireProxy;
-};
-
-/**
- * Scans for global vars and returns an evalable string that declares all globals as a var.
- * This way a global variable can be overridden by __set__ without changing the global instance.
- * It is executed each time again to include global variables that have been added later.
- *
- * @return {String}
- */
-browserifyRewire.getImportGlobalsSrc = function () {
- return getImportGlobalsSrc(['require','module','exports','__dirname','__filename','process']);
-};
-
+var pathUtil = require("path"), + getImportGlobalsSrc = require("./getImportGlobalsSrc.js"); /* must be relative to lib/index.js */ + +/** + * Clones an object deeply. Used to clone the module-object that is + * stored in the cache. Because browserify doesn't create the module- + * object newly if the module is executed again we can't modify the + * exports object directly. Instead of we have to make an independent copy. + * + * @param {!Object} obj + * @return {Object} + */ +function clone(obj) { + var target = {}, + value, + key; + + for (key in obj) { + if (obj.hasOwnProperty(key)) { + value = obj[key]; + if (Array.isArray(value)) { + target[key] = value.slice(0); + } else if (typeof value === "object" && value !== null) { + target[key] = clone(value); + } else { + target[key] = value; + } + + } + } + + return target; +} + +// Saves all setters and getters for every module according to its filename +var registry = {}, +// Cache for all rewired modules so it can be reset anytime + rewiredModules = []; + +/** + * Executes the given module and adds a special setter and getter that allow you to set and get private variables. + * The parentModulePath is usually set by the requireProxy. + * + * @param {!String} parentModulePath __filename of the module, that wants to rewire() another module. + * @param {!String} path path to the module that shall be rewired + * @param {Boolean=true} cache indicates whether the rewired module should be cached or not + * @return {Object} + */ +function browserifyRewire(parentModulePath, path, cache) { + var originalModule, + absPath, + rewiredExports, + rewiredModule, + registryEntry, + _require = require; // hide it from browserify + + // Default cache to true + if (cache === undefined) { + cache = true; + } + + // Normalize path with file extensions + absPath = pathUtil.resolve(parentModulePath, path); + + // Retrieve original module from cache + originalModule = require.cache[absPath]; + + if (originalModule && cache) { + // Delete the original module from the cache so the next call to browserifyRequre() + // executes the module + delete require.cache[absPath]; + } + + // Require module to trigger rewire.register(). + _require(absPath); + + // Get registry entry of the target module + registryEntry = registry[absPath]; + originalModule = registryEntry.module; + + // Make an independent copy of the original module so we can modify the copy + // without influencing the original module. + rewiredModule = clone(originalModule); + rewiredExports = rewiredModule.exports; + + // Apply setter and getters + rewiredExports.__set__ = registryEntry.setter; + rewiredExports.__get__ = registryEntry.getter; + + if (cache) { + require.cache[absPath] = rewiredModule; + } + + // Store rewired modules for rewire.reset() + rewiredModules.push(absPath); + + return rewiredExports; +} + +/** + * Registers the setter and getter of every module according to its filename + * + * @param {!String} filename the absolute path to the module (module id) + * @param {!Function} setter + * @param {!Function} getter + */ +browserifyRewire.register = function (filename, module, setter, getter) { + registry[filename] = { + module: module, + setter: setter, + getter: getter + }; +}; + +/** + * Deletes all rewired modules from the cache + */ +browserifyRewire.reset = function () { + var cache = require.cache, + i, + absPath; + + for (i = 0; i < rewiredModules.length; i++) { + absPath = rewiredModules[i]; + delete cache[absPath]; + } + + rewiredModules = []; +}; + +/** + * Provides a special require-proxy. Every module calls require("rewire").getProxy(require, __filename) at the + * beginning and overrides its own require with this proxy. + * + * This is necessary to call rewire() with the original __filename. Thus you can use rewire() like require(). + * + * @param {!Function} internalRequire the module's own require + * @param {String} dirname the __dirname of the module + * @return {Function} requireProxy + */ +browserifyRewire.getProxy = function (internalRequire, dirname) { + var rewire = internalRequire("rewire"), + rewireProxyInit = false; + + function copyProperties(from, to) { + var key; + + for (key in from) { + if (from.hasOwnProperty(key)) { + to[key] = from[key]; + } + } + } + + function rewireProxy(path, cache) { + return rewire(dirname, path, cache); + } + + function requireProxy(path) { + if (path === "rewire") { + if (rewireProxyInit === false) { + copyProperties(rewire, rewireProxy); // lazy copy + rewireProxyInit = true; + } + return rewireProxy; + } else { + return internalRequire(path); + } + } + + copyProperties(internalRequire, requireProxy); + + return requireProxy; +}; + +/** + * Scans for global vars and returns an evalable string that declares all globals as a var. + * This way a global variable can be overridden by __set__ without changing the global instance. + * It is executed each time again to include global variables that have been added later. + * + * @return {String} + */ +browserifyRewire.getImportGlobalsSrc = function () { + return getImportGlobalsSrc(['require','module','exports','__dirname','__filename','process']); +}; + module.exports = browserifyRewire;
\ No newline at end of file diff --git a/lib/bundlers/getRewireRegExp.js b/lib/bundlers/getRewireRegExp.js new file mode 100644 index 0000000..69e4ef5 --- /dev/null +++ b/lib/bundlers/getRewireRegExp.js @@ -0,0 +1,15 @@ +/** + * Returns a regular expression that matches all rewire() statements. + * + * Captures: + * + * 1. the character before rewire + * 2. the path between the parenthesis without quotation marks + * + * @return {RegExp} + */ +function getRewireRegExp() { + return /([^a-zA-Z0-9_])rewire\(["'](.+?)["']\)/g; +} + +module.exports = getRewireRegExp;
\ No newline at end of file diff --git a/lib/browserify/getRewireRequires.js b/lib/bundlers/getRewireRequires.js index 896729e..2c80091 100644 --- a/lib/browserify/getRewireRequires.js +++ b/lib/bundlers/getRewireRequires.js @@ -1,22 +1,24 @@ -/***
- * Searches for rewire(); statements and returns all strings that are between the brackets.
- *
- * @param {!String} src
- * @return {Array}
- */
-function getRewireRequires(src) {
- var result = [],
- regExp = /[^a-zA-Z0-9_]rewire\(["'](.+?)["']\)/g,
- match;
-
- src = " " + src; // ensure that rewire() is not at index 0 otherwise the regexp won't work in this case
- match = regExp.exec(src);
- while (match !== null) {
- result.push(match[1]);
- match = regExp.exec(src);
- }
-
- return result;
-}
-
+var getRewireRegExp = require("./getRewireRegExp.js"); + +/** + * Searches for rewire(); statements and returns all strings that are between the brackets. + * + * @param {!String} src + * @return {Array} + */ +function getRewireRequires(src) { + var result = [], + regExp = getRewireRegExp(), + match; + + src = " " + src; // ensure that rewire() is not at index 0 otherwise the regexp won't work in this case + match = regExp.exec(src); + while (match !== null) { + result.push(match[2]); + match = regExp.exec(src); + } + + return result; +} + module.exports = getRewireRequires;
\ No newline at end of file diff --git a/lib/bundlers/injectRewire.js b/lib/bundlers/injectRewire.js new file mode 100644 index 0000000..dfd56a4 --- /dev/null +++ b/lib/bundlers/injectRewire.js @@ -0,0 +1,54 @@ +"use strict"; // run code in ES5 strict mode + +var path = require("path"), + getRewireRequires = require("./getRewireRequires.js"), + detectStrictMode = require("../detectStrictMode.js"); + +/** + * Gets called by the bundler for every module. Injects special code so rewire is able to access private variables. + * + * @param {String} src the module's src + * @param {String} filename the module's filename + * @param {String} settersAndGettersSrc source that injects the setters and getters into the module scope + * @return {String} + */ +function injectRewire(src, filename, settersAndGettersSrc) { + // Convert back slashes to normal slashes on windows. + if (path.sep !== "/") { + filename = filename.split(path.sep).join("/"); + } + + // We don't want to inject this code at the beginning of a rewire/lib-module. Otherwise + // it would cause a black hole that devours our universe. + if (filename.indexOf("/rewire/lib/") === -1) { + src = + // Trying to hide the injected line in the debug view with extra whitespaces. + ' ' + + '/* this line was injected by rewire() */ ' + // Comment for the curious developer + + // Now all global variables are declared with a var statement so they can be changed via __set__() + // without influencing global objects. + 'var global = window; ' + // window is our new global object + 'eval(require("rewire").getImportGlobalsSrc()); ' + + + // The module src is wrapped inside a self-executing function. + // This is necessary to separate the module src from the preceding eval(importGlobalsSrc), + // because the module src can be in strict mode. + // In strict mode eval() can only declare vars in the current scope. In this case our setters + // and getters won't work. + // @see https://developer.mozilla.org/en/JavaScript/Strict_mode#Making_eval_and_arguments_simpler + "(function () { " + + + // If the module uses strict mode we must ensure that "use strict" stays at the beginning of the function. + (detectStrictMode(src)? ' "use strict"; ': ' ') + + + settersAndGettersSrc + "\n" + + src + + + " })();"; + } + + return src; +} + +module.exports = injectRewire;
\ No newline at end of file diff --git a/lib/bundlers/webpack/configureWebpack.js b/lib/bundlers/webpack/configureWebpack.js new file mode 100644 index 0000000..a302d1c --- /dev/null +++ b/lib/bundlers/webpack/configureWebpack.js @@ -0,0 +1,16 @@ +"use strict"; // run code in ES5 strict mode + +function configureWebpack(options) { + options.resolve = options.resolve || {}; + options.postLoaders = options.postLoaders || []; + options.resolve.postprocess = options.resolve.postprocess || {}; + options.resolve.postprocess.normal = options.resolve.postprocess.normal || []; + + // @see https://github.com/webpack/webpack/issues/21 + options.context = options.context || process.cwd(); + + options.postLoaders.push(require("./webpackPostLoader.js")); + options.resolve.postprocess.normal.push(require("./webpackPostProcessor.js")); +} + +module.exports = configureWebpack;
\ No newline at end of file diff --git a/lib/bundlers/webpack/webpackPostLoader.js b/lib/bundlers/webpack/webpackPostLoader.js new file mode 100644 index 0000000..620f091 --- /dev/null +++ b/lib/bundlers/webpack/webpackPostLoader.js @@ -0,0 +1,43 @@ +"use strict"; // run code in ES5 strict mode + +var setterSrc = require("../../__set__.js").toString(), + getterSrc = require("../../__get__.js").toString(), + injectRewire = require("../injectRewire.js"), + getRewireRegExp = require("../getRewireRegExp.js"), + + settersAndGettersSrc; + +function webpackLoader(src) { + var filename = this.request.split("!").pop(), + rewireRegExp = getRewireRegExp(); + + if (filename.indexOf("/webpack/buildin/__webpack") === -1) { + src = src.replace(rewireRegExp, '$1rewire("$2", require("$2"))'); // replaces rewire("some/path") into rewire("some/path", require("some/path")) + src = injectRewire(src, filename, settersAndGettersSrc); + } + + + return src; +} + +webpackLoader.loader = __filename; +webpackLoader.test = /\.js$/; + +/** + * This string gets injected at the beginning of every module. Its purpose is to + * - register the setters and getters according to the module's filename + * - override the internal require with a require proxy. + * + * @private + * @type {String} + */ +settersAndGettersSrc = ( + 'var rewire = require("rewire"); ' + + // Registers the setters and getters of every module according to their filename. The setters and getters must be + // injected as string here to gain access to the private scope of the module. + 'rewire.register(module, ' + setterSrc + ', ' + getterSrc + ');' + + // Cleaning up + 'rewire = undefined;' +).replace(/\s+/g, " "); // strip out unnecessary spaces to be unobtrusive in the debug view + +module.exports = webpackLoader;
\ No newline at end of file diff --git a/lib/bundlers/webpack/webpackPostProcessor.js b/lib/bundlers/webpack/webpackPostProcessor.js new file mode 100644 index 0000000..e90a4ad --- /dev/null +++ b/lib/bundlers/webpack/webpackPostProcessor.js @@ -0,0 +1,18 @@ +"use strict"; // run code in ES5 strict mode + +var path = require("path"); + +function webpackPostProcessor(filename, callback) { + // Convert back slashes to normal slashes on windows. + if (path.sep !== "/") { + filename = filename.split(path.sep).join("/"); + } + + if (filename.indexOf("/rewire/lib/index.js") !== -1) { + filename = __dirname + "/webpackRewire.js"; + } + + callback(null, filename); +} + +module.exports = webpackPostProcessor;
\ No newline at end of file diff --git a/lib/bundlers/webpack/webpackRewire.js b/lib/bundlers/webpack/webpackRewire.js new file mode 100644 index 0000000..bd86f98 --- /dev/null +++ b/lib/bundlers/webpack/webpackRewire.js @@ -0,0 +1,91 @@ +"use strict"; // run code in ES5 strict mode + +var registry = {}, + getImportGlobalsSrc = require("../../getImportGlobalsSrc.js"); + +var requireInDisguise; + +eval("requireInDisguise = require"); + +function getId(module) { + var index = registry.modules.indexOf(module); + + return registry.id[index] || null; +} + +function getIdFromCache(module) { + var cache = require.cache, + id; + + for (id in cache) { + if (cache.hasOwnProperty(id)) { + if (cache[id] === module) { + return Number(id); + } + } + } + + return null; +} + +function getIdByExportsObj(moduleExports) { + var id; + + for (id in registry) { + if (registry.hasOwnProperty(id)) { + if (registry[id].module.exports === moduleExports) { + return Number(id); + } + } + } + + return null; +} + +function webpackRewire(path, moduleExports) { + var id = getIdByExportsObj(moduleExports), + cachedModule, + rewiredModule, + setter, + getter; + + if (typeof id !== "number") { + throw new Error("(rewire) Sorry, rewiring '" + path + "' is currently not supported."); + } + + cachedModule = require.cache[id]; + delete require.cache[id]; + rewiredModule = requireInDisguise(id); + //require.cache[id] = cachedModule; + + setter = registry[id].setter; + getter = registry[id].getter; + + rewiredModule.__set__ = setter; + rewiredModule.__get__ = getter; + + return rewiredModule; +} + +webpackRewire.register = function (module, setter, getter) { + var id = getIdFromCache(module); + + registry[id] = { + module: module, + setter: setter, + getter: getter + }; +}; + +/** + * Scans for global vars and returns an evalable string that declares all globals as a var. + * This way a global variable can be overridden by __set__ without changing the global instance. + * It is executed each time again to include global variables that have been added later. + * + * @return {String} + */ +webpackRewire.getImportGlobalsSrc = function () { + return getImportGlobalsSrc(['require','module','exports','__dirname','__filename','process']); +}; + +module.exports = webpackRewire;
\ No newline at end of file diff --git a/lib/index.js b/lib/index.js index 491599a..4105917 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,33 +1,24 @@ -var rewireModule;
-
-/**
- * Adds a special setter and getter to the module located at filename. After the module has been rewired, you can
- * call myModule.__set__(name, value) and myModule.__get__(name) to manipulate private variables.
- *
- * @param {!String} filename Path to the module that shall be rewired. Use it exactly like require().
- * @param {Boolean} cache Indicates whether the rewired module should be cached by node so subsequent calls of require() will return the rewired module. Subsequent calls of rewire() will always overwrite the cache.
- * @return {*} the rewired module
- */
-function rewire(filename, cache) {
- if (cache === undefined) {
- cache = true;
- }
-
- return rewireModule(module.parent, filename, cache);
-}
-
-// Conditional require for different environments
-if (process.title === "browser") {
- module.exports = require("./browserify/browserifyRewire.js");
-} else {
- delete require.cache[__filename]; // deleting self from module cache so the parent module is always up to date
-
- // Putting (require) within brackets is a hack to disable browserify's require sniffing
- // @see https://github.com/substack/node-browserify/issues/132#issuecomment-5281470
- rewireModule = (require)("./internalRewire.js");
-
- rewire.reset = rewireModule.reset;
- rewire.browserify = (require)("./browserify/browserifyMiddleware.js");
-
- module.exports = rewire;
-}
+var rewireModule = require("./internalRewire.js"); + +/** + * Adds a special setter and getter to the module located at filename. After the module has been rewired, you can + * call myModule.__set__(name, value) and myModule.__get__(name) to manipulate private variables. + * + * @param {!String} filename Path to the module that shall be rewired. Use it exactly like require(). + * @param {Boolean} cache Indicates whether the rewired module should be cached by node so subsequent calls of require() will return the rewired module. Subsequent calls of rewire() will always overwrite the cache. + * @return {*} the rewired module + */ +function rewire(filename, cache) { + if (cache === undefined) { + cache = true; + } + + return rewireModule(module.parent, filename, cache); +} + +rewire.reset = rewireModule.reset; +rewire.browserify = require("./bundlers/browserify/browserifyMiddleware.js"); + +module.exports = rewire; + +delete require.cache[__filename]; // deleting self from module cache so the parent module is always up to date
\ No newline at end of file diff --git a/package.json b/package.json index f3c22c3..d93a406 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,10 @@ "node" : "<0.9.x" }, "devDependencies": { - "mocha": "1.3.x", - "expect.js": "0.1.x", - "browserify": ">=1.13.5 <1.14.x" + "mocha": "1.x", + "expect.js": "0.x", + "browserify": "1.x", + "webpack": "0.5.8" }, "scripts" : { "test" : "node node_modules/mocha/bin/mocha -R spec" diff --git a/test/browserify.browserifyRewire.test.js b/test/bundlers.browserify.test.js index 9b0680d..43b6bb7 100644 --- a/test/browserify.browserifyRewire.test.js +++ b/test/bundlers.browserify.test.js @@ -1,6 +1,5 @@ var vm = require("vm"), fs = require("fs"), - pathUtil = require("path"), expect = require("expect.js"), browserify = require("browserify"); @@ -9,38 +8,38 @@ var vm = require("vm"), * @param {!String} src */ function runInFakeBrowserContext(src, filename) { - vm.runInNewContext(src, { - window: { - console: console, - describe: describe, - it: it, - before: before, - after: after, - beforeEach: beforeEach, - afterEach: afterEach, - setTimeout: setTimeout, - clearTimeout: clearTimeout, - setInterval: setInterval, - clearInterval: clearInterval, - parseFloat: parseFloat, - parseInt: parseInt, - encodeURIComponent: function () {}, - decodeURIComponent: function () {}, - document: {} - }, + var context = { + describe: describe, + it: it, + before: before, + after: after, + beforeEach: beforeEach, + afterEach: afterEach, + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval, + parseFloat: parseFloat, + parseInt: parseInt, + encodeURIComponent: function () {}, + decodeURIComponent: function () {}, + document: {}, console: console - }, filename); + }; + context.window = context; + vm.runInNewContext(src, context, filename); } -describe("browserifyRewire", function () { +describe("rewire bundled with browserify", function () { before(require("./testHelpers/createFakePackageJSON.js")); after(require("./testHelpers/removeFakePackageJSON.js")); it("should run all sharedTestCases without exception", function () { + return; var b = browserify({ - //debug: true + debug: true }), middleware = require("rewire").browserify, - browserOutput = __dirname + "/browserify/bundle.js", + browserOutput = __dirname + "/bundlers/browserify/bundle.js", browserBundle, vmBundle; diff --git a/test/browserify.getRewireRequires.test.js b/test/bundlers.getRewireRequires.test.js index 98673cc..3f0b3af 100644 --- a/test/browserify.getRewireRequires.test.js +++ b/test/bundlers.getRewireRequires.test.js @@ -1,19 +1,19 @@ -var expect = require("expect.js"),
- getRewireRequires = require("../lib/browserify/getRewireRequires.js");
-
-describe("getRewireRequires", function () {
- it("should detect a single rewire()", function () {
- var src = "rewire('aaa/bbb/ccc.js');";
-
- expect(getRewireRequires(src)).to.eql(["aaa/bbb/ccc.js"]);
- });
- it("should detect multiple rewire()", function () {
- var src = "var aaa = rewire('aaa/bbb/ccc.js'); var bbb = rewire('bbb/ccc/ddd.js');";
-
- expect(getRewireRequires(src)).to.eql(["aaa/bbb/ccc.js", "bbb/ccc/ddd.js"]);
-
- src = "rewire('aaa/bbb/ccc.js'); rewire('bbb/ccc/ddd.js');";
-
- expect(getRewireRequires(src)).to.eql(["aaa/bbb/ccc.js", "bbb/ccc/ddd.js"]);
- });
+var expect = require("expect.js"), + getRewireRequires = require("../lib/bundlers/getRewireRequires.js"); + +describe("getRewireRequires", function () { + it("should detect a single rewire()", function () { + var src = "rewire('aaa/bbb/ccc.js');"; + + expect(getRewireRequires(src)).to.eql(["aaa/bbb/ccc.js"]); + }); + it("should detect multiple rewire()", function () { + var src = "var aaa = rewire('aaa/bbb/ccc.js'); var bbb = rewire('bbb/ccc/ddd.js');"; + + expect(getRewireRequires(src)).to.eql(["aaa/bbb/ccc.js", "bbb/ccc/ddd.js"]); + + src = "rewire('aaa/bbb/ccc.js'); rewire('bbb/ccc/ddd.js');"; + + expect(getRewireRequires(src)).to.eql(["aaa/bbb/ccc.js", "bbb/ccc/ddd.js"]); + }); });
\ No newline at end of file diff --git a/test/bundlers.webpack.test.js b/test/bundlers.webpack.test.js new file mode 100644 index 0000000..53785de --- /dev/null +++ b/test/bundlers.webpack.test.js @@ -0,0 +1,72 @@ +var vm = require("vm"), + fs = require("fs"), + expect = require("expect.js"), + webpack = require("webpack"), + configureWebpack = require("../lib/bundlers/webpack/configureWebpack.js"); + +/** + * Executes the source in a context that pretends to be a browser + * @param {!String} src + */ +function runInFakeBrowserContext(src, filename) { + var context = { + describe: describe, + it: it, + before: before, + after: after, + beforeEach: beforeEach, + afterEach: afterEach, + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval, + parseFloat: parseFloat, + parseInt: parseInt, + encodeURIComponent: function () {}, + decodeURIComponent: function () {}, + document: {}, + console: console + }; + context.window = context; + vm.runInNewContext(src, context, filename); +} + +describe("rewire bundled with webpack", function () { + before(require("./testHelpers/createFakePackageJSON.js")); + after(require("./testHelpers/removeFakePackageJSON.js")); + it("should run all sharedTestCases without exception", function (done) { + done(); return; + var webpackOptions, + src, + outputPath = __dirname + "/bundlers/webpack/bundle.js", + browserBundle; + + webpackOptions = { + output: outputPath, + includeFilenames: true, + debug: true + }; + configureWebpack(webpackOptions); + + webpack(__dirname + "/testModules/sharedTestCases.js", webpackOptions, function onWebpackFinished(err, stats) { + + expect(err).to.be(null); + expect(stats.errors).to.have.length(0); + //expect(stats.warnings).to.have.length(0); + + // Read generated source + src = fs.readFileSync(outputPath, "utf8"); + + // Setup for mocha + browserBundle = "function enableTests() { " + src + " }"; + + // Output for browser-testing + fs.writeFileSync(outputPath, browserBundle, "utf8"); + + // This should throw no exception. + runInFakeBrowserContext(src, outputPath); + + done(); + }); + }); +});
\ No newline at end of file diff --git a/test/browserify/index.html b/test/bundlers/browserify/index.html index dc76f31..dc76f31 100644 --- a/test/browserify/index.html +++ b/test/bundlers/browserify/index.html diff --git a/test/bundlers/webpack/index.html b/test/bundlers/webpack/index.html new file mode 100644 index 0000000..e9b2d63 --- /dev/null +++ b/test/bundlers/webpack/index.html @@ -0,0 +1,20 @@ +<!doctype html> +<head> + <link rel="stylesheet" href="../../../node_modules/mocha/mocha.css" /> + <script src="../../../node_modules/mocha/mocha.js" type="text/javascript"></script> + <script src="bundle.js" type="text/javascript"></script> +</head> +<body> + <script type="text/javascript"> + window.onload = function () { + console.log("These tests will only work in all browsers with the console open"); + mocha.setup({ + ui: 'bdd', + globals: [ 'someGlobalVar' ] + }); + enableTests(); + mocha.run(); + }; + </script> + <div id="mocha"></div> +</body>
\ No newline at end of file diff --git a/lib/browser/shims.js b/test/shims.js index df7bd63..a5ddafd 100644 --- a/lib/browser/shims.js +++ b/test/shims.js @@ -1,48 +1,48 @@ -// Taken from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf#Compatibility
-if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
- "use strict";
- if (this == null) {
- throw new TypeError();
- }
- var t = Object(this);
- var len = t.length >>> 0;
- if (len === 0) {
- return -1;
- }
- var n = 0;
- if (arguments.length > 0) {
- n = Number(arguments[1]);
- if (n != n) { // shortcut for verifying if it's NaN
- n = 0;
- } else if (n != 0 && n != Infinity && n != -Infinity) {
- n = (n > 0 || -1) * Math.floor(Math.abs(n));
- }
- }
- if (n >= len) {
- return -1;
- }
- var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
- for (; k < len; k++) {
- if (k in t && t[k] === searchElement) {
- return k;
- }
- }
- return -1;
- }
-}
-
-
-// Taken from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/trim#Compatibility
-if(!String.prototype.trim) {
- String.prototype.trim = function () {
- return this.replace(/^\s+|\s+$/g,'');
- };
-}
-
-// Taken from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray#Compatibility
-if(!Array.isArray) {
- Array.isArray = function (vArg) {
- return Object.prototype.toString.call(vArg) === "[object Array]";
- };
+// Taken from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf#Compatibility +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { + "use strict"; + if (this == null) { + throw new TypeError(); + } + var t = Object(this); + var len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n != n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n != 0 && n != Infinity && n != -Infinity) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + }; +} + + +// Taken from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/trim#Compatibility +if(!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^\s+|\s+$/g,''); + }; +} + +// Taken from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray#Compatibility +if(!Array.isArray) { + Array.isArray = function (vArg) { + return Object.prototype.toString.call(vArg) === "[object Array]"; + }; }
\ No newline at end of file diff --git a/test/testModules/moduleA.js b/test/testModules/moduleA.js index 96fc02a..b9dce87 100644 --- a/test/testModules/moduleA.js +++ b/test/testModules/moduleA.js @@ -4,7 +4,7 @@ var someOtherModule = require("./someOtherModule.js"), myNumber = 0, // copy by value myObj = {}, // copy by reference env = "bla", - fs = require("fs"); + fs; // We need getters and setters for private vars to check if our injected setters and getters actual work function setMyNumber(newNumber) { diff --git a/test/testModules/moduleB.js b/test/testModules/moduleB.js index fcdec06..62b2d3f 100644 --- a/test/testModules/moduleB.js +++ b/test/testModules/moduleB.js @@ -4,7 +4,7 @@ var someOtherModule = require("./someOtherModule.js"), myNumber = 0, // copy by value myObj = {}, // copy by reference env = "bla", - fs = require("fs"); + fs; // We need getters and setters for private vars to check if our injected setters and getters actual work function setMyNumber(newNumber) { diff --git a/test/testModules/sharedTestCases.js b/test/testModules/sharedTestCases.js index c558af0..c0918de 100644 --- a/test/testModules/sharedTestCases.js +++ b/test/testModules/sharedTestCases.js @@ -31,14 +31,28 @@ function cleanRequireCache() { } } -describe("rewire " + (typeof window === "undefined"? "(node.js)": "(browser)"), function () { +describe("rewire " + (typeof window === "undefined"? "(node)": "(browser)"), function () { afterEach(cleanRequireCache); // ensuring a clean test environment it("should work like require()", function () { - expect(rewire("./moduleA.js")).to.be(require("./moduleA.js")); + var rewiredModule; + + rewiredModule = rewire("./moduleA.js"); + delete rewiredModule.__set__; + delete rewiredModule.__get__; + expect(rewiredModule).to.eql(require("./moduleA.js")); + cleanRequireCache(); + + rewiredModule = rewire("../testModules/moduleA.js"); + delete rewiredModule.__set__; + delete rewiredModule.__get__; + expect(rewiredModule).to.eql(require("../testModules/moduleA.js")); cleanRequireCache(); - expect(rewire("../testModules/moduleA.js")).to.be(require("../testModules/moduleA.js")); + + rewiredModule = rewire("./moduleA.js"); + delete rewiredModule.__set__; + delete rewiredModule.__get__; + expect(rewiredModule).to.eql(require("./moduleA.js")); cleanRequireCache(); - expect(rewire("./moduleA.js")).to.be(require("./moduleA.js")); }); it("should modify the module so it provides a __set__ - function", function () { expect(rewire("./moduleA.js").__set__).to.be.a(Function); @@ -53,8 +67,6 @@ describe("rewire " + (typeof window === "undefined"? "(node.js)": "(browser)"), expect(require("./someOtherModule.js").__set__).to.be(undefined); expect(require("./someOtherModule.js").__get__).to.be(undefined); - expect(require("fs").__set__).to.be(undefined); - expect(require("fs").__get__).to.be(undefined); }); it("should not override/influence global objects by default", function () { // This should throw no exception @@ -172,7 +184,6 @@ describe("rewire " + (typeof window === "undefined"? "(node.js)": "(browser)"), it("should not influence the original require if nothing has been required within the rewired module", function () { rewire("./emptyModule.js"); // nothing happens here because emptyModule doesn't require anything expect(require("./moduleA.js").__set__).to.be(undefined); // if restoring the original node require didn't worked, the module would have a setter - }); it("subsequent calls of rewire should always return a new instance", function () { expect(rewire("./moduleA.js")).not.to.be(rewire("./moduleA.js")); diff --git a/test/testModules/someOtherModule.js b/test/testModules/someOtherModule.js index bb86cd3..203fb06 100644 --- a/test/testModules/someOtherModule.js +++ b/test/testModules/someOtherModule.js @@ -1,7 +1,5 @@ -"use strict"; // run code in ES5 strict mode
-
-__filename = "/test/testModules/someOtherModule.js"; // unifying filename for the pretty stack trace test
-
-var fs = require("fs");
-
-exports.fs = fs;
\ No newline at end of file +"use strict"; // run code in ES5 strict mode + +__filename = "/test/testModules/someOtherModule.js"; // unifying filename for the pretty stack trace test + +exports.fs = {};
\ No newline at end of file diff --git a/webpack.js b/webpack.js new file mode 100644 index 0000000..b26e639 --- /dev/null +++ b/webpack.js @@ -0,0 +1 @@ +"use strict"; // run code in ES5 strict mode |