twreporter-react
Version:
React-Redux site for The Reporter Foundation in Taiwan
482 lines (383 loc) • 14.7 kB
JavaScript
// Hacking too much time
// Based on Node.js Module class sources:
// https://github.com/nodejs/node/blob/master/lib/module.js
;
var _Set = require('babel-runtime/core-js/set')['default'];
var _getIterator = require('babel-runtime/core-js/get-iterator')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
exports.__esModule = true;
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _path2 = require('path');
var _path3 = _interopRequireDefault(_path2);
var _module2 = require('module');
var _module3 = _interopRequireDefault(_module2);
var _toolsLog = require('./tools/log');
var _toolsLog2 = _interopRequireDefault(_toolsLog);
var _helpers = require('./helpers');
var _toolsSerializeJavascript = require('./tools/serialize-javascript');
var _toolsSerializeJavascript2 = _interopRequireDefault(_toolsSerializeJavascript);
var require_hacker = {
preceding_path_resolvers: [],
path_resolvers: [],
global_hook_resolved_modules: {},
global_hooks_enabled: true,
occupied_file_extensions: new _Set(),
// logging
log: new _toolsLog2['default']('require-hook', { debug: false }), // this.options.debug
// installs a require() path resolver
//
// resolve - a function which takes two parameters:
//
// the path to be resolved
// the module in which the require() call was originated
//
// must return either a new path to the require()d module
// or it can return nothing to fall back to the original require()d module path
//
// returns an object with an .unmount() method
//
resolver: function resolver(resolve) {
validate.resolve(resolve);
var resolver = function resolver(path, module) {
// resolve the path for this require() call
var resolved_path = resolve(path, module);
// if no path was resolved - do nothing
if (!_helpers.exists(resolved_path)) {
return;
}
// return the path to be require()d
return resolved_path;
};
require_hacker.preceding_path_resolvers.push(resolver);
var result = {
unmount: function unmount() {
// javascript arrays still have no .remove() method in the XXI-st century
require_hacker.preceding_path_resolvers = require_hacker.preceding_path_resolvers.filter(function (x) {
return x !== resolver;
});
}
};
return result;
},
// installs a global require() hook for all paths
//
// (if these paths are certain to exist in the filesystem
// and if you need only a specific file extension
// then use the .hook(extension, resolve) method instead)
//
// id - a meaningful textual identifier
//
// resolve - a function which takes two parameters:
//
// the path to be resolved
// the module in which the require() call was originated
//
// must return either a javascript CommonJS module source code
// (i.e. "module.exports = ...", etc)
// or it can return nothing to fall back to the original Node.js loader
//
// returns an object with an .unmount() method
//
// options:
//
// precede_node_loader:
//
// true - this require() hook will intercept all require() calls
// before they go into the original Node.js loader
//
// false - this require() hook will only intercept those require() calls
// which failed to be resolved by the original Node.js loader
//
// default value: true
//
global_hook: function global_hook(id, resolve) {
var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
validate.global_hook(id, resolve);
var resolver = function resolver(path, module) {
// get CommonJS module source code for this require() call
var source = resolve(path, module);
// if no CommonJS module source code returned - skip this require() hook
if (!_helpers.exists(source)) {
return;
}
// CommonJS module source code returned,
// so put it into a hash for a corresponding key
var resolved_path = path + '.' + id;
// flush require() cache
delete require.cache[resolved_path];
// put the CommonJS module source code into the hash
require_hacker.global_hook_resolved_modules[resolved_path] = source;
// return the path to be require()d
// in order to get the CommonJS module source code
return resolved_path;
};
if (options.precede_node_loader === false) {
require_hacker.path_resolvers.push(resolver);
} else {
require_hacker.preceding_path_resolvers.push(resolver);
}
var hook = this.hook(id, function (path) {
var source = require_hacker.global_hook_resolved_modules[path];
delete require_hacker.global_hook_resolved_modules[path];
return source;
});
var result = {
unmount: function unmount() {
// javascript arrays still have no .remove() method in the XXI-st century
require_hacker.preceding_path_resolvers = require_hacker.preceding_path_resolvers.filter(function (x) {
return x !== resolver;
});
require_hacker.path_resolvers = require_hacker.path_resolvers.filter(function (x) {
return x !== resolver;
});
hook.unmount();
}
};
return result;
},
// installs a require() hook for the extension
//
// extension - a file extension to hook into require()s of
// (examples: 'css', 'jpg', 'js')
//
// resolve - a function that takes two parameters:
//
// the path requested in the require() call
// the module in which the require() call was originated
//
// must return either a javascript CommonJS module source code
// (i.e. "module.exports = ...", etc)
// or it can return nothing to fall back to the original Node.js loader
//
hook: function hook(extension, resolve) {
var _this = this;
this.log.debug('Hooking into *.' + extension + ' files loading');
// validation
validate.extension(extension);
validate.resolve(resolve);
// occupy file extension
this.occupied_file_extensions.add(extension);
// dotted extension
var dot_extension = '.' + extension;
// keep original extension loader
var original_loader = _module3['default']._extensions[dot_extension];
// display a warning in case of extension loader override
if (original_loader) {
// output a debug message in case of extension loader override,
// not a warning, so that it doesn't scare people
this.log.debug('-----------------------------------------------');
this.log.debug('Overriding an already existing require() hook ');
this.log.debug('for file extension ' + dot_extension);
this.log.debug('-----------------------------------------------');
}
// the list of cached modules
var cached_modules = new _Set();
// Node.js inner API check
/* istanbul ignore if */
if (!_module3['default']._extensions) {
throw new Error('Incompatilbe Node.js version detected: "Module._extensions" array is missing. File an issue on GitHub.');
}
// set new loader for this extension
_module3['default']._extensions[dot_extension] = function (module, filename) {
_this.log.debug('require() hook fired for ' + filename);
// var source = fs.readFileSync(filename, 'utf8')
var source = resolve(filename, module);
if (!_helpers.exists(source)) {
_this.log.debug('Fallback to original loader');
// this message would appear if there was no loader
// for the extension of the filename
if (_path3['default'].extname(filename) !== dot_extension) {
_this.log.info('Trying to load "' + _path3['default'].basename(filename) + '" as a "*' + dot_extension + '"');
}
// load the file with the original loader
return (original_loader || _module3['default']._extensions['.js'])(module, filename);
}
// add this file path to the list of cached modules
cached_modules.add(filename);
// Node.js inner API check
/* istanbul ignore if */
if (!module._compile) {
throw new Error('Incompatilbe Node.js version detected: "Module.prototype._compile" function is missing. File an issue on GitHub.');
}
// compile javascript module from its source
// https://github.com/nodejs/node/blob/master/lib/module.js#L379
module._compile(source, filename);
};
var result = {
// uninstall the hook
unmount: function unmount() {
// clear require() cache for this file extension
for (var _iterator = cached_modules, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _getIterator(_iterator);;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var _path = _ref;
delete require.cache[_path];
}
// mount the original loader for this file extension
_module3['default']._extensions[dot_extension] = original_loader;
// free file extension
_this.occupied_file_extensions['delete'](extension);
}
};
return result;
},
// returns a CommonJS modules source.
to_javascript_module_source: function to_javascript_module_source(anything) {
// if the asset source wasn't found - return an empty CommonJS module
if (!_helpers.exists(anything)) {
return 'module.exports = undefined';
}
// if it's already a common js module source
if (typeof anything === 'string' && is_a_module_declaration(anything)) {
return anything;
}
// generate javascript module source code based on the `source` variable
return 'module.exports = ' + _toolsSerializeJavascript2['default'](anything);
},
// resolves a requireable `path` to a real filesystem path relative to the `module`
// (resolves `npm link`, etc)
resolve: function resolve(path_to_resolve, module) {
// Module._resolveFilename existence check is perfomed outside of this method
try {
require_hacker.global_hooks_enabled = false;
return original_resolveFilename(path_to_resolve, module);
} finally {
require_hacker.global_hooks_enabled = true;
}
}
};
// validation
var validate = {
extension: function extension(_extension) {
// if (typeof extension !== 'string')
// {
// throw new Error(`Expected string extension. Got ${extension}`)
// }
if (_path3['default'].extname('test.' + _extension) !== '.' + _extension) {
throw new Error('Invalid file extension "' + _extension + '"');
}
// check if the file extension is already occupied
if (require_hacker.occupied_file_extensions.has(_extension)) {
throw new Error('File extension "' + _extension + '" is already occupied by require-hacker');
}
},
resolve: function resolve(_resolve) {
if (typeof _resolve !== 'function') {
throw new Error('Resolve should be a function. Got "' + _resolve + '"');
}
},
global_hook: function global_hook(id, resolver) {
if (!id) {
throw new Error('You must specify global hook id');
}
if (_path3['default'].extname('test.' + id) !== '.' + id) {
throw new Error('Invalid global hook id "' + id + '". Expected a valid file extension.');
}
// check if the file extension is already occupied
if (require_hacker.occupied_file_extensions.has(id)) {
throw new Error('File extension "' + id + '" is already occupied by require-hacker');
}
validate.resolve(resolver);
}
};
// Node.js inner API check
/* istanbul ignore if */
if (!_module3['default']._resolveFilename) {
throw new Error('Incompatilbe Node.js version detected: "Module._resolveFilename" function is missing. File an issue on GitHub.');
}
// Node.js inner API check
/* istanbul ignore if */
if (!_module3['default']._findPath) {
throw new Error('Incompatilbe Node.js version detected: "Module._findPath" function is missing. File an issue on GitHub.');
}
// the module in which the require() call originated
var require_caller = undefined;
// instrument Module._resolveFilename
// https://github.com/nodejs/node/blob/master/lib/module.js#L322
//
// `arguments` would conflict with Babel, therefore `...parameters`
//
// const native_module = require('native_module')
var original_resolveFilename = _module3['default']._resolveFilename;
_module3['default']._resolveFilename = function () {
for (var _len = arguments.length, parameters = Array(_len), _key = 0; _key < _len; _key++) {
parameters[_key] = arguments[_key];
}
var request = parameters[0];
var parent = parameters[1];
// take note of the require() caller
require_caller = parent;
return original_resolveFilename.apply(this, parameters);
};
// instrument Module._findPath
// https://github.com/nodejs/node/blob/master/lib/module.js#L335-L341
//
// `arguments` would conflict with Babel, therefore `...parameters`
//
var original_findPath = _module3['default']._findPath;
_module3['default']._findPath = function () {
for (var _len2 = arguments.length, parameters = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
parameters[_key2] = arguments[_key2];
}
var request = parameters[0];
// const paths = parameters[1]
// preceeding resolvers
if (require_hacker.global_hooks_enabled) {
for (var _iterator2 = require_hacker.preceding_path_resolvers, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _getIterator(_iterator2);;) {
var _ref2;
if (_isArray2) {
if (_i2 >= _iterator2.length) break;
_ref2 = _iterator2[_i2++];
} else {
_i2 = _iterator2.next();
if (_i2.done) break;
_ref2 = _i2.value;
}
var resolver = _ref2;
var resolved_path = resolver(request, require_caller);
if (_helpers.exists(resolved_path)) {
return resolved_path;
}
}
}
// original Node.js loader
var filename = original_findPath.apply(undefined, parameters);
if (filename !== false) {
return filename;
}
// rest resolvers
if (require_hacker.global_hooks_enabled) {
for (var _iterator3 = require_hacker.path_resolvers, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _getIterator(_iterator3);;) {
var _ref3;
if (_isArray3) {
if (_i3 >= _iterator3.length) break;
_ref3 = _iterator3[_i3++];
} else {
_i3 = _iterator3.next();
if (_i3.done) break;
_ref3 = _i3.value;
}
var resolver = _ref3;
var resolved = resolver.resolve(request, require_caller);
if (_helpers.exists(resolved)) {
return resolved;
}
}
}
return false;
};
// detect if it is a CommonJS module declaration
function is_a_module_declaration(text) {
return text.indexOf('module.exports = ') === 0 || /\s+module\.exports = .+/.test(text);
}
exports['default'] = require_hacker;
module.exports = exports['default'];
//# sourceMappingURL=require hacker.js.map