Home Reference Source

src/services/building/engine.js

const path = require('path');
const { provider } = require('jimple');
/**
 * This build engine is in charge of generating the CLI commands and the configuration to bundle
 * a target using Webpack.
 */
class WebpackBuildEngine {
  /**
   * Class constructor.
   * @param {EnvironmentUtils}     environmentUtils     To load environment variables sent by the
   *                                                    CLI command to the configuration builder
   *                                                    method.
   * @param {Targets}              targets              To get a target information.
   * @param {WebpackConfiguration} webpackConfiguration To generate a configuration for a target.
   * @param {WebpackPluginInfo}    webpackPluginInfo    To get the path to the configuration file.
   */
  constructor(
    environmentUtils,
    targets,
    webpackConfiguration,
    webpackPluginInfo
  ) {
    /**
     * A local reference for the `environmentUtils` service.
     * @type {EnvironmentUtils}
     */
    this.environmentUtils = environmentUtils;
    /**
     * A local reference for the `targets` service.
     * @type {Targets}
     */
    this.targets = targets;
    /**
     * A local reference for the `webpackConfiguration` service.
     * @type {WebpackConfiguration}
     */
    this.webpackConfiguration = webpackConfiguration;
    /**
     * A local reference for the plugin information.
     * @type {WebpackPluginInfo}
     */
    this.webpackPluginInfo = webpackPluginInfo;
    /**
     * A dictionary of environment variables the service will include on the CLI command and
     * that will be retrieved when generating the configuration.
     * The keys are the purpose and the values the actual names of the variables.
     * @type {Object}
     * @property {string} target  The name of the target being builded.
     * @property {string} type    The intended build type: `development` or `production`.
     * @property {string} run     Whether or not to execute the target. This will be like a fake
     *                            boolean as the CLI doesn't support boolean variables, so its
     *                            value will be either `'true'` or `'false'`.
     * @property {string} watch   Whether or not to watch the target files. This will be like a
     *                            fake boolean as the CLI doesn't support boolean variables, so
     *                            its value will be either `'true'` or `'false'`.
     * @property {string} inspect Whether or not to enable the Node inspector. This will be like a
     *                            fake boolean as the CLI doesn't support boolean variables, so its
     *                            value will be either `'true'` or `'false'`.
     * @property {string} analyze Whether or not to enable the bundle analyzer. This will be like a
     *                            fake boolean as the CLI doesn't support boolean variables, so its
     *                            value will be either `'true'` or `'false'`.
     *
     * @access protected
     * @ignore
     */
    this._envVars = {
      target: 'PXTWPK_TARGET',
      type: 'PXTWPK_TYPE',
      run: 'PXTWPK_RUN',
      watch: 'PXTWPK_WATCH',
      inspect: 'PXTWPK_INSPECT',
      analyze: 'PXTWPK_ANALYZE',
    };
  }
  /**
   * Get the CLI build command to bundle a target.
   * @param {Target}  target               The target information.
   * @param {string}  buildType            The intended build type: `development` or `production`.
   * @param {boolean} [forceRun=false]     Force the target to run even if the `runOnDevelopment`
   *                                       setting is `false`.
   * @param {boolean} [forceWatch=false]   Force webpack to use the watch mode even if the `watch`
   *                                       setting for the required build type is set to `false`.
   * @param {boolean} [forceInspect=false] Enables the Node inspector even if the target setting
   *                                       is set to `false`.
   * @param {boolean} [forceAnalyze=false] Enables the bundle analyzer.
   * @return {string}
   */
  getBuildCommand(
    target,
    buildType,
    forceRun = false,
    forceWatch = false,
    forceInspect = false,
    forceAnalyze = false
  ) {
    const vars = this._getEnvVarsAsString({
      target: target.name,
      type: buildType,
      run: forceRun,
      watch: forceWatch,
      inspect: forceInspect,
      analyze: forceAnalyze,
    });

    const config = path.join(
      'node_modules',
      this.webpackPluginInfo.name,
      this.webpackPluginInfo.configuration
    );

    const options = [
      '--progress',
      '--profile',
      '--colors',
    ]
    .join(' ');

    const command = !forceAnalyze && target.is.browser && (target.runOnDevelopment || forceRun) ?
      'webpack-dev-server' :
      'webpack';

    return `${vars} ${command} --config ${config} ${options}`;
  }
  /**
   * Get a webpack configuration for a target.
   * @param {Target} target    The target configuration.
   * @param {string} buildType The intended build type: `development` or `production`.
   * @return {object}
   */
  getConfiguration(target, buildType) {
    return this.webpackConfiguration.getConfig(target, buildType);
  }
  /**
   * Get a Webpack configuration by reading the environment variables sent by the CLI command
   * `getBuildCommand` generates.
   * @return {object}
   * @throws {Error} If the environment variables are not present.
   */
  getWebpackConfig() {
    const vars = this._getEnvVarsValues();
    if (!vars.target || !vars.type) {
      throw new Error('This file can only be run by using the `build` command');
    }

    const {
      type,
      run,
      inspect,
      analyze,
    } = vars;
    const target = Object.assign({}, this.targets.getTarget(vars.target));
    if (analyze === 'true') {
      target.analyze = true;
    } else {
      if (run === 'true') {
        target.runOnDevelopment = true;
        if (inspect === 'true') {
          target.inspect.enabled = true;
        }
      }

      if (vars.watch === 'true') {
        target.watch[type] = true;
      }
    }

    return this.getConfiguration(target, type);
  }
  /**
   * Given a dictionary with the environment variables purpose and values, this method generates
   * a string with the variables real names and values.
   * @example
   * console.log(_getEnvVarsAsString{
   *   target: 'my-target',
   *   type: 'development',
   * });
   * // will output `PXTWPK_TARGET=my-target PXTWPK_TYPE=development`
   * @param {object} values A dictionary with the purpose(alias) of the variables as keys.
   * @return {string}
   * @access protected
   * @ignore
   */
  _getEnvVarsAsString(values) {
    return Object.keys(values)
    .map((name) => `${this._envVars[name]}=${values[name]}`)
    .join(' ');
  }
  /**
   * Load the environment variables and returns them on a dictionary.
   * @return {object} The dictionary will have the purpose(alias) of the variables as keys.
   * @access protected
   * @ignore
   */
  _getEnvVarsValues() {
    const vars = {};
    Object.keys(this._envVars).forEach((name) => {
      vars[name] = this.environmentUtils.get(this._envVars[name]);
    });

    return vars;
  }
}
/**
 * The service provider that once registered on the app container will set an instance of
 * `WebpackBuildEngine` as the `webpackBuildEngine` service.
 * @example
 * // Register it on the container
 * container.register(webpackBuildEngine);
 * // Getting access to the service instance
 * const webpackBuildEngine = container.get('webpackBuildEngine');
 * @type {Provider}
 */
const webpackBuildEngine = provider((app) => {
  app.set('webpackBuildEngine', () => new WebpackBuildEngine(
    app.get('environmentUtils'),
    app.get('targets'),
    app.get('webpackConfiguration'),
    app.get('webpackPluginInfo')
  ));
});

module.exports = {
  WebpackBuildEngine,
  webpackBuildEngine,
};