webpack-isomorphic-tools
Version:
Transforms CSS-alike text into a React style JSON object
670 lines (537 loc) • 21.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
exports.default = write_assets;
exports.extract_path = extract_path;
var _fsExtra = require('fs-extra');
var _fsExtra2 = _interopRequireDefault(_fsExtra);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _semver = require('semver');
var _semver2 = _interopRequireDefault(_semver);
var _requireHacker = require('require-hacker');
var _requireHacker2 = _interopRequireDefault(_requireHacker);
var _serializeJavascript = require('../tools/serialize-javascript');
var _serializeJavascript2 = _interopRequireDefault(_serializeJavascript);
var _helpers = require('../helpers');
var _common = require('../common');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// one can supply a custom filter
var default_filter = function default_filter(module, regular_expression) {
return regular_expression.test(module.name);
};
// one can supply a custom namer
var default_asset_path = function default_asset_path(module) {
return module.name;
};
// one can supply a custom parser
var default_parser = function default_parser(module) {
return module.source;
};
// writes webpack-assets.json file, which contains assets' file paths
function write_assets(json, options, log) {
// take the passed in options
options = (0, _helpers.clone)(options);
log.debug('running write assets webpack plugin v' + require('../../package.json').version + ' with options', options);
// make webpack stats accessible for asset functions (parser, path, filter)
options.webpack_stats = json;
var development = options.development;
if (development) {
log.debug(' (development mode is on)');
}
// write webpack stats json for debugging purpose
if (options.debug) {
// write webpack stats file
log.debug('writing webpack stats to ' + options.webpack_stats_path);
// write the file
// (format the JSON for better readability)
_fsExtra2.default.outputFileSync(options.webpack_stats_path, (0, _stringify2.default)(json, null, 2));
}
// Note Webpack version due to breaking changes between v2 and v3.
// https://github.com/catamphetamine/webpack-isomorphic-tools/issues/142
//
// Convert things like `3.0.0-alpha.1` to just `3.0.0`.
//
var version = new _semver2.default(json.version);
version = version.major + '.' + version.minor + '.' + version.patch;
options.webpackVersion = version;
options.webpackUsesTildeForNodeModules = (0, _common.webpack_uses_tilde_for_node_modules)(version);
// the output object with assets
var output = options.output;
output.webpack = { version: version
// populate the output object with assets
};populate_assets(output, json, options, log);
// write webpack assets info file
if (options.output_to_a_file) {
// format the JSON for better readability if in debug mode
var assets_info = development ? (0, _stringify2.default)(output, null, 2) : (0, _stringify2.default)(output);
// rewrite `webpack-assets.json`
var rewrite = true;
// for `webpack-assets.json` caching to work
// chunks info should be moved out of it,
// otherwise chunk hashsums constantly change,
// and there won't be any caching.
//
// const assets_buffer = Buffer.from(assets_info)
//
// // if webpack-assets.json already exists,
// // then maybe no need to rewrite it
// if (fs.existsSync(options.webpack_assets_path))
// {
// // previously written webpack-assets.json
// const previous_assets_buffer = fs.readFileSync(options.webpack_assets_path)
//
// // if webpack-assets.json rewrite is not needed, then don't do it
// if (assets_buffer.equals(previous_assets_buffer))
// {
// rewrite = false
// }
// }
// if webpack-assets.json rewrite is needed, then do it
if (rewrite) {
log.debug('writing webpack assets info to ' + options.webpack_assets_path);
// write the file
_fsExtra2.default.outputFileSync(options.webpack_assets_path, assets_info);
}
} else {
log.debug('serving webpack assets from memory');
}
// return Webpack assets JSON object
// for serving it through HTTP service
return output;
}
// populates the output object with assets
function populate_assets(output, json, options, log) {
// for each chunk name ("main", "common", ...)
(0, _keys2.default)(json.assetsByChunkName).forEach(function (name) {
log.debug('getting javascript and styles for chunk "' + name + '"');
// get javascript chunk real file path
var javascript = get_assets(name, 'js')[0];
// the second asset is usually a source map
if (javascript) {
log.debug(' (got javascript)');
output.javascript[name] = javascript;
}
// get style chunk real file path
var style = get_assets(name, 'css')[0];
// the second asset is usually a source map
if (style) {
log.debug(' (got style)');
output.styles[name] = style;
}
});
// gets asset paths by name and extension of their chunk
function get_assets(name) {
var extension = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'js';
var chunk = json.assetsByChunkName[name];
// a chunk could be a string or an array, so make sure it is an array
if (!Array.isArray(chunk)) {
chunk = [chunk];
}
return chunk
// filter by extension
.filter(function (name) {
return _path2.default.extname(extract_path(name)) === '.' + extension;
})
// adjust the real path (can be http, filesystem)
.map(function (name) {
return options.assets_base_url + name;
});
}
// 1st pass
var parsed_assets = {};
// global paths to parsed asset paths
var global_paths_to_parsed_asset_paths = {};
// define __webpack_public_path__ webpack variable
// (resolves "ReferenceError: __webpack_public_path__ is not defined")
var define_webpack_public_path = 'var __webpack_public_path__ = ' + (0, _stringify2.default)(options.assets_base_url) + ';\n';
// for each user specified asset type
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
var _loop = function _loop() {
var asset_type = _step.value;
// asset type settings
var asset_type_settings = options.assets[asset_type];
// one can supply his own filter
var filter = asset_type_settings.filter || default_filter; //.bind(this)
// one can supply his own path parser
var extract_asset_path = asset_type_settings.path || default_asset_path; //.bind(this)
// one can supply his own parser
var parser = asset_type_settings.parser || default_parser; //.bind(this)
// guard agains typos, etc
// for filter
if (!asset_type_settings.filter) {
log.debug('No filter specified for "' + asset_type + '" assets. Using a default one.');
}
// for path parser
if (!asset_type_settings.path) {
log.debug('No path parser specified for "' + asset_type + '" assets. Using a default one.');
}
// for parser
if (!asset_type_settings.parser) {
log.debug('No parser specified for "' + asset_type + '" assets. Using a default one.');
}
log.debug('parsing assets of type "' + asset_type + '"');
// timer start
var began_at = new Date().getTime();
// get real paths for all the files from this asset type
json.modules
// take just modules of this asset type
.filter(function (module) {
// check that this asset is of the asset type
if (!filter(module, options.regular_expressions[asset_type], options, log)) {
return false;
}
// guard against an empty source.
if (!module.source) {
log.error('Module "' + module.name + '" has no source. Maybe Webpack compilation of this module failed. Skipping this asset.');
return false;
}
// include this asset
return true;
}).reduce(function (set, module) {
// determine asset real path
var asset_path = extract_asset_path(module, options, log);
// asset module source, or asset content (or whatever else)
var parsed_asset = parser(module, options, log);
log.trace('Adding asset "' + asset_path + '", module id ' + module.id + ' (in webpack-stats.json)');
// check for naming collisions (just in case)
if ((0, _helpers.exists)(set[asset_path])) {
log.error('-----------------------------------------------------------------');
log.error('Asset with path "' + asset_path + '" was overwritten because of path collision.');
log.error('Use the "filter" function of this asset type to narrow the results.');
log.error('Previous asset with this path:');
log.error(set[asset_path]);
log.error('New asset with this path:');
log.error(parsed_asset);
log.error('-----------------------------------------------------------------');
}
// add this asset to the list
//
// also resolve "ReferenceError: __webpack_public_path__ is not defined".
// because it may be a url-loaded resource (e.g. a font inside a style).
set[asset_path] = define_webpack_public_path + _requireHacker2.default.to_javascript_module_source(parsed_asset);
// add path mapping
global_paths_to_parsed_asset_paths[_path2.default.resolve(options.project_path, asset_path)] = asset_path;
// continue
return set;
}, parsed_assets);
// timer stop
log.debug(' time taken: ' + (new Date().getTime() - began_at) + ' ms');
};
for (var _iterator = (0, _getIterator3.default)((0, _keys2.default)(options.assets)), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
_loop();
}
// register a special require() hook for requiring() raw webpack modules
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var require_hook = _requireHacker2.default.global_hook('webpack-module', function (required_path, module) {
log.debug('require()ing "' + required_path + '"');
// if Webpack aliases are supplied
if (options.alias) {
// possibly alias the path
var aliased_global_path = (0, _common.alias_hook)(required_path, module, options.project_path, options.alias, log);
// if an alias is found
if (aliased_global_path) {
var result = {
source: _requireHacker2.default.to_javascript_module_source(safe_require(aliased_global_path, log)),
path: aliased_global_path
};
return result;
}
}
// If the `required_path` is an npm package path
// still can't simply `return` and fall back to default Node.js behaviour
// because it could still be an asset like
// `require('bootstrap/style.css')` inside `./style.css`.
//
// If the `required_path` is not an asset though
// (which means "has no extension" in this case — should work)
// then `require()` it as usual.
//
if (required_path.split(_path2.default.sep).slice(-1)[0].indexOf('.') === -1) {
return;
}
// find an asset with this path
//
// the require()d path will be global path in case of the for..of require() loop
// for the assets (the code a couple of screens below).
//
// (it can be anything in other cases (e.g. nested require() calls from the assets))
//
if ((0, _helpers.exists)(global_paths_to_parsed_asset_paths[required_path])) {
log.debug(' found in parsed assets');
var _result = {
source: parsed_assets[global_paths_to_parsed_asset_paths[required_path]],
path: required_path
};
return _result;
}
log.debug(' not found in parsed assets, searching in webpack stats');
// find a webpack module which has a reason with this path
var candidates = [];
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = (0, _getIterator3.default)(json.modules), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var _module = _step2.value;
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator4 = (0, _getIterator3.default)(_module.reasons), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var reason = _step4.value;
if (reason.userRequest === required_path) {
candidates.push(_module);
break;
}
}
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
}
// guard against ambiguity
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
if (candidates.length === 1) {
log.debug(' found in webpack stats, module id ' + candidates[0].id);
// also resolve "ReferenceError: __webpack_public_path__ is not defined".
// because it may be a url-loaded resource (e.g. a font inside a style).
var _result2 = {
source: define_webpack_public_path + candidates[0].source,
path: candidates[0].identifier
};
return _result2;
}
// if there are more than one candidate for this require()d path,
// then try to guess which one is the one require()d
if (candidates.length > 1) {
log.debug(' More than a single candidate module was found in webpack stats for require()d path "' + required_path + '"');
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = (0, _getIterator3.default)(candidates), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var candidate = _step3.value;
log.debug(' ', candidate);
}
// (loaders matter so the program can't simply throw them away from the required path)
//
// // tries to normalize a cryptic Webpack loader path
// // into a regular relative file path
// // https://webpack.github.io/docs/loaders.html
// let filesystem_required_path = last(required_path
// .replace(/^!!/, '')
// .replace(/^!/, '')
// .replace(/^-!/, '')
// .split('!'))
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
var fail = function fail() {
throw new Error('More than a single candidate module was found in webpack stats for require()d path "' + required_path + '". Enable "debug: true" flag in webpack-isomorphic-tools configuration for more info.');
};
// https://webpack.github.io/docs/loaders.html
var is_webpack_loader_path = required_path.indexOf('!') >= 0;
// if it's a Webpack loader-powered path, the code gives up
if (is_webpack_loader_path) {
fail();
}
// from here on it's either a filesystem path or an npm module path
// if it's a global path it can be resolved right away
if ((0, _common.is_global_path)(required_path)) {
var _result3 = {
source: _requireHacker2.default.to_javascript_module_source(safe_require(required_path, log)),
path: required_path
};
return _result3;
}
// from here on it's either a relative filesystem path or an npm module path,
// so it can be resolved against the require()ing file path (if it can be recovered).
// `module.filename` here can be anything, not just a filesystem absolute path,
// since some advanced require() hook trickery is involved.
// therefore it will be parsed.
//
var requiring_file_path = module.filename.replace(/\.webpack-module$/, '');
// if it's a webpack loader-powered path, then extract the filesystem path from it
if (requiring_file_path.indexOf('!') >= 0) {
requiring_file_path = requiring_file_path.substring(requiring_file_path.lastIndexOf('!') + 1);
}
// make relative path global
if ((0, _common.is_relative_path)(requiring_file_path)) {
requiring_file_path = _path2.default.resolve(options.project_path, requiring_file_path);
}
// if `requiring_file_path` is a filesystem path (not an npm module path),
// then the require()d path can possibly be resolved
if ((0, _common.is_global_path)(requiring_file_path)) {
log.debug(' The module is being require()d from "' + requiring_file_path + '", so resolving the path against this file');
// if it's a relative path, can try to resolve it
if ((0, _common.is_relative_path)(required_path)) {
var absolute_path = _path2.default.resolve(requiring_file_path, '..', required_path);
var _result4 = {
source: _requireHacker2.default.to_javascript_module_source(safe_require(absolute_path, log)),
path: absolute_path
};
return _result4;
}
// if it's an npm module path (e.g. 'babel-runtime/core-js/object/assign'),
// can try to require() it from the requiring asset path
if ((0, _common.is_package_path)(required_path) && (0, _common.is_global_path)(module.filename)) {
var _absolute_path = _requireHacker2.default.resolve(required_path, module);
var _result5 = {
source: _requireHacker2.default.to_javascript_module_source(safe_require(_absolute_path, log)),
path: _absolute_path
};
return _result5;
}
}
// if it's still here then it means it's either a
fail();
}
});
log.debug('compiling assets');
// timer start
var began_at = new Date().getTime();
// evaluate parsed assets source code
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
for (var _iterator5 = (0, _getIterator3.default)((0, _keys2.default)(parsed_assets)), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var asset_path = _step5.value;
var compile = true;
// If this asset should be compiled at runtime then skip its compilation.
// (runtime compilation can be used for Webpack loaders returning
// javascript functions or React component classes — stuff like that,
// because it's not serializable to `webpack-assets.json`)
var _iteratorNormalCompletion6 = true;
var _didIteratorError6 = false;
var _iteratorError6 = undefined;
try {
for (var _iterator6 = (0, _getIterator3.default)((0, _keys2.default)(options.assets)), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
var _asset_type = _step6.value;
if (options.regular_expressions[_asset_type].test(asset_path)) {
if (options.assets[_asset_type].runtime) {
compile = false;
}
}
}
} catch (err) {
_didIteratorError6 = true;
_iteratorError6 = err;
} finally {
try {
if (!_iteratorNormalCompletion6 && _iterator6.return) {
_iterator6.return();
}
} finally {
if (_didIteratorError6) {
throw _iteratorError6;
}
}
}
if (!compile) {
output.assets[asset_path] = parsed_assets[asset_path];
continue;
}
// set asset value
log.debug('compiling asset "' + asset_path + '"');
output.assets[asset_path] = safe_require(_path2.default.resolve(options.project_path, asset_path), log);
// inside that require() call above
// all the assets are resolved relative to this `module`,
// which is irrelevant because they are all absolute filesystem paths.
//
// if in some of those assets a nested require() call is present
// then it will be resolved relative to that asset folder.
}
// unmount the previously installed require() hook
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5.return) {
_iterator5.return();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
require_hook.unmount();
// timer stop
log.debug(' time taken: ' + (new Date().getTime() - began_at) + ' ms');
}
function safe_require(path, log) {
try {
return require(path);
} catch (error) {
log.error(error);
return undefined;
}
}
function extract_path(from) {
var question_mark_index = from.indexOf('?');
if (question_mark_index === -1) {
return from;
}
return from.slice(0, question_mark_index);
}
//# sourceMappingURL=write assets.js.map