viewer.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. "use strict";
  2. const path = require('path');
  3. const fs = require('fs');
  4. const http = require('http');
  5. const WebSocket = require('ws');
  6. const sirv = require('sirv');
  7. const _ = require('lodash');
  8. const {
  9. bold
  10. } = require('chalk');
  11. const Logger = require('./Logger');
  12. const analyzer = require('./analyzer');
  13. const {
  14. open
  15. } = require('./utils');
  16. const {
  17. renderViewer
  18. } = require('./template');
  19. const projectRoot = path.resolve(__dirname, '..');
  20. function resolveTitle(reportTitle) {
  21. if (typeof reportTitle === 'function') {
  22. return reportTitle();
  23. } else {
  24. return reportTitle;
  25. }
  26. }
  27. module.exports = {
  28. startServer,
  29. generateReport,
  30. generateJSONReport,
  31. // deprecated
  32. start: startServer
  33. };
  34. async function startServer(bundleStats, opts) {
  35. const {
  36. port = 8888,
  37. host = '127.0.0.1',
  38. openBrowser = true,
  39. bundleDir = null,
  40. logger = new Logger(),
  41. defaultSizes = 'parsed',
  42. excludeAssets = null,
  43. reportTitle
  44. } = opts || {};
  45. const analyzerOpts = {
  46. logger,
  47. excludeAssets
  48. };
  49. let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
  50. if (!chartData) return;
  51. const sirvMiddleware = sirv(`${projectRoot}/public`, {
  52. // disables caching and traverse the file system on every request
  53. dev: true
  54. });
  55. const server = http.createServer((req, res) => {
  56. if (req.method === 'GET' && req.url === '/') {
  57. const html = renderViewer({
  58. mode: 'server',
  59. title: resolveTitle(reportTitle),
  60. chartData,
  61. defaultSizes,
  62. enableWebSocket: true
  63. });
  64. res.writeHead(200, {
  65. 'Content-Type': 'text/html'
  66. });
  67. res.end(html);
  68. } else {
  69. sirvMiddleware(req, res);
  70. }
  71. });
  72. await new Promise(resolve => {
  73. server.listen(port, host, () => {
  74. resolve();
  75. const url = `http://${host}:${server.address().port}`;
  76. logger.info(`${bold('Webpack Bundle Analyzer')} is started at ${bold(url)}\n` + `Use ${bold('Ctrl+C')} to close it`);
  77. if (openBrowser) {
  78. open(url, logger);
  79. }
  80. });
  81. });
  82. const wss = new WebSocket.Server({
  83. server
  84. });
  85. wss.on('connection', ws => {
  86. ws.on('error', err => {
  87. // Ignore network errors like `ECONNRESET`, `EPIPE`, etc.
  88. if (err.errno) return;
  89. logger.info(err.message);
  90. });
  91. });
  92. return {
  93. ws: wss,
  94. http: server,
  95. updateChartData
  96. };
  97. function updateChartData(bundleStats) {
  98. const newChartData = getChartData(analyzerOpts, bundleStats, bundleDir);
  99. if (!newChartData) return;
  100. chartData = newChartData;
  101. wss.clients.forEach(client => {
  102. if (client.readyState === WebSocket.OPEN) {
  103. client.send(JSON.stringify({
  104. event: 'chartDataUpdated',
  105. data: newChartData
  106. }));
  107. }
  108. });
  109. }
  110. }
  111. async function generateReport(bundleStats, opts) {
  112. const {
  113. openBrowser = true,
  114. reportFilename,
  115. reportTitle,
  116. bundleDir = null,
  117. logger = new Logger(),
  118. defaultSizes = 'parsed',
  119. excludeAssets = null
  120. } = opts || {};
  121. const chartData = getChartData({
  122. logger,
  123. excludeAssets
  124. }, bundleStats, bundleDir);
  125. if (!chartData) return;
  126. const reportHtml = renderViewer({
  127. mode: 'static',
  128. title: resolveTitle(reportTitle),
  129. chartData,
  130. defaultSizes,
  131. enableWebSocket: false
  132. });
  133. const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
  134. fs.mkdirSync(path.dirname(reportFilepath), {
  135. recursive: true
  136. });
  137. fs.writeFileSync(reportFilepath, reportHtml);
  138. logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
  139. if (openBrowser) {
  140. open(`file://${reportFilepath}`, logger);
  141. }
  142. }
  143. async function generateJSONReport(bundleStats, opts) {
  144. const {
  145. reportFilename,
  146. bundleDir = null,
  147. logger = new Logger(),
  148. excludeAssets = null
  149. } = opts || {};
  150. const chartData = getChartData({
  151. logger,
  152. excludeAssets
  153. }, bundleStats, bundleDir);
  154. if (!chartData) return;
  155. await fs.promises.mkdir(path.dirname(reportFilename), {
  156. recursive: true
  157. });
  158. await fs.promises.writeFile(reportFilename, JSON.stringify(chartData));
  159. logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
  160. }
  161. function getChartData(analyzerOpts, ...args) {
  162. let chartData;
  163. const {
  164. logger
  165. } = analyzerOpts;
  166. try {
  167. chartData = analyzer.getViewerData(...args, analyzerOpts);
  168. } catch (err) {
  169. logger.error(`Could't analyze webpack bundle:\n${err}`);
  170. logger.debug(err.stack);
  171. chartData = null;
  172. }
  173. if (_.isPlainObject(chartData) && _.isEmpty(chartData)) {
  174. logger.error("Could't find any javascript bundles in provided stats file");
  175. chartData = null;
  176. }
  177. return chartData;
  178. }