twreporter-react
Version:
React-Redux site for The Reporter Foundation in Taiwan
746 lines (590 loc) • 24.6 kB
JavaScript
'use strict';
exports.__esModule = true;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
var _path2 = require('path');
var _path3 = _interopRequireDefault(_path2);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _requireHacker = require('require-hacker');
var _requireHacker2 = _interopRequireDefault(_requireHacker);
var _toolsLog = require('./tools/log');
var _toolsLog2 = _interopRequireDefault(_toolsLog);
var _uglifyJs = require('uglify-js');
var _uglifyJs2 = _interopRequireDefault(_uglifyJs);
var _helpers = require('./helpers');
var _common = require('./common');
// using ES6 template strings
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings
var webpack_isomorphic_tools = (function () {
function webpack_isomorphic_tools(options) {
_classCallCheck(this, webpack_isomorphic_tools);
this.hooks = [];
this.cached_assets = [];
// take the passed in options
this.options = _helpers.convert_from_camel_case(_helpers.clone(options));
// add missing fields, etc
_common.normalize_options(this.options);
// set require-hacker debug mode if run in debug mode
if (this.options.debug) {
_requireHacker2['default'].log.options.debug = true;
}
// logging
this.log = new _toolsLog2['default']('webpack-isomorphic-tools', { debug: this.options.debug });
this.log.debug('instantiated webpack-isomorphic-tools v' + require('../package.json').version + ' with options', this.options);
}
// sets development mode flag to whatever was passed (or true if nothing was passed)
// (development mode allows asset hot reloading when used with webpack-dev-server)
webpack_isomorphic_tools.prototype.development = function development(flag) {
// set development mode flag
this.options.development = _helpers.exists(flag) ? flag : true;
if (this.options.development) {
this.log.debug('entering development mode');
} else {
this.log.debug('entering production mode');
}
// allows method chaining
return this;
};
// returns a mapping to read file paths for all the user specified asset types
// along with a couple of predefined ones: javascripts and styles
webpack_isomorphic_tools.prototype.assets = function assets() {
// when in development mode
if (this.options.development) {
// webpack and node.js start in parallel
// so webpack-assets.json might not exist on the very first run
// if a developer chose not to use the .server() method with a callback
// (or if a developer chose not to wait for a Promise returned by the .server() method)
if (!_fs2['default'].existsSync(this.webpack_assets_path)) {
this.log.error('"' + this.webpack_assets_path + '" not found. Most likely it hasn\'t yet been generated by Webpack. The most probable cause of this error is that you placed your server code outside of the callback in "webpack_isomorphic_tools.server(path, callback)" (or outside of the ".then()" call if you are using promises API). Using an empty stub instead.');
return _common.default_webpack_assets();
}
}
return require(this.webpack_assets_path);
};
// clear the require.cache (only used in developer mode with webpack-dev-server)
webpack_isomorphic_tools.prototype.refresh = function refresh() {
// ensure this is development mode
if (!this.options.development) {
throw new Error('.refresh() called in production mode. Did you forget to call .development() method on your webpack-isomorphic-tools server instance?');
}
this.log.debug('flushing require() caches');
// uncache webpack-assets.json file
// this.log.debug(' flushing require() cache for webpack assets json file')
// this.log.debug(` (was cached: ${typeof(require.cache[this.webpack_assets_path]) !== 'undefined'})`)
delete require.cache[this.webpack_assets_path];
// uncache cached assets
for (var _iterator = this.cached_assets, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.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;
this.log.debug(' flushing require() cache for ' + _path);
delete require.cache[_path];
}
// no assets are cached now
this.cached_assets = [];
};
// Makes `webpack-isomorphic-tools` aware of Webpack aliasing feature.
// https://webpack.github.io/docs/resolving.html#aliasing
// The `aliases` parameter corresponds to `resolve.alias`
// in your Webpack configuration.
// If this method is used it must be called before the `.server()` method.
webpack_isomorphic_tools.prototype.enable_aliasing = function enable_aliasing() {
var _this = this;
// mount require() hook
this.alias_hook = _requireHacker2['default'].resolver(function (path, module) {
// returns aliased global filesystem path
return _common.alias_hook(path, module, _this.options.project_path, _this.options.alias, _this.log);
});
// allows method chaining
return this;
};
// Initializes server-side instance of `webpack-isomorphic-tools`
// with the base path for your project, then calls `.register()`,
// and after that calls .wait_for_assets(callback).
//
// The `project_path` parameter must be identical
// to the `context` parameter of your Webpack configuration
// and is needed to locate `webpack-assets.json`
// which is output by Webpack process.
//
// sets up "project_path" option
// (this option is required on the server to locate webpack-assets.json)
webpack_isomorphic_tools.prototype.server = function server(project_path, callback) {
var _this2 = this;
// project base path, required to locate webpack-assets.json
this.options.project_path = project_path;
// resolve webpack-assets.json file path
this.webpack_assets_path = _path3['default'].resolve(this.options.project_path, this.options.webpack_assets_file_path);
// register require() hooks
this.register();
// if Webpack aliases are supplied, enable aliasing
if (this.options.alias) {
this.enable_aliasing();
}
// if Webpack `modulesDirectories` are supplied, enable them
if (this.options.modules_directories) {
this.inject_modules_directories(this.options.modules_directories);
}
// inject helpers like require.context() and require.ensure()
if (this.options.patch_require) {
this.log.debug('Patching Node.js require() function');
this.patch_require();
}
// when ready:
// if callback is given, call it back
if (callback) {
// call back when ready
return this.wait_for_assets(callback);
}
// otherwise resolve a Promise
else {
// no callback given, return a Promise
return new Promise(function (resolve, reject) {
return _this2.wait_for_assets(resolve);
});
}
};
// Registers Node.js require() hooks for the assets
//
// This is what makes the `requre()` magic work on server.
// These `require()` hooks must be set before you `require()`
// any of your assets
// (e.g. before you `require()` any React components
// `require()`ing your assets).
//
// read this article if you don't know what a "require hook" is
// http://bahmutov.calepin.co/hooking-into-node-loader-for-fun-and-profit.html
webpack_isomorphic_tools.prototype.register = function register() {
this.log.debug('registering require() hooks for assets');
// // a helper array for extension matching
// const extensions = []
//
// // for each user specified asset type,
// // for each file extension,
// // create an entry in the extension matching array
// for (let asset_type of Object.keys(this.options.assets))
// {
// const description = this.options.assets[asset_type]
//
// for (let extension of description.extensions)
// {
// extensions.push([`.${extension}`, description])
// }
// }
//
// // registers a global require() hook which runs
// // before the default Node.js require() logic
// this.asset_hook = require_hacker.global_hook('webpack-asset', (path, module) =>
// {
// // for each asset file extension
// for (let extension of extensions)
// {
// // if the require()d path has this file extension
// if (ends_with(path, extension[0]))
// {
// // then require() it using webpack-assets.json
// return this.require(require_hacker.resolve(path, module), extension[1])
// }
// }
// })
// for each user specified asset type,
// register a require() hook for each file extension of this asset type
for (var _iterator2 = Object.keys(this.options.assets), _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
var _ref2;
if (_isArray2) {
if (_i2 >= _iterator2.length) break;
_ref2 = _iterator2[_i2++];
} else {
_i2 = _iterator2.next();
if (_i2.done) break;
_ref2 = _i2.value;
}
var asset_type = _ref2;
var description = this.options.assets[asset_type];
for (var _iterator3 = description.extensions, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
var _ref3;
if (_isArray3) {
if (_i3 >= _iterator3.length) break;
_ref3 = _iterator3[_i3++];
} else {
_i3 = _iterator3.next();
if (_i3.done) break;
_ref3 = _i3.value;
}
var extension = _ref3;
this.register_extension(extension, description);
}
}
// allows method chaining
return this;
};
// registers a require hook for a particular file extension
webpack_isomorphic_tools.prototype.register_extension = function register_extension(extension, description) {
var _this3 = this;
this.log.debug(' registering a require() hook for *.' + extension);
// place the require() hook for this extension
if (extension === 'json') {
this.hooks.push(_requireHacker2['default'].hook(extension, function (path) {
// special case for require('webpack-assets.json') and 'json' asset extension
if (path === _this3.webpack_assets_path) {
return;
}
return _this3.require(path, description);
}));
} else {
this.hooks.push(_requireHacker2['default'].hook(extension, function (path) {
return _this3.require(path, description);
}));
}
};
// injects Webpack's `modulesDirectories` into Node.js module resolver
webpack_isomorphic_tools.prototype.inject_modules_directories = function inject_modules_directories(modules_directories) {
modules_directories = modules_directories.filter(function (x) {
return x !== 'node_modules';
});
// instrument Module._nodeModulePaths function
// https://github.com/nodejs/node/blob/master/lib/module.js#L202
//
var original_find_paths = require('module')._findPath;
//
require('module')._findPath = function (request, paths) {
paths.map(function (a_path) {
var parts = a_path.split(_path3['default'].sep);
if (parts[parts.length - 1] === 'node_modules') {
parts[parts.length - 1] = '';
return parts.join(_path3['default'].sep);
}
}).filter(function (a_path) {
return a_path;
}).forEach(function (a_path) {
modules_directories.forEach(function (modules_directory) {
paths.push(a_path + modules_directory);
});
});
return original_find_paths(request, paths);
};
};
// injects helper functions into `require()` function
// (such as `.context()` and `.ensure()`)
// https://github.com/halt-hammerzeit/webpack-isomorphic-tools/issues/48#issuecomment-182878437
// (this is a "dirty" way to do it but it works)
webpack_isomorphic_tools.prototype.patch_require = function patch_require() {
// a source code of a function that
// require()s all modules inside the `base` folder
// and puts them into a hash map for further reference
//
// https://webpack.github.io/docs/context.html
//
var require_context = 'require.context = function(base, scan_subdirectories, regular_expression)\n\t\t{\n\t\t\tbase = require(\'path\').join(require(\'path\').dirname(module.filename), base)\n\n\t\t\tvar contents = {}\n\n\t\t\t// recursive function\n\t\t\tfunction read_directory(directory)\n\t\t\t{\n\t\t\t\trequire(\'fs\').readdirSync(directory).forEach(function(child)\n\t\t\t\t{\n\t\t\t\t\tvar full_path = require(\'path\').resolve(directory, child)\n\n\t\t\t\t\tif (require(\'fs\').statSync(full_path).isDirectory())\n\t\t\t\t\t{\n\t\t\t\t\t\tif (scan_subdirectories)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tread_directory(full_path)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tvar asset_path = require(\'path\').relative(base, full_path)\n\n\t\t\t\t\t\t// analogous to "uniform_path" from "./common.js"\n\t\t\t\t\t\tasset_path = (asset_path[0] === \'.\' ? asset_path : (\'./\' + asset_path)).replace(/\\\\/g, \'/\')\n\n\t\t\t\t\t\tif (regular_expression && !regular_expression.test(asset_path))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcontents[asset_path] = full_path\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tread_directory(base)\n\n\t\t\tvar result = function(asset_path)\n\t\t\t{\n\t\t\t\treturn require(contents[asset_path])\n\t\t\t}\n\n\t\t\tresult.keys = function()\n\t\t\t{\n\t\t\t\treturn Object.keys(contents)\n\t\t\t}\n\n\t\t\tresult.resolve = function(asset_path)\n\t\t\t{\n\t\t\t\treturn contents[asset_path]\n\t\t\t}\n\n\t\t\treturn result\n\t\t};';
// some code minification
require_context = _uglifyJs2['default'].minify(require_context, { fromString: true }).code;
// Source code for `require.ensure()`
// https://github.com/halt-hammerzeit/webpack-isomorphic-tools/issues/84
var require_ensure = 'require.ensure=function(d,c){c(require)};';
var debug = this.log.debug.bind(this.log);
// instrument Module.prototype._compile function
// https://github.com/nodejs/node/blob/master/lib/module.js#L376-L380
//
var original_compile = require('module').prototype._compile;
//
require('module').prototype._compile = function (content, filename) {
// inject it only in .js files
if (!_helpers.ends_with(filename, '.js')) {
// (the return value is supposed to be `undefined`)
return original_compile.call(this, content, filename);
}
// will be prepended to the module source code
var preamble = '';
// inject it only in .js files which
// might probably have `require.context` reference
if (content.indexOf('require.context') >= 0) {
debug('Injecting require.context() into "' + filename + '"');
preamble += require_context;
}
// inject it only in .js files which
// might probably have `require.ensure` reference
if (content.indexOf('require.ensure') >= 0) {
debug('Injecting require.ensure() into "' + filename + '"');
preamble += require_ensure;
}
// If there is a preamble to prepend
if (preamble) {
// Account for "use strict" which is required to be in the beginning of the source code
if (_helpers.starts_with(content, '\'use strict\'') || _helpers.starts_with(content, '"use strict"')) {
preamble = '"use strict";' + preamble;
}
}
// the "dirty" way
content = preamble + content;
// (the return value is supposed to be `undefined`)
return original_compile.call(this, content, filename);
};
};
// require()s an asset by a path
webpack_isomorphic_tools.prototype.require = function require(global_asset_path, description) {
this.log.debug('require() called for ' + global_asset_path);
// sanity check
/* istanbul ignore if */
if (!this.options.project_path) {
throw new Error('You forgot to call the .server() method passing it your project\'s base path');
}
// convert global asset path to local-to-the-project asset path
var asset_path = _common.normalize_asset_path(global_asset_path, this.options.project_path);
// if this filename is in the user specified exceptions list
// (or is not in the user explicitly specified inclusion list)
// then fall back to the normal require() behaviour
if (!this.includes(asset_path, description) || this.excludes(asset_path, description)) {
this.log.debug(' skipping require call for ' + asset_path);
return;
}
// track cached assets (only in development mode)
if (this.options.development) {
// mark this asset as cached
this.cached_assets.push(global_asset_path);
}
// return CommonJS module source for this asset
return _requireHacker2['default'].to_javascript_module_source(this.asset_source(asset_path));
};
// returns asset source by path (looks it up in webpack-assets.json)
webpack_isomorphic_tools.prototype.asset_source = function asset_source(asset_path) {
this.log.debug(' requiring ' + asset_path);
// Webpack replaces `node_modules` with `~`.
// I don't know how exactly it decides whether to
// replace `node_modules` with `~` or not
// so it will be a guess.
function possible_webpack_paths(asset_path) {
// Webpack always replaces project's own `node_modules` with `~`
if (_helpers.starts_with(asset_path, './node_modules/')) {
asset_path = asset_path.replace('./node_modules/', './~/');
}
// if there are any `node_modules` left,
// supposing the count is N,
// then there are 2 to the power of N possible guesses
// on how webpack path might look like.
var parts = asset_path.split('/node_modules/');
function construct_guesses(parts) {
if (parts.length === 1) {
return [parts];
}
var last = parts.pop();
var rest = construct_guesses(parts);
var guesses = [];
for (var _iterator4 = rest, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) {
var _ref4;
if (_isArray4) {
if (_i4 >= _iterator4.length) break;
_ref4 = _iterator4[_i4++];
} else {
_i4 = _iterator4.next();
if (_i4.done) break;
_ref4 = _i4.value;
}
var guess = _ref4;
var one = _helpers.clone(guess);
one.push('/~/');
one.push(last);
var two = _helpers.clone(guess);
two.push('/node_modules/');
two.push(last);
guesses.push(one);
guesses.push(two);
}
return guesses;
}
return construct_guesses(parts);
}
// get real file path list
var assets = this.assets().assets;
var possible_webpack_asset_paths = possible_webpack_paths(asset_path).map(function (path) {
return path.join('');
});
for (var _iterator5 = possible_webpack_asset_paths, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) {
var _ref5;
if (_isArray5) {
if (_i5 >= _iterator5.length) break;
_ref5 = _iterator5[_i5++];
} else {
_i5 = _iterator5.next();
if (_i5.done) break;
_ref5 = _i5.value;
}
var webpack_asset_path = _ref5;
if (possible_webpack_asset_paths.length > 1) {
this.log.debug(' trying "' + webpack_asset_path + '"');
}
// find this asset in the real file path list
var asset = assets[webpack_asset_path];
if (_helpers.exists(asset)) {
// the asset was found in the list - return it
return asset;
}
}
// if the asset was not found in the list,
// return nothing and output an error
this.log.error('asset not found: ' + asset_path);
return;
};
// unregisters require() hooks
webpack_isomorphic_tools.prototype.undo = function undo() {
// for each user specified asset type,
// unregister a require() hook for each file extension of this asset type
for (var _iterator6 = this.hooks, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) {
var _ref6;
if (_isArray6) {
if (_i6 >= _iterator6.length) break;
_ref6 = _iterator6[_i6++];
} else {
_i6 = _iterator6.next();
if (_i6.done) break;
_ref6 = _i6.value;
}
var hook = _ref6;
hook.unmount();
}
// this.asset_hook.unmount()
// unmount the aliasing hook (if mounted)
if (this.alias_hook) {
this.alias_hook.unmount();
}
};
// Checks if the required path should be excluded from the custom require() hook
webpack_isomorphic_tools.prototype.excludes = function excludes(path, options) {
// if "exclude" parameter isn't specified, then exclude nothing
if (!_helpers.exists(options.exclude)) {
return false;
}
// for each exclusion case
for (var _iterator7 = options.exclude, _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) {
var _ref7;
if (_isArray7) {
if (_i7 >= _iterator7.length) break;
_ref7 = _iterator7[_i7++];
} else {
_i7 = _iterator7.next();
if (_i7.done) break;
_ref7 = _i7.value;
}
var exclude = _ref7;
// supports regular expressions
if (exclude instanceof RegExp) {
if (exclude.test(path)) {
return true;
}
}
// check for a compex logic match
else if (typeof exclude === 'function') {
if (exclude(path)) {
return true;
}
}
// otherwise check for a simple textual match
else {
if (exclude === path) {
return true;
}
}
}
// no matches found.
// returns false so that it isn't undefined (for testing purpose)
return false;
};
// Checks if the required path should be included in the custom require() hook
webpack_isomorphic_tools.prototype.includes = function includes(path, options) {
// if "include" parameter isn't specified, then include everything
if (!_helpers.exists(options.include)) {
return true;
}
// for each inclusion case
for (var _iterator8 = options.include, _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) {
var _ref8;
if (_isArray8) {
if (_i8 >= _iterator8.length) break;
_ref8 = _iterator8[_i8++];
} else {
_i8 = _iterator8.next();
if (_i8.done) break;
_ref8 = _i8.value;
}
var include = _ref8;
// supports regular expressions
if (include instanceof RegExp) {
if (include.test(path)) {
return true;
}
}
// check for a compex logic match
else if (typeof include === 'function') {
if (include(path)) {
return true;
}
}
// otherwise check for a simple textual match
else {
if (include === path) {
return true;
}
}
}
// no matches found.
// returns false so that it isn't undefined (for testing purpose)
return false;
};
// Waits for webpack-assets.json to be created after Webpack build process finishes
//
// The callback is called when `webpack-assets.json` has been found
// (it's needed for development because `webpack-dev-server`
// and your application server are usually run in parallel).
//
webpack_isomorphic_tools.prototype.wait_for_assets = function wait_for_assets(done) {
var _this4 = this;
// condition check interval
var check_interval = 300; // in milliseconds
var message_interval = 2000; // in milliseconds
// show the message not too often
var message_timer = 0;
// selfie
var tools = this;
// waits for condition to be met, then proceeds
function wait_for(condition, proceed) {
function check() {
// if the condition is met, then proceed
if (condition()) {
return proceed();
}
message_timer += check_interval;
if (message_timer >= message_interval) {
message_timer = 0;
tools.log.debug('(' + tools.webpack_assets_path + ' not found)');
tools.log.info('(waiting for the first Webpack build to finish)');
}
setTimeout(check, check_interval);
}
check();
}
// wait for webpack-assets.json to be written to disk by Webpack
// (setTimeout() for global.webpack_isomorphic_tools )
setImmediate(function () {
return wait_for(function () {
return _fs2['default'].existsSync(_this4.webpack_assets_path);
}, done);
});
// allows method chaining
return this;
};
return webpack_isomorphic_tools;
})();
exports['default'] = webpack_isomorphic_tools;
// alias camel case for those who prefer it
_helpers.alias_properties_with_camel_case(webpack_isomorphic_tools.prototype);
module.exports = exports['default'];
// require() hooks for assets
// used to keep track of cached assets and flush their caches on .refresh() call
//# sourceMappingURL=index.js.map