fis.js

'use strict';

var last = Date.now();

/*
 * Object.derive,产生一个Class的工厂方法
 * @param  {Function} constructor 构造函数
 * @param  {Object} proto     对象共有变量
 * @return {Function}      构造方法
 * @example
 *   var class1 = Object.derive(function(){ console.log(this.name) }, {name: 'class1'});
 *   var class2 = Object.derive({
 *     constructor: function() {
 *       console.log(this.name)
 *     }
 *   }, {name: 'class2'})
 */
Function.prototype.derive = function(constructor, proto) {
  if (typeof constructor === 'object') {
    proto = constructor;
    constructor = proto.constructor || function() {};
    delete proto.constructor;
  }
  var parent = this;
  var fn = function() {
    parent.apply(this, arguments);
    constructor.apply(this, arguments);
  };
  var tmp = function() {};
  tmp.prototype = parent.prototype;
  var fp = new tmp(),
    cp = constructor.prototype,
    key;
  for (key in cp) {
    if (cp.hasOwnProperty(key)) {
      fp[key] = cp[key];
    }
  }
  proto = proto || {};
  for (key in proto) {
    if (proto.hasOwnProperty(key)) {
      fp[key] = proto[key];
    }
  }
  fp.constructor = constructor.prototype.constructor;
  fn.prototype = fp;
  return fn;
};

//factory
Function.prototype.factory = function() {
  var clazz = this;

  function F(args) {
    clazz.apply(this, args);
  }
  F.prototype = clazz.prototype;
  return function() {
    return new F(arguments);
  };
};

/**
 * fis 名字空间,fis 中所有工具和方法都是通过此变量暴露给外部使用。
 * @namespace fis
 */
var fis = module.exports = {};

// register global variable
Object.defineProperty(global, 'fis', {
  enumerable: true,
  writable: false,
  value: fis
});

/**
 * 事件监听器, fis 中所有的事件都通过它来监听和触发。
 * @name emitter
 * @type {EventEmitter}
 * @namespace fis.emitter
 * @see {@link https://nodejs.org/api/events.html#events_class_events_eventemitter}
 */
fis.emitter = new(require('events').EventEmitter);
['on', 'once', 'removeListener', 'removeAllListeners', 'emit'].forEach(function(key) {
  fis[key] = function() {
    if (arguments[0].match(/^proccess\:/)) {
      arguments[0] = 'process:' + arguments[0].slice(9)
      fis.log.warning('Did you mean ' + arguments[0] + ' event?')
    }
    var emitter = fis.emitter;
    return emitter[key].apply(emitter, arguments);
  };
});

/**
 * 用来监听 fis 中的事件。每次添加都不会有额外的检测工作,也就是说重复添加有可能被调用多次。
 *
 * 注意:以下示例中 type 为 `project:lookup`, 但是它没有多余的意思,就是一段字符串,跟 namespace 没有一点关系。
 *
 * 代理 fis.emitter.on.
 *
 * @example
 * fis.on('project:lookup', function(uri, file) {
 *   // looking for uri from file.
 *   console.log('Looking for %s from $s', uri, file.subpath);
 * });
 * @see {@link https://nodejs.org/api/events.html}
 * @param {Sring} type 事件类型
 * @param {Function} handler 响应函数
 * @function on
 * @memberOf fis
 */

/**
 * 跟 fis.on 差不多,但是只会触发一次。
 *
 * 代理 fis.emitter.once.
 *
 * @example
 * fis.once('compile:start', function(file) {
 *   console.log('The file %s is gona compile.', file.subpath);
 * });
 * @see fis.on
 * @see {@link https://nodejs.org/api/events.html}
 * @param {Sring} type 事件类型
 * @param {Function} handler 响应函数
 * @function once
 * @memberOf fis
 */

/**
 * 取消监听某事件。注意,如果同一个事件类型和同一响应函数被监听了多次,此函数一次只会移除一次。
 *
 * 代理 fis.emitter.removeListener.
 *
 * @example
 * fis.on('project:lookup', function onLookup(uri, file) {
 *   // looking for uri from file.
 *   console.log('Looking for %s from $s', uri, file.subpath);
 * });
 *
 * fis.removeListener('project:lookup', onLookup);
 * @see {@link https://nodejs.org/api/events.html}
 * @function removeListener
 * @param {String} type 事件类型
 * @param {Function} handler 响应函数
 * @memberOf fis
 */

