Home Reference Source

src/services/configurations/browserProductionConfiguration.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const { provider } = require('jimple');
const ConfigurationFile = require('../../abstracts/configurationFile');
const { ProjextWebpackRuntimeDefinitions } = require('../../plugins');
/**
 * Creates the specifics of a Webpack configuration for a browser target production build.
 * @extends {ConfigurationFile}
 */
class WebpackBrowserProductionConfiguration extends ConfigurationFile {
  /**
   * Class constructor.
   * @param {Events}                   events                  To reduce the configuration.
   * @param {PathUtils}                pathUtils                Required by `ConfigurationFile`
   *                                                            in order to build the path to the
   *                                                            overwrite file.
   * @param {TargetsHTML}              targetsHTML              The service in charge of generating
   *                                                            a default HTML file in case the
   *                                                            target doesn't have one.
   * @param {WebpackBaseConfiguration} webpackBaseConfiguration The configuration this one will
   *                                                            extend.
   */
  constructor(
    events,
    pathUtils,
    targetsHTML,
    webpackBaseConfiguration
  ) {
    super(
      pathUtils,
      [
        'config/webpack/browser.production.config.js',
        'config/webpack/browser.config.js',
      ],
      true,
      webpackBaseConfiguration
    );
    /**
     * A local reference for the `events` service.
     * @type {Events}
     */
    this.events = events;
    /**
     * A local reference for the `targetsHTML` service.
     * @type {TargetsHTML}
     */
    this.targetsHTML = targetsHTML;
  }
  /**
   * Create the configuration with the `entry`, the `output` and the plugins specifics for a
   * browser target production build.
   * This method uses the reducer events `webpack-browser-production-configuration` and
   * `webpack-browser-configuration`. It sends the configuration, the received `params` and
   * expects a configuration on return.
   * @param {WebpackConfigurationParams} 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}
   */
  createConfig(params) {
    const {
      definitions,
      copy,
      entry,
      target,
      output,
      additionalWatch,
      analyze,
    } = params;
    // Define the basic stuff: entry, output and mode.
    const config = {
      entry,
      output: {
        path: `./${target.folders.build}`,
        filename: output.js,
        chunkFilename: output.jsChunks,
        publicPath: '/',
      },
      mode: 'production',
    };
    // If the target has source maps enabled...
    if (target.sourceMap.production) {
      config.devtool = 'source-map';
    }
    // Enable or not uglification for the target bundle.
    if (target.uglifyOnProduction) {
      config.optimization = {
        minimizer: [
          new TerserPlugin({
            sourceMap: !!target.sourceMap.production,
          }),
        ],
      };
    } else {
      config.optimization = {
        minimize: false,
      };
    }

    // Setup the plugins.
    config.plugins = [
      // If the target is a library, it doesn't need HTML on production.
      ...(
        target.library ?
          [] :
          [
            // To automatically inject the `script` tag on the target `html` file.
            new HtmlWebpackPlugin(Object.assign({}, target.html, {
              template: this.targetsHTML.getFilepath(target),
              inject: 'body',
            })),
            // To add the `async` attribute to the  `script` tag.
            new ScriptExtHtmlWebpackPlugin({
              defaultAttribute: 'async',
            }),
          ]
      ),
      // To add the _'browser env variables'_.
      new ProjextWebpackRuntimeDefinitions(
        Object.keys(entry).reduce(
          (current, key) => [...current, ...entry[key].filter((file) => path.isAbsolute(file))],
          []
        ),
        definitions
      ),
      // To optimize the SCSS and remove repeated declarations.
      new OptimizeCssAssetsPlugin(),
      // To compress the emitted assets using gzip, if the target is not a library.
      ...(!target.library || target.libraryOptions.compress ? [new CompressionPlugin()] : []),
      // Copy the files the target specified on its settings.
      new CopyWebpackPlugin(copy),
      /**
       * If the target doesn't inject the styles on runtime, add the plugin to push them all on
       * a single file.
       */
      ...(
        target.css.inject ?
          [] :
          [new MiniCssExtractPlugin({
            filename: output.css,
          })]
      ),
      // If there are additionals files to watch, add the plugin for it.
      ...(
        additionalWatch.length ?
          [new ExtraWatchWebpackPlugin({ files: additionalWatch })] :
          []
      ),
      // If the the bundle should be analyzed, add the plugin for it.
      ...(
        analyze ?
          [new BundleAnalyzerPlugin()] :
          []
      ),
    ];
    // Enable the watch mode if required...
    if (target.watch.production) {
      config.watch = true;
    }

    // Reduce the configuration
    return this.events.reduce(
      [
        'webpack-browser-production-configuration',
        'webpack-browser-configuration',
      ],
      config,
      params
    );
  }
}
/**
 * The service provider that once registered on the app container will set an instance of
 * `WebpackBrowserProductionConfiguration` as the `webpackBrowserProductionConfiguration` service.
 * @example
 * // Register it on the container
 * container.register(webpackBrowserProductionConfiguration);
 * // Getting access to the service instance
 * const webpackBrowserProdConfig = container.get('webpackBrowserProductionConfiguration');
 * @type {Provider}
 */
const webpackBrowserProductionConfiguration = provider((app) => {
  app.set(
    'webpackBrowserProductionConfiguration',
    () => new WebpackBrowserProductionConfiguration(
      app.get('events'),
      app.get('pathUtils'),
      app.get('targetsHTML'),
      app.get('webpackBaseConfiguration')
    )
  );
});

module.exports = {
  WebpackBrowserProductionConfiguration,
  webpackBrowserProductionConfiguration,
};