Home Reference Source

src/plugins/compression/index.js

const path = require('path');
const zlib = require('zlib');
const rollupUtils = require('rollup-pluginutils');
const extend = require('extend');
const fs = require('fs-extra');
const { deferred } = require('wootils/shared');
/**
 * This is a Rollup plugin that takes all the files that match an specific filter and compress using
 * Gzip.
 */
class ProjextRollupCompressionPlugin {
  /**
   * @param {ProjextRollupCompressionPluginOptions} [options={}]
   * The options to customize the plugin behaviour.
   * @param {string} [name='projext-rollup-plugin-compression']
   * The name of the plugin's instance.
   */
  constructor(options = {}, name = 'projext-rollup-plugin-compression') {
    /**
     * The plugin options.
     * @type {ProjextRollupCompressionPluginOptions}
     * @access protected
     * @ignore
     */
    this._options = extend(
      true,
      {
        folder: './dist',
        include: [],
        exclude: [],
        stats: () => {},
      },
      options
    );
    /**
     * The name of the plugin instance.
     * @type {string}
     */
    this.name = name;
    /**
     * The filter to decide which files will be compressed and which won't.
     * @type {RollupFilter}
     */
    this.filter = rollupUtils.createFilter(
      this._options.include,
      this._options.exclude
    );
    /**
     * @ignore
     */
    this.writeBundle = this.writeBundle.bind(this);
  }
  /**
   * Gets the plugin options
   * @return {ProjextRollupCompressionPluginOptions}
   */
  getOptions() {
    return this._options;
  }
  /**
   * This method gets called after Rollup writes the files on the file system. It takes care
   * of finding all the files that match the filter and compressing them with Gzip.
   * @return {Promise<Array,Error>} If everything goes well, the Promise will resolve on a list
   *                                of {@link ProjextRollupCompressionPluginEntry} objects.
   */
  writeBundle() {
    return Promise.all(this._findAllTheFiles().map((file) => this._compressFile(file)));
  }
  /**
   * Finds all the files on the directory specified on the options.
   * @return {Array}
   * @access protected
   * @ignore
   */
  _findAllTheFiles() {
    return this._readDirectory(this._options.folder);
  }
  /**
   * Reads a directory recursively until it finds all the files that match the plugin's filter.
   * @param {string} directory The directory to read.
   * @return {Array}
   * @access protected
   * @ignore
   */
  _readDirectory(directory) {
    const result = [];
    // Get all the items on the directory.
    fs.readdirSync(directory)
    // Filter hidden and already compressed files.
    .filter((item) => !item.startsWith('.') && !item.endsWith('.gz'))
    // Normalize the item information.
    .map((item) => {
      const itemPath = path.join(directory, item);
      return {
        path: itemPath,
        isDirectory: fs.lstatSync(itemPath).isDirectory(),
      };
    })
    // Remove items that aren't directories or that doesn't match the plugin's filter.
    .filter((item) => item.isDirectory || this.filter(item.path))
    // Process the remaining items.
    .forEach((item) => {
      // If the item is a directory...
      if (item.isDirectory) {
        // ...read all its files and push them to the return list.
        result.push(...this._readDirectory(item.path));
      } else {
        // ...otherwise, just add it to the return list.
        result.push(item.path);
      }
    });
    // Return the findings.
    return result;
  }
  /**
   * Compresses a file using Gzip.
   * @param {string} filepath The path to the file to compress.
   * @return {Promise<ProjextRollupCompressionPluginEntry,Error>}
   * @access protected
   * @ignore
   */
  _compressFile(filepath) {
    // Get a deferred for the stats entry.
    const statsDeferred = deferred();
    // Add the deferred promise on the stats.
    this._options.stats(statsDeferred.promise);
    return new Promise((resolve, reject) => {
      // Define the path for the compressed file.
      const newFilepath = `${filepath}.gz`;
      // Read the file.
      fs.createReadStream(filepath)
      .on('error', reject)
      // Compress the file.
      .pipe(zlib.createGzip())
      // Write the compressed file.
      .pipe(fs.createWriteStream(newFilepath))
      .on('error', (error) => {
        statsDeferred.reject(error);
        reject(error);
      })
      .on('close', () => {
        // Resolve the deferred for the stats entry.
        statsDeferred.resolve({
          plugin: this.name,
          filepath: newFilepath,
        });
        // Resolve the method's promise.
        resolve({
          original: filepath,
          compressed: newFilepath,
        });
      });
    });
  }
}
/**
 * Shorthand method to create an instance of {@link ProjextRollupCompressionPlugin}.
 * @param {ProjextRollupCompressionPluginOptions} options
 * The options to customize the plugin behaviour.
 * @param {string} name
 * The name of the plugin's instance.
 * @return {ProjextRollupCompressionPlugin}
 */
const compression = (options, name) => new ProjextRollupCompressionPlugin(options, name);

module.exports = {
  ProjextRollupCompressionPlugin,
  compression,
};