Source: modules/base/module.js

/**
 * Module provides methods to create a new module. Any module created here has
 * the ability of being extended by other modules. Existing properties are
 * overridden.
 *
 * Creating a module
 * @example
 *   var NewModule = Module.create(function() {
 *     this.value = 100;
 *   });
 *   NewModule.prototype.print = function() { console.log(this.value); };
 *
 *   var instance = NewModule();
 *   instance.print(); // 100
 *
 * Extending a module
 * @example
 *   var AnotherModule = Module.create();
 *   AnotherModule.prototype.inc = function() { this.value++; };
 *   var ExtendingModule = NewModule.extend(AnotherModule);
 *
 *   var instance = ExtendingModule();
 *   instance.inc();
 *   instance.print(); // 101
 *
 * @module modules/base/module
 */
define(function() {
  'use strict';

  const LOG_LEVEL = {
    NONE: 0,
    DEBUG: 1,
    INFO: 2,
    WARN: 3,
    ERROR: 4,
    ALL: 5
  };

  var _constructorMap = (function() {
    var _ctorMap = new Map();
    var _getConstructor = function(Module) {
      return _ctorMap.get(Module) || function() {};
    };
    var _registerConstructor = function(Module, constructor) {
      _ctorMap.set(Module, constructor || function() {});
    };
    return {
      getConstructor: _getConstructor,
      registerConstructor: _registerConstructor
    };
  })();

  var _emptyFunction = function() {};
  var _createLogger = function(name) {
    switch (name) {
      case 'DEBUG':
        return function(msg) {
          console.log(this._msgPrefix + msg);
        };
      case 'INFO':
        return function(msg) {
          console.info(this._msgPrefix + msg);
        };
      case 'WARN':
        return function(msg) {
          console.warn(this._msgPrefix + msg);
        };
      case 'ERROR':
        return function(msg) {
          console.error(this._msgPrefix + msg);
        };
    }
  };

  var ModulePrototype = {
    get _msgPrefix() {
      return '[' + this.$name + ']: ';
    },
    debug: _emptyFunction,
    info: _emptyFunction,
    warn: _emptyFunction,
    error: _emptyFunction,
    throw: function(msg) {
      throw new Error(this._msgPrefix + msg);
    },
    set _logLevel(value) {
      Object.keys(LOG_LEVEL).forEach((name) => {
        var level = LOG_LEVEL[name];
        if (value >= level && value > 0) {
          this[name.toLowerCase()] = _createLogger(name);
        }
      });
    },
    super: _constructorMap.getConstructor
  };

  var _extend = function() {
    switch (arguments.length) {
      case 0:
        break;
      case 1:
        var Module = arguments[0];
        for (var prop in Module.prototype) {
          if (prop === '$name') {
            continue;
          }
          var pd = Object.getOwnPropertyDescriptor(Module.prototype, prop);
          if (pd) {
            Object.defineProperty(this.prototype, prop, pd);
          }
        }
        break;
      default:
        Array.prototype.forEach.call(arguments, (Module) => {
          _extend.call(this, Module);
        });
        break;
    }
    return this;
  };

  // Returns a module function. The function returns an instance of the
  // module when it is called. The passed constructor is called using the
  // instance as "this".
  function _create(constructor) {
    if (constructor && typeof constructor !== 'function') {
      throw new Error('[Module]: Invalid constructor');
    }

    var ModuleFunc = function() {};
    ModuleFunc.prototype = Object.create(ModulePrototype);
    ModuleFunc.prototype.$name = constructor && constructor.name || '';

    var Module = function() {
      var instance = new ModuleFunc();
      if (constructor) {
        constructor.apply(instance, arguments);
      }
      return instance;
    };
    _constructorMap.registerConstructor(Module, constructor);
    Module.extend = _extend;
    Module.prototype = ModuleFunc.prototype;
    return Module;
  }

  return {
    LOG_LEVEL: LOG_LEVEL,
    create: _create
  };
});