63c8e50cc38ed442f083f7dcaeca0da3052cfdfb67ae6fc9b3e701a418f7fc4e2c15afb5de8247e1321fe99244196bcd2fd5c6c84d9bb7af7950562e5fb94f 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. const bfj = require('bfj');
  2. const path = require('path');
  3. const mkdir = require('mkdirp');
  4. const {bold} = require('chalk');
  5. const Logger = require('./Logger');
  6. const viewer = require('./viewer');
  7. const utils = require('./utils');
  8. class BundleAnalyzerPlugin {
  9. constructor(opts = {}) {
  10. this.opts = {
  11. analyzerMode: 'server',
  12. analyzerHost: '127.0.0.1',
  13. reportFilename: null,
  14. reportTitle: utils.defaultTitle,
  15. defaultSizes: 'parsed',
  16. openAnalyzer: true,
  17. generateStatsFile: false,
  18. statsFilename: 'stats.json',
  19. statsOptions: null,
  20. excludeAssets: null,
  21. logLevel: 'info',
  22. // deprecated
  23. startAnalyzer: true,
  24. ...opts,
  25. analyzerPort: 'analyzerPort' in opts ? (opts.analyzerPort === 'auto' ? 0 : opts.analyzerPort) : 8888
  26. };
  27. this.server = null;
  28. this.logger = new Logger(this.opts.logLevel);
  29. }
  30. apply(compiler) {
  31. this.compiler = compiler;
  32. const done = (stats, callback) => {
  33. callback = callback || (() => {});
  34. const actions = [];
  35. if (this.opts.generateStatsFile) {
  36. actions.push(() => this.generateStatsFile(stats.toJson(this.opts.statsOptions)));
  37. }
  38. // Handling deprecated `startAnalyzer` flag
  39. if (this.opts.analyzerMode === 'server' && !this.opts.startAnalyzer) {
  40. this.opts.analyzerMode = 'disabled';
  41. }
  42. if (this.opts.analyzerMode === 'server') {
  43. actions.push(() => this.startAnalyzerServer(stats.toJson()));
  44. } else if (this.opts.analyzerMode === 'static') {
  45. actions.push(() => this.generateStaticReport(stats.toJson()));
  46. } else if (this.opts.analyzerMode === 'json') {
  47. actions.push(() => this.generateJSONReport(stats.toJson()));
  48. }
  49. if (actions.length) {
  50. // Making analyzer logs to be after all webpack logs in the console
  51. setImmediate(async () => {
  52. try {
  53. await Promise.all(actions.map(action => action()));
  54. callback();
  55. } catch (e) {
  56. callback(e);
  57. }
  58. });
  59. } else {
  60. callback();
  61. }
  62. };
  63. if (compiler.hooks) {
  64. compiler.hooks.done.tapAsync('webpack-bundle-analyzer', done);
  65. } else {
  66. compiler.plugin('done', done);
  67. }
  68. }
  69. async generateStatsFile(stats) {
  70. const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
  71. mkdir.sync(path.dirname(statsFilepath));
  72. try {
  73. await bfj.write(statsFilepath, stats, {
  74. space: 2,
  75. promises: 'ignore',
  76. buffers: 'ignore',
  77. maps: 'ignore',
  78. iterables: 'ignore',
  79. circular: 'ignore'
  80. });
  81. this.logger.info(
  82. `${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`
  83. );
  84. } catch (error) {
  85. this.logger.error(
  86. `${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`
  87. );
  88. }
  89. }
  90. async startAnalyzerServer(stats) {
  91. if (this.server) {
  92. (await this.server).updateChartData(stats);
  93. } else {
  94. this.server = viewer.startServer(stats, {
  95. openBrowser: this.opts.openAnalyzer,
  96. host: this.opts.analyzerHost,
  97. port: this.opts.analyzerPort,
  98. reportTitle: this.opts.reportTitle,
  99. bundleDir: this.getBundleDirFromCompiler(),
  100. logger: this.logger,
  101. defaultSizes: this.opts.defaultSizes,
  102. excludeAssets: this.opts.excludeAssets
  103. });
  104. }
  105. }
  106. async generateJSONReport(stats) {
  107. await viewer.generateJSONReport(stats, {
  108. reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.json'),
  109. bundleDir: this.getBundleDirFromCompiler(),
  110. logger: this.logger,
  111. excludeAssets: this.opts.excludeAssets
  112. });
  113. }
  114. async generateStaticReport(stats) {
  115. await viewer.generateReport(stats, {
  116. openBrowser: this.opts.openAnalyzer,
  117. reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
  118. reportTitle: this.opts.reportTitle,
  119. bundleDir: this.getBundleDirFromCompiler(),
  120. logger: this.logger,
  121. defaultSizes: this.opts.defaultSizes,
  122. excludeAssets: this.opts.excludeAssets
  123. });
  124. }
  125. getBundleDirFromCompiler() {
  126. switch (this.compiler.outputFileSystem.constructor.name) {
  127. case 'MemoryFileSystem':
  128. return null;
  129. // Detect AsyncMFS used by Nuxt 2.5 that replaces webpack's MFS during development
  130. // Related: #274
  131. case 'AsyncMFS':
  132. return null;
  133. default:
  134. return this.compiler.outputPath;
  135. }
  136. }
  137. }
  138. module.exports = BundleAnalyzerPlugin;