rambler-digital-solutions/rship

View on GitHub
cli/commands/compilers/server.js

Summary

Maintainability
B
5 hrs
Test Coverage
'use strict';

// ======================
// Depends
// ======================
const cluster   = require('cluster');
const path      = require('path');
const MemoryFS  = require('memory-fs');
const colors    = require('colors');
const webpack   = require('webpack');
const utils     = require('../utils');

/**
 * Server Compiler
 * @param {[type]} config [description]
 */
const ServerCompiler = function(config) {
  this.config = config || {};
};

/**
 * Start
 * @return {[type]} [description]
 */
ServerCompiler.prototype.start = function(devScreen) {
  const { config }    = this;
  const { dir, cwd }  = config;
  let secrets = {};

  if (config.secrets) {
    secrets = require(config.secrets);
  }

  // define mmemory fs
  let fs = new MemoryFS();

  // get webpack configs
  let serverConfig = require(config.webpack.server)(config);

  // push modules directories
  serverConfig.resolve.modules.push(`${cwd}/node_modules`);

  // prepare separated webpack instances
  let serverCompiler = webpack(serverConfig);

  // server file
  let serverFile = path.join(config.build.server.path, config.build.server.file);

  // get avaliable screens from blessed container
  let { memoryBlock, activeProcessBlock, logsBlock, screen } = devScreen;

  // define workers object
  // all changes will execute at separated process
  let workers = {
    main: false,  // current working process
    temp: false   // temporary process
  };

  // check version
  utils.getLatestVersion((err, lastVersion) => {
    let currentVersion = this.config.details.version;
    if (err) {
      utils.log(logsBlock, 'SHIP: Can\'t check last verion', 'red');
    }

    if (lastVersion > currentVersion ) {
      utils.log(logsBlock, `SHIP: Is outdated, current version is: ${currentVersion}, last version is: ${lastVersion}`, 'red'); 
      utils.log(logsBlock, `SHIP: Please update @see https://rambler-digital-solutions.github.io/rship/en/parts/update.html`, 'red'); 
    }
  });

  // run the trap!
  utils.log(logsBlock, 'SHIP: Initialize', 'green');
  utils.log(logsBlock, 'SHIP: Run client compile', 'green');
  utils.log(logsBlock, 'SHIP: Run server compile', 'green');

  /**
   * Create worker, also will subscrbe on process messages
   * @param  {Function} cb [description]
   * @return {[type]}      [description]
   */
  let createWorker = function(cb = function() {}) {
    // define colors by message type
    let messageTypesColors = {
      log: 'yellow',
      info: 'blue',
      warn: 'magenta',
      error: 'red'
    };

    // processes content
    let worker = cluster.fork(
      Object.assign({}, { NODE_ENV: 'development', NODE_PATH: `${dir}/node_modules` }, secrets)
    ).on('online', cb);

    cluster.workers[worker.id].on('message', msg => {
      let { data }  = msg;
      switch (msg.type) {

        // some console messages
        case 'log':
        case 'warn':
        case 'info':
        case 'error':
          utils.log( logsBlock, `${msg.type.toUpperCase()}: ${utils.logFormat(data)}`, messageTypesColors[msg.type] );
          break;

        case 'active-worker-usage': {
          let memoryBoxContent = [
            `${colors.yellow('CPU')}: ${msg.data.cpuPersents.toFixed(2)}%`,
            `${colors.yellow('Heap Total')}: ${utils.toUnits(msg.data.memory.heapTotal)}`,
            `${colors.yellow('Heap Used')}: ${utils.toUnits(msg.data.memory.heapUsed)}`
          ].join('\n');

          let activeProcessBoxContent = [
            `${colors.yellow('Real PID')}: ${msg.data.pid}`,
            `${colors.yellow('Current PID')}: ${workers.main.id}`,
            `${colors.yellow('Uptime')}: ${msg.data.uptime.toFixed(0)} sec`
          ].join('\n');

          // set memory block content
          memoryBlock.setContent(memoryBoxContent);

          // set active process block content
          activeProcessBlock.setContent(activeProcessBoxContent);

          // re render screen window
          screen.render();
        } break;
        default: null;
      }
    });

    return worker;
  };

  // create worker main worjer
  workers.main = createWorker(() => {
    utils.log(logsBlock, 'SHIP: created main worker PID#' + workers.main.id, 'white');
  });

  // files will save at memory
  serverCompiler.outputFileSystem = fs;

  // start watching source
  serverCompiler.watch({ poll: true }, function(err, stats) {
    let statistic = stats.toJson();

    if (stats.hasErrors()) {
      statistic.errors.forEach(error => {
        utils.log(logsBlock, 'SHIP: Webpack->Server->Error: ' + error, 'red');
      });
      return false;
    }

    if (stats.hasWarnings()) {
      statistic.warnings.forEach(warn => {
        utils.log(logsBlock, 'SHIP: Webpack->Server->Warning: ' + warn, 'magenta');
      });
      return false;
    } else {
      utils.log(logsBlock, 'SHIP: Webpack->Server->Hash: ' + statistic.hash, 'green');
    }

    utils.log(logsBlock, 'SHIP: Server compiled', 'green');

    if (fs.statSync(serverFile).isFile()) {
      // get file source code
      let sourceCode = stats.compilation.assets[config.build.server.file].source();

      // @TODO: Make good choice and parse source map for debugging
      // let sourceMap = stats.compilation.assets[config.build.server.file + '.map'].source();
      if (workers.temp) {
        utils.log(logsBlock, 'SHIP: kill worker PID#' +  workers.main.id);
        workers.main.kill('SIGTERM');
        workers.main.disconnect();
        workers.main = false;
        workers.main = workers.temp;
      }

      if (workers.main && workers.main.isConnected()) {
        workers.main.send(sourceCode, () => {
          workers.temp = createWorker(() => {
            utils.log(logsBlock, 'SHIP: created temp worker PID#' + workers.temp.id, 'white');
          });
        });
      }
    }

    screen.render();
    return true;
  });
};

module.exports = ServerCompiler;