write-file-webpack-plugin
Version:
Forces webpack-dev-server to write bundle files to the file system.
246 lines (178 loc) • 8.24 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = WriteFileWebpackPlugin;
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _crypto = require('crypto');
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _mkdirp = require('mkdirp');
var _mkdirp2 = _interopRequireDefault(_mkdirp);
var _chalk = require('chalk');
var _chalk2 = _interopRequireDefault(_chalk);
var _moment = require('moment');
var _moment2 = _interopRequireDefault(_moment);
var _filesize = require('filesize');
var _filesize2 = _interopRequireDefault(_filesize);
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
var _writeFileAtomic = require('write-file-atomic');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var debug = (0, _debug2.default)('write-file-webpack-plugin');
/**
* When 'webpack' program is used, constructor name is equal to 'NodeOutputFileSystem'.
* When 'webpack-dev-server' program is used, constructor name is equal to 'MemoryFileSystem'.
*/
var isMemoryFileSystem = function isMemoryFileSystem(outputFileSystem) {
return outputFileSystem.constructor.name === 'MemoryFileSystem';
};
/**
* @typedef {Object} options
* @property {boolean} atomicReplace Atomically replace files content (i.e., to prevent programs like test watchers from seeing partial files) (default: true).
* @property {boolean} exitOnErrors Stop writing files on webpack errors (default: true).
* @property {boolean} force Forces the execution of the plugin regardless of being using `webpack-dev-server` or not (default: false).
* @property {boolean} log Logs names of the files that are being written (or skipped because they have not changed) (default: true).
* @property {RegExp} test A regular expression or function used to test if file should be written. When not present, all bundle will be written.
* @property {boolean} useHashIndex Use hash index to write only files that have changed since the last iteration (default: true).
*/
function WriteFileWebpackPlugin() {
var userOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var options = _lodash2.default.assign({}, {
atomicReplace: true,
exitOnErrors: true,
force: false,
log: true,
test: null,
useHashIndex: true
}, userOptions);
if (!_lodash2.default.isBoolean(options.exitOnErrors)) {
throw new TypeError('options.exitOnErrors value must be of boolean type.');
}
if (!_lodash2.default.isBoolean(options.force)) {
throw new TypeError('options.force value must be of boolean type.');
}
if (!_lodash2.default.isBoolean(options.log)) {
throw new TypeError('options.log value must be of boolean type.');
}
if (!_lodash2.default.isNull(options.test) && !(_lodash2.default.isRegExp(options.test) || _lodash2.default.isFunction(options.test))) {
throw new TypeError('options.test value must be an instance of RegExp or Function.');
}
if (!_lodash2.default.isBoolean(options.useHashIndex)) {
throw new TypeError('options.useHashIndex value must be of boolean type.');
}
if (!_lodash2.default.isBoolean(options.atomicReplace)) {
throw new TypeError('options.atomicReplace value must be of boolean type.');
}
var writeFileMethod = options.atomicReplace ? _writeFileAtomic.sync : _fs2.default.writeFileSync;
var log = function log() {
for (var _len = arguments.length, append = Array(_len), _key = 0; _key < _len; _key++) {
append[_key] = arguments[_key];
}
if (!options.log) {
return;
}
debug.apply(undefined, [_chalk2.default.dim('[' + (0, _moment2.default)().format('HH:mm:ss') + '] [write-file-webpack-plugin]')].concat(append));
};
var getAssetSource = function getAssetSource(asset) {
var source = asset.source();
if (Array.isArray(source)) {
return source.join('\n');
} else if (source instanceof ArrayBuffer) {
return Buffer.from(source);
}
return source;
};
var assetSourceHashIndex = {};
log('options', options);
var apply = function apply(compiler) {
var outputPath = void 0;
var setupDone = void 0;
var setupStatus = void 0;
var setup = function setup() {
if (setupDone) {
return setupStatus;
}
setupDone = true;
log('compiler.outputFileSystem is "' + _chalk2.default.cyan(compiler.outputFileSystem.constructor.name) + '".');
if (!isMemoryFileSystem(compiler.outputFileSystem) && !options.force) {
return false;
}
if (_lodash2.default.has(compiler, 'options.output.path') && compiler.options.output.path !== '/') {
outputPath = compiler.options.output.path;
}
if (!outputPath) {
throw new Error('output.path is not defined. Define output.path.');
}
log('outputPath is "' + _chalk2.default.cyan(outputPath) + '".');
setupStatus = true;
return setupStatus;
};
// eslint-disable-next-line promise/prefer-await-to-callbacks
var handleAfterEmit = function handleAfterEmit(compilation, callback) {
if (!setup()) {
// eslint-disable-next-line promise/prefer-await-to-callbacks
callback();
return;
}
if (options.exitOnErrors && compilation.errors.length) {
// eslint-disable-next-line promise/prefer-await-to-callbacks
callback();
return;
}
log('compilation.errors.length is "' + _chalk2.default.cyan(compilation.errors.length) + '".');
_lodash2.default.forEach(compilation.assets, function (asset, assetPath) {
var outputFilePath = _path2.default.isAbsolute(assetPath) ? assetPath : _path2.default.join(outputPath, assetPath);
var relativeOutputPath = _path2.default.relative(process.cwd(), outputFilePath);
var targetDefinition = 'asset: ' + _chalk2.default.cyan('./' + assetPath) + '; destination: ' + _chalk2.default.cyan('./' + relativeOutputPath);
var test = options.test;
if (test) {
var skip = _lodash2.default.isRegExp(test) ? !test.test(assetPath) : !test(assetPath);
if (skip) {
log(targetDefinition, _chalk2.default.yellow('[skipped; does not match test]'));
return;
}
}
// asset.size method returns `undefined` for binary files (e.g. WASM). This is a temporarily workaround.
var assetSize = asset.size() || 0;
var assetSource = getAssetSource(asset);
if (options.useHashIndex) {
var assetSourceHash = (0, _crypto.createHash)('sha256').update(assetSource).digest('hex');
if (assetSourceHashIndex[relativeOutputPath] && assetSourceHashIndex[relativeOutputPath] === assetSourceHash) {
log(targetDefinition, _chalk2.default.yellow('[skipped; matched hash index]'));
return;
}
assetSourceHashIndex[relativeOutputPath] = assetSourceHash;
}
_mkdirp2.default.sync(_path2.default.dirname(relativeOutputPath));
try {
writeFileMethod(relativeOutputPath.split('?')[0], assetSource);
log(targetDefinition, _chalk2.default.green('[written]'), _chalk2.default.magenta('(' + (0, _filesize2.default)(assetSize) + ')'));
} catch (error) {
log(targetDefinition, _chalk2.default.bold.red('[is not written]'), _chalk2.default.magenta('(' + (0, _filesize2.default)(assetSize) + ')'));
log(_chalk2.default.bold.bgRed('Exception:'), _chalk2.default.bold.red(error.message));
}
});
// eslint-disable-next-line promise/prefer-await-to-callbacks
callback();
};
/**
* webpack 4+ comes with a new plugin system.
*
* Check for hooks in-order to support old plugin system
*/
if (compiler.hooks) {
compiler.hooks.afterEmit.tapAsync('write-file-webpack-plugin', handleAfterEmit);
} else {
compiler.plugin('after-emit', handleAfterEmit);
}
};
return {
apply
};
}
module.exports = exports['default'];
//# sourceMappingURL=WriteFileWebpackPlugin.js.map