/**
 * 取消监听所有事件监听,如果指定了事件类型,那么只会取消掉指定的事件类型的所有监听。
 *
 * 代理 fis.emitter.removeAllListeners.
 *
 * @see {@link https://nodejs.org/api/events.html}
 * @function removeAllListeners
 * @param {String} [type] 事件类型
 * @memberOf fis
 */

/**
 * 发送事件,将所有监听此事件名的响应函数挨个执行一次,并把消息体参数 args.. 带过去。
 *
 * 代理 fis.emitter.emit.
 *
 * @example
 * fis.emit('donthing', {
 *   foo: 1
 * });
 * @see {@link https://nodejs.org/api/events.html}
 * @param {Sring} type 事件类型
 * @param {Mixed} args... 消息体数据,任意多个,所有参数都可以在 `handler` 中获取到。
 * @function emit
 * @return {Boolean} 如果有事件响应了,则返回 `true` 否则返回 `false`.
 * @memberOf fis
 */


/**
 * 输出时间消耗,单位为 ms.
 * @example
 * fis.time('Comiple cost');
 * // => compile cost 56ms
 * @param  {String} title 描述内容
 * @return {Undefined}
 * @function time
 * @memberOf fis
 */
fis.time = function(title) {
  console.log(title + ' : ' + (Date.now() - last) + 'ms');
  last = Date.now();
};


fis.log = require('./log.js');

// utils
var _ = fis.util = require('./util.js');

// config
fis.config = require('./config.js');

// resource location
fis.uri = require('./uri.js');

// project
fis.project = require('./project.js');

// file
fis.file = require('./file.js');

// cache
fis.cache = require('./cache.js');

// compile kernel
fis.compile = require('./compile.js');

// release api
fis.release = require('./release.js');

['get', 'set', 'env', 'media', 'match', 'hook', 'unhook'].forEach(function(key) {
  fis[key] = function() {
    var config = fis.config;
    return config[key].apply(config, arguments);
  };
});


/**
 * 仅限于 fis-conf.js 中使用,用来包装 fis 插件配置。
 *
 * 需要在对应的插件扩展点中配置才有效,否则直接执行 fis.plugin 没有任何意义。
 *
 * 单文件扩展点:
 * - lint
 * - parser
 * - preprocess
 * - standard
 * - postprocess
 * - optimizer
 *
 * 打包阶段扩展点:
 * - prepacakger
 * - sprite
 * - packager
 * - postpackager
 *
 * @example
 * fis.match('*.scss', {
 *   parser: fis.plugin('sass', {
 *     include_paths: [
 *       './static/scss/libaray'
 *     ]
 *   })
 * });
 *
 * fis.match('::packager', {
 *   postpackager: fis.plugin('loader', {
 *     allInOne: true
 *   });
 * })
 * @memberOf fis
 * @param {String} pluginName 插件名字。
 *
 * 说明:pluginName 不是对应的 npm 包名,而是对应 npm 包名去掉 fis 前缀,去掉插件扩展点前缀。
 *
 * 如:fis-parser-sass 包,在这里面配置就是:
 *
 * ```js
 * fis.match('*.scss', {
 *   parser: fis.plugin('sass')
 * });
 * ```
 * @param {Object} options 插件配置项,具体请参看插件说明。
 * @param {String} [position] 可选:'prepend' | 'append'。默认为空,当给某类文件配置插件时,都是覆盖式的。而通过设置此插件可以做到,往前追加和往后追加。
 *
 * 如:
 *
 * ```js
 * fis.match('*.xxx', {
 *   parser: fis.plugin('a')
 * });
 *
 * // 保留 plugin a 同时,之后再执行 plugin b
 * fis.match('*.xxx', {
 *   parser: fis.plugin('b', null, 'append')
 * });
 * ```
 */
fis.plugin = function(key, options, position) {
  if (arguments.length === 2 && !_.isPlainObject(options)) {
    position = options;
    options = null;
  }

  options = options || {};
  options.__name = options.__plugin = key;
  options.__pos = position;
  options.__isPlugin = true;
  return options;
};

/**
 * fis 的 npm 包信息。
 * @memberOf fis
 */
fis.info = fis.util.readJSON(__dirname + '/../package.json');

/**
 * fis 版本号
 * @memberOf fis
 */
fis.version = fis.info.version;

fis.require = require('./require.js');

fis.cli = require('./cli.js');