Home Reference Source

src/services/configurations/pluginsConfiguration.js

const fs = require('fs-extra');
const postcss = require('postcss');
const LazyResult = require('postcss/lib/lazy-result');
const postcssModules = require('postcss-modules');
const builtinModules = require('builtin-modules');
const nodeSass = require('node-sass');
const { provider } = require('jimple');
const ConfigurationFile = require('../../abstracts/configurationFile');
/**
 * This service creates a configuration with all the settings for the plugins this build engine
 * uses.
 * @extends {ConfigurationFile}
 */
class RollupPluginSettingsConfiguration extends ConfigurationFile {
  /**
   * @param {Logger}             appLogger          To send to the plugins that support a logger.
   * @param {BabelConfiguration} babelConfiguration To get the target Babel configuration.
   * @param {BabelHelper}        babelHelper        To disable the `modules` setting of the Babel
   *                                                env preset for a target, as Rollup requires.
   * @param {Events}             events             To reduce the settings.
   * @param {Object}             packageInfo        To get the dependencies and define them as
   *                                                externals for Node targets.
   * @param {PathUtils}          pathUtils          Require by `ConfigurationFile` in order to
   *                                                build the path to the overwrite file. It's
   *                                                also used to build the paths of SSL
   *                                                certificates the dev server plugin may use.
   * @param {Object}             rollupPluginInfo   To get the name of the _"sub packages"_ the
   *                                                plugin provides and that should be marked as
   *                                                external.
   * @param {TargetsHTML}        targetsHTML        To get the path to a target HTML template
   *                                                file.
   */
  constructor(
    appLogger,
    babelConfiguration,
    babelHelper,
    events,
    packageInfo,
    pathUtils,
    rollupPluginInfo,
    targetsHTML
  ) {
    super(pathUtils, 'rollup/plugins.config.js');
    /**
     * A local reference for the `appLogger` service.
     * @type {Logger}
     */
    this.appLogger = appLogger;
    /**
     * A local reference for the `babelConfiguration` service.
     * @type {BabelConfiguration}
     */
    this.babelConfiguration = babelConfiguration;
    /**
     * A local reference for the `babelHelper` service.
     * @type {BabelHelper}
     */
    this.babelHelper = babelHelper;
    /**
     * A local reference for the `events` service.
     * @type {Events}
     */
    this.events = events;
    /**
     * The project `package.json` information.
     * @type {Object}
     */
    this.packageInfo = packageInfo;
    /**
     * A local reference for the plugin information.
     * @type {RollupPluginInfo}
     */
    this.rollupPluginInfo = rollupPluginInfo;
    /**
     * A local reference for the `targetsHTML` service.
     * @type {TargetsHTML}
     */
    this.targetsHTML = targetsHTML;
  }
  /**
   * Creates the plugins settings for the required target.
   * This method uses the reducer events `rollup-plugin-settings-configuration-for-node` or
   * `rollup-plugin-settings-configuration-for-browser`, depending on the target type, and then
   * `rollup-plugin-settings-configuration`. The event receives the configuration object, the
   * `params` and it expects an updated configuration object on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @param {Function}                  stats  A function to send to the plugins that support
   *                                           logging stats entries.
   * @return {Object}
   */
  createConfig(params, stats) {
    // Get the external settings.
    const external = this._getExternalSettings(params);
    // Define the basic settings.
    const settings = {
      external,
      globals: this._getGlobalVariablesSettings(params, external.external),
      resolve: this._getResolveSettings(params, stats),
      extraWatch: this._getExtraWatchSettings(params, stats),
      moduleReplace: this._getModuleReplaceSettings(params, stats),
      babel: this._getBabelSettings(params, stats),
      commonjs: this._getCommonJSSettings(params, stats),
      sass: this._getSASSSettings(params, stats),
      css: this._getCSSSettings(params, stats),
      stylesheetAssets: this._getStyleheetAssetsSettings(params, stats),
      stylesheetModulesFixer: this._getStylesheetModulesFixerSettings(params, stats),
      html: this._getHTMLSettings(params, stats),
      json: this._getJSONSettings(params, stats),
      urls: this._getURLsSettings(params, stats),
      watch: this._getWatchSettings(params, stats),
      terser: this._getTerserSettings(params, stats),
      compression: this._getCompressionSettings(params, stats),
      copy: this._getCopySettings(params, stats),
      visualizer: this._getVisualizerSettings(params, stats),
      statsLog: this._getStatsLogSettings(params),
    };

    let eventName;
    // Based on the target type, define the event reducer name and add specific settings.
    if (params.target.is.node) {
      eventName = 'rollup-plugin-settings-configuration-for-node';
      settings.nodeRunner = this._getNodeRunnerSettings(params);
      settings.stylesheetAssetsHelper = this._getStyleheetAssetsHelperSettings(params);
    } else {
      eventName = 'rollup-plugin-settings-configuration-for-browser';
      settings.template = this._getTemplateSettings(params, stats);
      settings.devServer = this._getDevServerSettings(params);
    }
    // Return the reduced settings.
    return this.events.reduce(
      [eventName, 'rollup-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the external dependencies.
   * This method uses the reducer event `rollup-external-plugin-settings-configuration-for-browser`
   * or `rollup-external-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-external-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getExternalSettings(params) {
    const { target, buildType } = params;
    // Define the list of modules that shuld be external.
    const external = [];

    // Push modules the target is excluding by configuration.
    if (target.excludeModules) {
      external.push(...target.excludeModules);
    }
    // If the target is for Node...
    if (target.is.node) {
      // Push all the Node builtin modules
      external.push(...builtinModules);
      // Push the plugin _"sub modules"_.
      external.push(...this.rollupPluginInfo.external.map((dependencyName) => (
        `${this.rollupPluginInfo.name}/${dependencyName}`
      )));
      // Push the production dependencies.
      external.push(...Object.keys(this.packageInfo.dependencies));
      // And if the build is for development, push the dev dependencies too.
      if (buildType === 'development') {
        external.push(...Object.keys(this.packageInfo.devDependencies));
      }
    }
    // Wrap the list on an object.
    const settings = { external };

    const eventName = target.is.node ?
      'rollup-external-plugin-settings-configuration-for-node' :
      'rollup-external-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-external-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the global variables on the bundle. They are actually based on the
   * modules that are handled as external.
   * This method uses the reducer event
   * `rollup-global-variables-plugin-settings-configuration-for-browser` or
   * `rollup-global-variables-plugin-settings-configuration-for-node`, depending on the target
   * type, and then `rollup-global-variables-plugin-settings-configuration`. The event receives
   * the settings, the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params   A dictionary generated by the top service building
   *                                             the configuration and that includes things like the
   *                                             target information, its entry settings, output
   *                                             paths, etc.
   * @param {Array}                     external The list of modules that will be handled as
   *                                             external dependencies.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getGlobalVariablesSettings(params, external) {
    // Define the settings dictionary.
    const settings = {};
    // Loop all the external modules.
    external.forEach((name) => {
      // Format the module name into a variable.
      const globalName = name
      .replace(/\//g, '-')
      .replace(/-(\w)/g, (match, letter) => letter.toUpperCase());
      // Set it on the dictionary using the module name as key.
      settings[name] = globalName;
    });

    const eventName = params.target.is.node ?
      'rollup-global-variables-settings-configuration-for-node' :
      'rollup-global-variables-settings-configuration-for-browser';

    // return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-global-variables-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `resolve` plugin.
   * This method uses the reducer event `rollup-resolve-plugin-settings-configuration-for-browser`
   * or `rollup-resolve-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-resolve-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getResolveSettings(params) {
    const { target } = params;
    const settings = {
      // Add just for basic JS files and JSON.
      extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'],
      browser: target.is.browser,
      preferBuiltins: target.is.node,
    };

    const eventName = target.is.node ?
      'rollup-resolve-plugin-settings-configuration-for-node' :
      'rollup-resolve-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-resolve-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `extraWatch` plugin, which is a projext's plugin that takes
   * care watching additional files and reloading the bundle when any of them change.
   * This method uses the reducer event
   * `rollup-extra-watch-plugin-settings-configuration-for-browser` or
   * `rollup-extra-watch-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-extra-watch-plugin-settings-configuration`. The event receives the list of files,
   * the `params` and expects a new list on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getExtraWatchSettings(params) {
    const { additionalWatch, input, target } = params;
    const settings = [
      input,
      ...additionalWatch.map((filepath) => this.pathUtils.join(filepath)),
    ];

    const eventName = target.is.node ?
      'rollup-extra-watch-plugin-settings-configuration-for-node' :
      'rollup-extra-watch-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-extra-watch-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `moduleReplace` plugin, which is a projext's plugin that allows
   * the replacement of strings on specific modules.
   * For example, `core-js` has a module that does `const location = `, which causes an
   * inifinite redirection loop, so (for now), the plugin will take care of changing `location`
   * to `_location`.
   * This method uses the reducer event
   * `rollup-module-replace-plugin-settings-configuration-for-browser` or
   * `rollup-module-replace-plugin-settings-configuration-for-node`, depending on the target
   * type, and then `rollup-module-replace-plugin-settings-configuration`. The event receives the
   * plugin settings, the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getModuleReplaceSettings(params) {
    const { target, buildType } = params;
    const settings = {
      instructions: [],
      sourceMap: !!(target.sourceMap && target.sourceMap[buildType]),
    };

    if (target.is.browser) {
      settings.instructions.push({
        module: /node_modules\/core-js\/internals\/task\.js$/i,
        search: /(\s*)location(\.|\s*=)/i,
        replace: '$1_location$2',
      });
    }

    const eventName = target.is.node ?
      'rollup-module-replace-plugin-settings-configuration-for-node' :
      'rollup-module-replace-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-module-replace-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `babel` plugin.
   * This method uses the reducer event `rollup-babel-plugin-settings-configuration-for-browser`
   * or `rollup-babel-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-babel-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getBabelSettings(params) {
    const { target, targetRules } = params;
    // Get the rule for JS files.
    const jsRule = targetRules.js.getRule();
    // Get the target Babel configuration.
    const baseConfiguration = this.babelConfiguration.getConfigForTarget(target);
    // Disable the `modules` feature for the `env` preset.
    const configuration = this.babelHelper.disableEnvPresetModules(baseConfiguration);
    // Define the plugin settings.
    const settings = Object.assign(
      {},
      configuration,
      {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
        // The plugin doesn't support RegExp, so it will use the glob patterns.
        include: [...jsRule.files.glob.include],
        exclude: [...jsRule.files.glob.exclude],
      }
    );

    const eventName = target.is.node ?
      'rollup-babel-plugin-settings-configuration-for-node' :
      'rollup-babel-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-babel-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `commonjs` plugin.
   * This method uses the reducer event `rollup-commonjs-plugin-settings-configuration-for-browser`
   * or `rollup-commonjs-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-commonjs-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getCommonJSSettings(params) {
    // Define the plugin settings.
    const settings = {
      include: [
        new RegExp(this.pathUtils.join('config'), 'i'),
        /node_modules\//i,
      ],
    };

    const eventName = params.target.is.node ?
      'rollup-commonjs-plugin-settings-configuration-for-node' :
      'rollup-commonjs-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-commonjs-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `sass` plugin.
   * This method uses the reducer event `rollup-sass-plugin-settings-configuration-for-browser`
   * or `rollup-sass-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-sass-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getSASSSettings(params) {
    const { target, paths, targetRules } = params;
    // Get the rule for SCSS files.
    const scssRule = targetRules.scss.getRule();

    // Define the plugin settings.
    const settings = {
      include: [...scssRule.files.include],
      exclude: [...scssRule.files.exclude],
      runtime: nodeSass,
      options: {
        sourceMapEmbed: true,
        outputStyle: 'compressed',
        includePaths: ['node_modules'],
        data: '',
      },
      processor: this._getStylesProcessor(target.css.modules),
      failOnError: true,
    };

    // If the CSS should be injected, turn on the flag.
    if (target.css.inject) {
      settings.insert = true;
    } else if (target.is.browser) {
      // If the CSS shouldn't be injected and the target is for browser, define a bundle path.
      settings.output = `${target.paths.build}/${paths.css}`;
    } else {
      // Otherwise, it means that is a Node target, so just return the code.
      settings.output = false;
    }

    const eventName = target.is.node ?
      'rollup-sass-plugin-settings-configuration-for-node' :
      'rollup-sass-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-sass-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `css` plugin.
   * This method uses the reducer event `rollup-css-plugin-settings-configuration-for-browser`
   * or `rollup-css-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-css-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @param {Function}                  stats  A function to send to the plugins that support
   *                                           logging stats entries.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getCSSSettings(params, stats) {
    const { target, paths, targetRules } = params;
    // Get the rule for SCSS files.
    const cssRule = targetRules.css.getRule();
    // Define the plugin settings.
    const settings = {
      include: [...cssRule.files.include],
      exclude: [...cssRule.files.exclude],
      processor: this._getStylesProcessor(false, { map: true }),
      stats,
    };

    // If the CSS should be injected, turn on the flag.
    if (target.css.inject) {
      settings.insert = true;
    } else if (target.is.browser) {
      // If the CSS shouldn't be injected and the target is for browser, define a bundle path.
      settings.output = `${target.paths.build}/${paths.css}`;
    } else {
      // Otherwise, it means that is a Node target, so just return the code.
      settings.output = false;
    }

    const eventName = target.is.node ?
      'rollup-css-plugin-settings-configuration-for-node' :
      'rollup-css-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-css-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `stylesheetAssets` plugin, which is a projext's plugin that takes
   * care of copying and fixing the paths for the files linked on stylesheets.
   * This method uses the reducer event
   * `rollup-stylesheet-assets-plugin-settings-configuration-for-browser` or
   * `rollup-stylesheet-assets-plugin-settings-configuration-for-node`, depending on the target
   * type, and then `rollup-stylesheet-assets-plugin-settings-configuration`. The event receives
   * the settings, the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @param {Function}                  stats  A function to send to the plugins that support
   *                                           logging stats entries.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getStyleheetAssetsSettings(params, stats) {
    const {
      target,
      paths,
      output,
    } = params;
    // Get the rules for common assets.
    const assetsRules = this._getAssetsRules(params);
    /**
     * Define the file the plugin will parse. If the target injects the CSS or is a Node target,
     * use the main JS file, otherwise, the path for bundling CSS.
     */
    const stylesheet = target.css.inject || target.is.node ?
      output.file :
      `${target.paths.build}/${paths.css}`;

    // Define the plugin settings.
    const settings = {
      stylesheet,
      stats,
      urls: [
        assetsRules.fonts,
        assetsRules.images,
      ],
    };

    const eventName = target.is.node ?
      'rollup-stylesheet-assets-plugin-settings-configuration-for-node' :
      'rollup-stylesheet-assets-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-stylesheet-assets-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `stylesheetModulesFixer` plugin, which is a projext's plugin that
   * parses modules for CSS (stylsheet that Rollup transforms into ES modules) and if they inject
   * the CSS on the browser or they code was moved to a separate bundle, replace their default
   * exports for the named export for CSS Modules locals.
   * This method uses the reducer event
   * `rollup-stylesheet-modules-fixer-plugin-settings-configuration-for-browser` or
   * `rollup-stylesheet-modules-fixer-plugin-settings-configuration-for-node`, depending on the
   * target type, and then `rollup-stylesheet-modules-fixer-plugin-settings-configuration`. The
   * event receives the settings, the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getStylesheetModulesFixerSettings(params) {
    const { target, targetRules } = params;
    // Get both SCSS and CSS rules.
    const scssRule = targetRules.scss.getRule();
    const cssRule = targetRules.css.getRule();
    // Define the plugin settings.
    const settings = {
      include: [
        ...scssRule.files.include,
        ...cssRule.files.include,
      ],
      exclude: [
        ...scssRule.files.exclude,
        ...cssRule.files.exclude,
      ],
    };

    const eventName = target.is.node ?
      'rollup-stylesheet-modules-fixer-plugin-settings-configuration-for-node' :
      'rollup-stylesheet-modules-fixer-plugin-settings-configuration-for-browser';

    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-stylesheet-modules-fixer-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `stylesheetAssets` helper plugin, which is a projext's plugin
   * that wraps the default exports of stylesheets Rollup transforms into ES modules so
   * the `stylesheetAssets` plugin can find them and parse them.
   * This method uses the reducer event
   * `rollup-stylesheet-assets-helper-plugin-settings-configuration`, it receives the settings,
   * the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getStyleheetAssetsHelperSettings(params) {
    const { targetRules } = params;
    // Get both SCSS and CSS rules.
    const scssRule = targetRules.scss.getRule();
    const cssRule = targetRules.css.getRule();
    // Define the plugin settings.
    const settings = {
      include: [
        ...scssRule.files.include,
        ...cssRule.files.include,
      ],
      exclude: [
        ...scssRule.files.exclude,
        ...cssRule.files.exclude,
      ],
    };
    // Return the reduced configuration.
    return this.events.reduce(
      'rollup-stylesheet-assets-helper-plugin-settings-configuration',
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `html` plugin.
   * This method uses the reducer event `rollup-html-plugin-settings-configuration-for-browser`
   * or `rollup-html-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-html-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getHTMLSettings(params) {
    /**
     * There are no settings, but because the plugin is used, the method reduces the empty
     * configuration so other plugins/services can update it if needed.
     */
    const settings = {};

    const eventName = params.target.is.node ?
      'rollup-html-plugin-settings-configuration-for-node' :
      'rollup-html-plugin-settings-configuration-for-browser';
    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-html-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `json` plugin.
   * This method uses the reducer event `rollup-json-plugin-settings-configuration-for-browser`
   * or `rollup-json-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-json-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getJSONSettings(params) {
    /**
     * There are no settings, but because the plugin is used, the method reduces the empty
     * configuration so other plugins/services can update it if needed.
     */
    const settings = {};

    const eventName = params.target.is.node ?
      'rollup-json-plugin-settings-configuration-for-node' :
      'rollup-json-plugin-settings-configuration-for-browser';
    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-json-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `urls` plugin, which is a projext's plugin that transforms
   * files matching a filter and copies its contents into a new file on the output directory,
   * then it replaces its default export with a URL for them.
   * This method uses the reducer event `rollup-urls-plugin-settings-configuration-for-browser` or
   * `rollup-urls-plugin-settings-configuration-for-node`, depending on the target type, and then
   * `rollup-urls-plugin-settings-configuration`. The event receives the settings, the `params`
   * and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @param {Function}                  stats  A function to send to the plugins that support
   *                                           logging stats entries.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getURLsSettings(params, stats) {
    const { target } = params;
    // Get the rules for common assets.
    const assetsRules = this._getAssetsRules(params);
    // Define the plugin settings.
    const settings = {
      urls: [
        assetsRules.fonts,
        assetsRules.images,
        assetsRules.favicon,
      ],
      stats,
    };

    const eventName = target.is.node ?
      'rollup-urls-plugin-settings-configuration-for-node' :
      'rollup-urls-plugin-settings-configuration-for-browser';
    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-urls-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `template` plugin, which is a projext's plugin that generates
   * and HTML file and injects a list of JS and CSS files.
   * This method uses the reducer event `rollup-html-plugin-settings-configuration-for-browser` or
   * `rollup-html-plugin-settings-configuration-for-node`, depending on the target type, and then
   * `rollup-html-plugin-settings-configuration`. The event receives the settings, the `params`
   * and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @param {Function}                  stats  A function to send to the plugins that support
   *                                           logging stats entries.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getTemplateSettings(params, stats) {
    const {
      target,
      paths,
      buildType,
      output,
    } = params;
    // Get the rules for common assets.
    const assetsRules = this._getAssetsRules(params);
    const script = {
      src: `/${paths.js}`,
      async: 'async',
      type: output.format === 'es' ? 'module' : 'text/javascript',
    };
    // Define the plugin settings.
    const settings = {
      template: this.targetsHTML.getFilepath(target, false, buildType),
      output: `${target.paths.build}/${target.html.filename}`,
      stylesheets: target.css.inject ?
        [] :
        [`/${paths.css}`],
      scripts: [script],
      urls: [
        assetsRules.images,
        assetsRules.favicon,
      ],
      stats,
    };
    // Return the reduced configuration.
    return this.events.reduce(
      'rollup-template-plugin-settings-configuration',
      settings,
      params
    );
  }
  /**
   * Defines the settings for the Rollup watcher.
   * This method uses the reducer event `rollup-watch-plugin-settings-configuration-for-browser` or
   * `rollup-watch-plugin-settings-configuration-for-node`, depending on the target type, and then
   * `rollup-watch-plugin-settings-configuration`. The event receives the settings, the `params`
   * and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getWatchSettings(params) {
    // Define the plugin settings.
    const settings = {
      clearScreen: false,
    };

    const eventName = params.target.is.node ?
      'rollup-watch-plugin-settings-configuration-for-node' :
      'rollup-watch-plugin-settings-configuration-for-browser';
    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-watch-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `devServer` plugin, which is a projext's plugin for running web
   * apps while on development.
   * This method uses the reducer event `rollup-dev-server-plugin-settings-configuration`, it
   * receives the settings, the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getDevServerSettings(params) {
    const { target } = params;
    const { devServer } = target;
    // Define the basic settings.
    const settings = {
      host: devServer.host,
      port: devServer.port,
      contentBase: target.paths.build,
      historyApiFallback: !!devServer.historyApiFallback,
      open: !!devServer.open,
      https: null,
      logger: this.appLogger,
    };
    // Loop the SSL settings and load the file for those which have a valid file path.
    const sslSettings = {};
    /**
     * The idea of this flag is that if none of the SSL settings has value, the `ssl` setting
     * won't be added.
     */
    let atLeastOneSSLSetting = false;
    [
      'key',
      'cert',
      'ca',
    ].forEach((sslSettingName) => {
      const file = devServer.ssl[sslSettingName];
      // Make sure the setting value is a string.
      if (typeof file === 'string') {
        const filepath = this.pathUtils.join(file);
        // Verify that the file exists.
        if (fs.pathExistsSync(filepath)) {
          // Turn on the flag.
          atLeastOneSSLSetting = true;
          // Set the value of the setting with the contents of the file.
          sslSettings[sslSettingName] = fs.readFileSync(filepath, 'utf-8');
        }
      }
    });
    /**
     * If there's at least one setting implemented, set it for the plugin to use, otherwise keep
     * it as `undefined`.
     */
    if (atLeastOneSSLSetting) {
      settings.https = sslSettings;
    }
    /**
     * Build the _"proxied settings"_ in case the server is behind a proxy. This will tell the
     * dev server plugin to build a URL using these settings and use it for opening the browser
     * and logging messages, instead of the base one.
     */
    if (devServer.proxied.enabled) {
      /**
       * Define the proxied host by checking if one was defined on the settings. If there's no
       * host, it will fallback to the one on the base settings.
       */
      const proxiedHost = devServer.proxied.host === null ?
        settings.host :
        devServer.proxied.host;
      /**
       * Define whether to use HTTPS by checking the settings. If it wasn't specified, it will
       * only set it to `true` if one of the SSL files for the base config exists.
       */
      const proxiedHTTPS = devServer.proxied.https === null ?
        atLeastOneSSLSetting :
        devServer.proxied.https;
      // Add the `proxied` settings to the object to be returned.
      settings.proxied = {
        host: proxiedHost,
        https: proxiedHTTPS,
      };
    }

    // Return the reduced configuration.
    return this.events.reduce(
      'rollup-dev-server-plugin-settings-configuration',
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `terser` plugin.
   * This method uses the reducer event `rollup-terser-plugin-settings-configuration-for-browser`
   * or `rollup-terser-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-terser-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getTerserSettings(params) {
    /**
     * There are no settings, but because the plugin is used, the method reduces the empty
     * configuration so other plugins/services can update it if needed.
     */
    const settings = {};

    const eventName = params.target.is.node ?
      'rollup-terser-plugin-settings-configuration-for-node' :
      'rollup-terser-plugin-settings-configuration-for-browser';
    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-terser-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `compression` plugin, which is a projext's plugin that takes care
   * of compressing the generated assets using Gzip.
   * This method uses the reducer event
   * `rollup-compression-plugin-settings-configuration-for-browser` or
   * `rollup-compression-plugin-settings-configuration-for-node`, depending on the target type,
   * and then `rollup-compression-plugin-settings-configuration`. The event receives the settings,
   * the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @param {Function}                  stats  A function to send to the plugins that support
   *                                           logging stats entries.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getCompressionSettings(params, stats) {
    const { target } = params;
    // Get the rule to find ALL generated assets.
    const rule = this._getARuleForAllTheAssets(params);
    // Define the plugin settings.
    const settings = {
      folder: target.paths.build,
      include: rule.include,
      exclude: rule.exclude,
      stats,
    };

    const eventName = target.is.node ?
      'rollup-compression-plugin-settings-configuration-for-node' :
      'rollup-compression-plugin-settings-configuration-for-browser';
    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-compression-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `copy` plugin, which is a projext's plugin that takes care
   * of copying specific files during the bundling process.
   * This method uses the reducer event
   * `rollup-copy-plugin-settings-configuration-for-browser` or
   * `rollup-copy-plugin-settings-configuration-for-node`, depending on the target type,
   * and then `rollup-copy-plugin-settings-configuration`. The event receives the settings,
   * the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @param {Function}                  stats  A function to send to the plugins that support
   *                                           logging stats entries.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getCopySettings(params, stats) {
    const { target, copy: files } = params;
    // Define the plugin settings.
    const settings = {
      files,
      stats,
    };

    const eventName = target.is.node ?
      'rollup-copy-plugin-settings-configuration-for-node' :
      'rollup-copy-plugin-settings-configuration-for-browser';
    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-copy-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `visualizer` plugin.
   * This method uses the reducer event
   * `rollup-visualizer-plugin-settings-configuration-for-browser` or
   * `rollup-visualizer-plugin-settings-configuration-for-node`, depending on the target type, and
   * then `rollup-visualizer-plugin-settings-configuration`. The event receives the settings, the
   * `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getVisualizerSettings(params) {
    const { target } = params;
    const targetFilename = target.name.replace(/\//g, '-');
    const settings = {
      filename: `${target.paths.build}/${targetFilename}-stats-visualizer.html`,
      open: true,
    };

    const eventName = target.is.node ?
      'rollup-visualizer-plugin-settings-configuration-for-node' :
      'rollup-visualizer-plugin-settings-configuration-for-browser';

    return this.events.reduce(
      [eventName, 'rollup-visualizer-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `stats` plugin `log` _"sub plugin"_. The `stats` plugin is a
   * projext plugin that allows other plugins and services to add stats entries to eventually
   * show a report table when the bundle process finishes. The `log` _"sub plugin"_ is the method
   * that you would add to the Rollup plugins queue and that it actually logs the report table.
   * This method uses the reducer event
   * `rollup-stats-plugin-settings-configuration-for-browser` or
   * `rollup-stats-plugin-settings-configuration-for-node`, depending on the target type,
   * and then `rollup-stats-plugin-settings-configuration`. The event receives the settings,
   * the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getStatsLogSettings(params) {
    const { target, paths, buildType } = params;
    // As the first extra entry, add the bundle generated by Rollup.
    const extraEntries = [
      {
        plugin: 'rollup',
        filepath: `${target.paths.build}/${paths.js}`,
      },
    ];
    // If the target implements source maps, add the entry for the source map.
    if (target.sourceMap && target.sourceMap[buildType]) {
      extraEntries.push({
        plugin: 'rollup',
        filepath: `${target.paths.build}/${paths.js}.map`,
      });
    }
    /**
     * If the target is for browser and is not injecting the styles, assume the `sass` plugin
     * has generated a bundle, so add and entry for it.
     * @todo This should be on the `processor` to be able to know if it was the `sass` or `css`
     *       plugin.
     */
    if (target.is.browser && !target.css.inject) {
      extraEntries.push({
        plugin: 'rollup-plugin-sass',
        filepath: `${target.paths.build}/${paths.css}`,
      });
    }
    // Define the plugin settings.
    const settings = {
      extraEntries,
    };

    const eventName = target.is.node ?
      'rollup-stats-plugin-settings-configuration-for-node' :
      'rollup-stats-plugin-settings-configuration-for-browser';
    // Return the reduced configuration.
    return this.events.reduce(
      [eventName, 'rollup-stats-plugin-settings-configuration'],
      settings,
      params
    );
  }
  /**
   * Defines the settings for the `nodeRunner` plugin, which is a projext's plugin for running Node
   * apps while on development.
   * This method uses the reducer event `rollup-node-runner-plugin-settings-configuration`, it
   * receives the settings, the `params` and expects new settings on return.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getNodeRunnerSettings(params) {
    const { output } = params;
    // Define the plugin settings.
    const settings = {
      file: output.file,
      logger: this.appLogger,
      inspect: params.target.inspect,
    };
    // Return the reduced configuration.
    return this.events.reduce(
      'rollup-node-runner-plugin-settings-configuration',
      settings,
      params
    );
  }
  /**
   * This a helper method that generates a dictionary of {@link ProjextRollupPluginURL}
   * definitions for common assets (fonts, images and the favicon). These definitions can be used
   * on different plugin settings.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getAssetsRules(params) {
    const { target, targetRules, paths } = params;
    const commonFontsRule = targetRules.fonts.common.getRule();
    const svgFontsRule = targetRules.fonts.svg.getRule();
    const imagesRule = targetRules.images.getRule();
    const faviconRule = targetRules.favicon.getRule();

    return {
      fonts: {
        include: [
          ...commonFontsRule.files.include,
          ...svgFontsRule.files.include,
        ],
        exclude: [
          ...commonFontsRule.files.exclude,
          ...svgFontsRule.files.exclude,
        ],
        output: `${target.paths.build}/${paths.fonts}`,
        url: `/${paths.fonts}`,
      },
      images: {
        include: [...imagesRule.files.include],
        exclude: [...imagesRule.files.exclude],
        output: `${target.paths.build}/${paths.images}`,
        url: `/${paths.images}`,
      },
      favicon: {
        include: [...faviconRule.files.include],
        exclude: [...faviconRule.files.exclude],
        output: `${target.paths.build}/[name].[ext]`,
        url: '/[name].[ext]',
      },
    };
  }
  /**
   * This a helper method that generates a set of `include` and `exclude` rules to match all
   * possible generated assets on the output directory.
   * @param {RollupConfigurationParams} params A dictionary generated by the top service building
   *                                           the configuration and that includes things like the
   *                                           target information, its entry settings, output
   *                                           paths, etc.
   * @return {Object}
   * @access protected
   * @ignore
   */
  _getARuleForAllTheAssets(params) {
    const { target } = params;
    const extensions = [
      'js',
      'jsx',
      'ts',
      'tsx',
      'css',
      'html',
      'map',
      'woff',
      'woff2',
      'ttf',
      'eot',
      'jpg',
      'jpeg',
      'png',
      'gif',
      'svg',
      'ico',
    ];
    const extensionsStr = extensions.join('|');
    const extensionsRegex = `\\.(?:${extensionsStr})$`;
    return {
      include: [new RegExp(`${target.paths.build}/.*?${extensionsRegex}`, 'i')],
      exclude: [],
    };
  }
  /**
   * Helper method for the stylsheet processor. It searches and retrieves a source map comment
   * from a given CSS code.
   * @param {string} code The CSS code where the source map will be retrieved from.
   * @return {?string} If the source map it's found, it will return it, otherwise, it will return
   *                   `null`.
   * @access protected
   * @ignore
   */
  _getSourceMap(code) {
    const regex = /(\/\*# sourceMappingURL=.*? \*\/)/i;
    const match = regex.exec(code);
    let result = null;
    if (match) {
      [result] = match;
    }

    return result;
  }
  /**
   * This is a custom stylesheet processor for the `sass` and `css` plugin. It makes sure all
   * generated styles have a source map that plugins like `stylesheetAssets` can use. It also
   * takes care of implementing CSS modules if the target has them enabled.
   * @param {boolean} modules                      Whether or not CSS modules are enabled.
   * @param {Object}  [processorOptions={}]        Custom options for the processor.
   * @param {boolean} [processorOptions.map=false] Whether or not a source map should be generated
   *                                               for the stylesheet. When generated with the
   *                                               `sass` plugin, the source map already comes
   *                                               with the code; but when generated with the
   *                                               `css` plugin, it should be added.
   * @return {function}
   * @access protected
   * @ignore
   */
  _getStylesProcessor(modules, processorOptions = {}) {
    // Merge the default options with the received custom options.
    const options = Object.assign(
      {},
      {
        map: false,
        /**
         * If the file already has a source map, this value will be filled with the path to
         * the file being processed, as required by `postcss`.
         */
        from: undefined,
      },
      processorOptions
    );
    // Return the processor function.
    return (css, filepath) => {
      let map;
      /**
       * If the stylesheet needs a source map, complete the `from` option; otherwise, read it from
       * the code.
       * The reason the source map is being extracted before processing the stylesheet is because
       * `postcss` may update it and the reference for the original sources may get lost.
       */
      if (options.map) {
        options.from = filepath;
      } else {
        map = this._getSourceMap(css) || '';
      }
      // Define the variable that will be used to store the CSS modules locals if enabled.
      let locals;
      // Define the list of plugins for `postcss`.
      const plugins = [];
      /**
       * If CSS modules are enabled for the target, push the plugin with a callback to obtain the
       * locals names.
       */
      if (modules) {
        plugins.push(postcssModules({
          getJSON: (filename, json) => {
            locals = json;
          },
        }));
      }

      let processor;
      // Avoid using `postcss` if not needed
      if (plugins.length || options.map || options.from) {
        // If we actually have plugins...
        if (plugins.length) {
          // Let's use `postcss` like you would normally do.
          processor = postcss(plugins)
          // Process the stylesheet code.
          .process(css, options);
        } else {
          /**
           * But if we are using `postcss` just to get the source map, let's wrap it on a
           * `LazyResult`. It seems to be the only way to hide the warning that we are not
           * using plugins.
           */
          processor = new LazyResult(postcss(), css, options);
        }
      } else {
        processor = Promise.resolve({
          css: css.replace(map, '').trim(),
        });
      }

      return processor
      .then((processed) => {
        // Add the source map if needed.
        const cssCode = options.map ?
          `${processed.css}\n` :
          `${processed.css}\n\n${map}\n`;
        // Define the return object.
        let result;
        /**
         * If CSS modules are enabled for the target, return a dictionary with the code and the
         * locals, otherwise just return the code.
         */
        if (modules) {
          result = {
            css: cssCode,
            locals,
          };
        } else {
          result = cssCode;
        }

        return result;
      });
    };
  }
}
/**
 * The service provider that once registered on the app container will set an instance of
 * `RollupPluginSettingsConfiguration` as the `rollupPluginSettingsConfiguration` service.
 * @example
 * // Register it on the container
 * container.register(rollupPluginSettingsConfiguration);
 * // Getting access to the service instance
 * const rollupPluginSettingsConfiguration = container.get('rollupPluginSettingsConfiguration');
 * @type {Provider}
 */
const rollupPluginSettingsConfiguration = provider((app) => {
  app.set('rollupPluginSettingsConfiguration', () => new RollupPluginSettingsConfiguration(
    app.get('appLogger'),
    app.get('babelConfiguration'),
    app.get('babelHelper'),
    app.get('events'),
    app.get('packageInfo'),
    app.get('pathUtils'),
    app.get('rollupPluginInfo'),
    app.get('targetsHTML')
  ));
});

module.exports = {
  RollupPluginSettingsConfiguration,
  rollupPluginSettingsConfiguration,
};