Newer
Older
alert / js / node_modules / webpack-bundle-analyzer / lib / BundleAnalyzerPlugin.js
@Réz István Réz István on 18 Nov 2021 4 KB first commit
"use strict";

const fs = require('fs');

const path = require('path');

const {
  bold
} = require('chalk');

const Logger = require('./Logger');

const viewer = require('./viewer');

const utils = require('./utils');

const {
  writeStats
} = require('./statsUtils');

class BundleAnalyzerPlugin {
  constructor(opts = {}) {
    this.opts = {
      analyzerMode: 'server',
      analyzerHost: '127.0.0.1',
      reportFilename: null,
      reportTitle: utils.defaultTitle,
      defaultSizes: 'parsed',
      openAnalyzer: true,
      generateStatsFile: false,
      statsFilename: 'stats.json',
      statsOptions: null,
      excludeAssets: null,
      logLevel: 'info',
      // deprecated
      startAnalyzer: true,
      ...opts,
      analyzerPort: 'analyzerPort' in opts ? opts.analyzerPort === 'auto' ? 0 : opts.analyzerPort : 8888
    };
    this.server = null;
    this.logger = new Logger(this.opts.logLevel);
  }

  apply(compiler) {
    this.compiler = compiler;

    const done = (stats, callback) => {
      callback = callback || (() => {});

      const actions = [];

      if (this.opts.generateStatsFile) {
        actions.push(() => this.generateStatsFile(stats.toJson(this.opts.statsOptions)));
      } // Handling deprecated `startAnalyzer` flag


      if (this.opts.analyzerMode === 'server' && !this.opts.startAnalyzer) {
        this.opts.analyzerMode = 'disabled';
      }

      if (this.opts.analyzerMode === 'server') {
        actions.push(() => this.startAnalyzerServer(stats.toJson()));
      } else if (this.opts.analyzerMode === 'static') {
        actions.push(() => this.generateStaticReport(stats.toJson()));
      } else if (this.opts.analyzerMode === 'json') {
        actions.push(() => this.generateJSONReport(stats.toJson()));
      }

      if (actions.length) {
        // Making analyzer logs to be after all webpack logs in the console
        setImmediate(async () => {
          try {
            await Promise.all(actions.map(action => action()));
            callback();
          } catch (e) {
            callback(e);
          }
        });
      } else {
        callback();
      }
    };

    if (compiler.hooks) {
      compiler.hooks.done.tapAsync('webpack-bundle-analyzer', done);
    } else {
      compiler.plugin('done', done);
    }
  }

  async generateStatsFile(stats) {
    const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
    await fs.promises.mkdir(path.dirname(statsFilepath), {
      recursive: true
    });

    try {
      await writeStats(stats, statsFilepath);
      this.logger.info(`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`);
    } catch (error) {
      this.logger.error(`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`);
    }
  }

  async startAnalyzerServer(stats) {
    if (this.server) {
      (await this.server).updateChartData(stats);
    } else {
      this.server = viewer.startServer(stats, {
        openBrowser: this.opts.openAnalyzer,
        host: this.opts.analyzerHost,
        port: this.opts.analyzerPort,
        reportTitle: this.opts.reportTitle,
        bundleDir: this.getBundleDirFromCompiler(),
        logger: this.logger,
        defaultSizes: this.opts.defaultSizes,
        excludeAssets: this.opts.excludeAssets
      });
    }
  }

  async generateJSONReport(stats) {
    await viewer.generateJSONReport(stats, {
      reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.json'),
      bundleDir: this.getBundleDirFromCompiler(),
      logger: this.logger,
      excludeAssets: this.opts.excludeAssets
    });
  }

  async generateStaticReport(stats) {
    await viewer.generateReport(stats, {
      openBrowser: this.opts.openAnalyzer,
      reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
      reportTitle: this.opts.reportTitle,
      bundleDir: this.getBundleDirFromCompiler(),
      logger: this.logger,
      defaultSizes: this.opts.defaultSizes,
      excludeAssets: this.opts.excludeAssets
    });
  }

  getBundleDirFromCompiler() {
    if (typeof this.compiler.outputFileSystem.constructor === 'undefined') {
      return this.compiler.outputPath;
    }

    switch (this.compiler.outputFileSystem.constructor.name) {
      case 'MemoryFileSystem':
        return null;
      // Detect AsyncMFS used by Nuxt 2.5 that replaces webpack's MFS during development
      // Related: #274

      case 'AsyncMFS':
        return null;

      default:
        return this.compiler.outputPath;
    }
  }

}

module.exports = BundleAnalyzerPlugin;