123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- module.exports = webpackHotMiddleware;
- var helpers = require('./helpers');
- var pathMatch = helpers.pathMatch;
- function webpackHotMiddleware(compiler, opts) {
- opts = opts || {};
- opts.log =
- typeof opts.log == 'undefined' ? console.log.bind(console) : opts.log;
- opts.path = opts.path || '/__webpack_hmr';
- opts.heartbeat = opts.heartbeat || 10 * 1000;
- opts.statsOptions =
- typeof opts.statsOptions == 'undefined' ? {} : opts.statsOptions;
- var eventStream = createEventStream(opts.heartbeat);
- var latestStats = null;
- var closed = false;
- if (compiler.hooks) {
- compiler.hooks.invalid.tap('webpack-hot-middleware', onInvalid);
- compiler.hooks.done.tap('webpack-hot-middleware', onDone);
- } else {
- compiler.plugin('invalid', onInvalid);
- compiler.plugin('done', onDone);
- }
- function onInvalid() {
- if (closed) return;
- latestStats = null;
- if (opts.log) opts.log('webpack building...');
- eventStream.publish({ action: 'building' });
- }
- function onDone(statsResult) {
- if (closed) return;
- // Keep hold of latest stats so they can be propagated to new clients
- latestStats = statsResult;
- publishStats(
- 'built',
- latestStats,
- eventStream,
- opts.log,
- opts.statsOptions
- );
- }
- var middleware = function (req, res, next) {
- if (closed) return next();
- if (!pathMatch(req.url, opts.path)) return next();
- eventStream.handler(req, res);
- if (latestStats) {
- // Explicitly not passing in `log` fn as we don't want to log again on
- // the server
- publishStats('sync', latestStats, eventStream, false, opts.statsOptions);
- }
- };
- middleware.publish = function (payload) {
- if (closed) return;
- eventStream.publish(payload);
- };
- middleware.close = function () {
- if (closed) return;
- // Can't remove compiler plugins, so we just set a flag and noop if closed
- // https://github.com/webpack/tapable/issues/32#issuecomment-350644466
- closed = true;
- eventStream.close();
- eventStream = null;
- };
- return middleware;
- }
- function createEventStream(heartbeat) {
- var clientId = 0;
- var clients = {};
- function everyClient(fn) {
- Object.keys(clients).forEach(function (id) {
- fn(clients[id]);
- });
- }
- var interval = setInterval(function heartbeatTick() {
- everyClient(function (client) {
- client.write('data: \uD83D\uDC93\n\n');
- });
- }, heartbeat).unref();
- return {
- close: function () {
- clearInterval(interval);
- everyClient(function (client) {
- if (!client.finished) client.end();
- });
- clients = {};
- },
- handler: function (req, res) {
- var headers = {
- 'Access-Control-Allow-Origin': '*',
- 'Content-Type': 'text/event-stream;charset=utf-8',
- 'Cache-Control': 'no-cache, no-transform',
- // While behind nginx, event stream should not be buffered:
- // http://nginx.org/docs/http/ngx_http_proxy_module.html#proxy_buffering
- 'X-Accel-Buffering': 'no',
- };
- var isHttp1 = !(parseInt(req.httpVersion) >= 2);
- if (isHttp1) {
- req.socket.setKeepAlive(true);
- Object.assign(headers, {
- Connection: 'keep-alive',
- });
- }
- res.writeHead(200, headers);
- res.write('\n');
- var id = clientId++;
- clients[id] = res;
- req.on('close', function () {
- if (!res.finished) res.end();
- delete clients[id];
- });
- },
- publish: function (payload) {
- everyClient(function (client) {
- client.write('data: ' + JSON.stringify(payload) + '\n\n');
- });
- },
- };
- }
- function publishStats(action, statsResult, eventStream, log, statsOptions) {
- var resultStatsOptions = Object.assign(
- {
- all: false,
- cached: true,
- children: true,
- modules: true,
- timings: true,
- hash: true,
- errors: true,
- warnings: true,
- },
- statsOptions
- );
- var bundles = [];
- // multi-compiler stats have stats for each child compiler
- // see https://github.com/webpack/webpack/blob/main/lib/MultiCompiler.js#L97
- if (statsResult.stats) {
- var processed = statsResult.stats.map(function (stats) {
- return extractBundles(normalizeStats(stats, resultStatsOptions));
- });
- bundles = processed.flat();
- } else {
- bundles = extractBundles(normalizeStats(statsResult, resultStatsOptions));
- }
- bundles.forEach(function (stats) {
- var name = stats.name || '';
- // Fallback to compilation name in case of 1 bundle (if it exists)
- if (!name && stats.compilation) {
- name = stats.compilation.name || '';
- }
- if (log) {
- log(
- 'webpack built ' +
- (name ? name + ' ' : '') +
- stats.hash +
- ' in ' +
- stats.time +
- 'ms'
- );
- }
- eventStream.publish({
- name: name,
- action: action,
- time: stats.time,
- hash: stats.hash,
- warnings: formatErrors(stats.warnings || []),
- errors: formatErrors(stats.errors || []),
- modules: buildModuleMap(stats.modules),
- });
- });
- }
- function formatErrors(errors) {
- if (!errors || !errors.length) {
- return [];
- }
- if (typeof errors[0] === 'string') {
- return errors;
- }
- // Convert webpack@5 error info into a backwards-compatible flat string
- return errors.map(function (error) {
- var moduleName = error.moduleName || '';
- var loc = error.loc || '';
- return moduleName + ' ' + loc + '\n' + error.message;
- });
- }
- function normalizeStats(stats, statsOptions) {
- var statsJson = stats.toJson(statsOptions);
- if (stats.compilation) {
- // webpack 5 has the compilation property directly on stats object
- Object.assign(statsJson, {
- compilation: stats.compilation,
- });
- }
- return statsJson;
- }
- function extractBundles(stats) {
- // Stats has modules, single bundle
- if (stats.modules) return [stats];
- // Stats has children, multiple bundles
- if (stats.children && stats.children.length) return stats.children;
- // Not sure, assume single
- return [stats];
- }
- function buildModuleMap(modules) {
- var map = {};
- modules.forEach(function (module) {
- map[module.id] = module.name;
- });
- return map;
- }
|