release.js

'use strict';

var hookLoaded = false;
var _ = require('./util.js');

var onCompileStart, onCompileEnd, onProcessEnd;

/**
 * fis 整体编译入口。
 * @param {Object} opt 配置项
 * @param {Array} [opt.srcCache] 需要编译的文件列表,当没有填写时,fis 将通过 {@link fis.project.getSource}() 获取
 * @param {Callback} [opt.beforeEach] 编译开始前执行的回调函数,无论走缓存与否。
 * @param {Callback} [opt.afterEach] 编译完成后执行的回调函数,无论走缓存与否。
 * @param {Callback} [opt.beforeCompile] 编译开始前执行,当采用缓存时不执行。
 * @param {Callback} [opt.afterCompile] 编译完成后执行,当采用缓存时不执行。
 * @param {Callback} [opt.beforeCacheRevert] 在缓存被应用到文件对象前执行。
 * @param {Callback} [opt.afterCacheRevert] 在缓存被应用到文件对象后执行。
 * @param {Callback} callback 当整体编译完成后执行。
 * @function
 * @memberOf fis
 * @name release
 */
var exports = module.exports = function(opt, callback) {
  if (typeof opt === 'function') {
    callback = opt;
    opt = {};
  } else {
    opt = opt || {};
  }

  var src = {};
  if (Array.isArray(opt.srcCache) && opt.srcCache.length) {
    opt.srcCache.forEach(function(path) {
      if (!fis.util.isFile(path)) return;

      var file = fis.file(path);
      if (file.release) {
        src[file.subpath] = file;
      }
    });
  } else {
    src = fis.project.getSource();
  }
  var ret = {
    src: src,
    ids: {},
    pkg: {},
    map: {
      res: {},
      pkg: {}
    }
  };

  // 通过 env 的配置来决定。
  var config = fis.media();
  fis.compile.setup(opt);

  // load hooks
  hookLoaded || (fis.util.pipe('hook', function(processor, settings) {
    processor(fis, settings);
  }), hookLoaded = true);

  fis.emit('release:start', ret);
  var total = opt.total || {};
  var resourceMapFiles = [];

  var collect = function(file, type) {
    type = type || 'res';

    if (file.release && file.useMap) {
      //add resource map
      var id = file.getId();
      ret.ids[id] = file;
      var res = file.map = ret.map[type][id] = {
        uri: file.getUrl(),
        type: file.rExt.replace(/^\./, '')
      };
      for (var key in file.extras) {
        if (file.extras.hasOwnProperty(key)) {
          res.extras = file.extras;
          break;
        }
      }
      if (file.requires && file.requires.length) {
        res.deps = file.requires;
      }
    }
    if (file._isResourceMap) {
      resourceMapFiles.push(file); // the resource map file is special
    }
  };

  Object.keys(total).forEach(function(subpath) {
    var file = total[subpath];
    collect(file);
  });

  onCompileStart && fis.removeListener('compile:start', onCompileStart);
  fis.on('compile:start', (onCompileStart = function(file) {
    total[file.subpath] = file;
    opt.beforeEach && opt.beforeEach(file, ret);
  }));

  onCompileEnd && fis.removeListener('compile:end', onCompileEnd);
  fis.on('compile:end', (onCompileEnd = function(file) {
    opt.afterEach && opt.afterEach(file, ret);
    collect(file);

    // 兼容 fis2 中的用法
    if (file.extras && file.extras.derived) {
      file.derived = file.derived.concat(file.extras.derived);
      delete file.extras.derived;
    }

    if (!file.derived || !file.derived.length) {
      return;
    }

    file.derived.forEach(function(obj) {
      obj.__proto__ = file.__proto__;
      total[obj.subpath] = obj;
      obj.defineLikes();

      opt.beforeEach && opt.beforeEach(obj);
      opt.afterEach && opt.afterEach(obj, ret);
      collect(obj);
    });
  }));

  onProcessEnd && fis.removeListener('process:end', onProcessEnd);
  fis.on('process:end', (onProcessEnd = function(file) {
    if (file.useSameNameRequire) {
      if (file.isJsLike) {
        file.addSameNameRequire('.css');
      } else if (file.isHtmlLike) {
        file.addSameNameRequire('.js');
        file.addSameNameRequire('.css');
      }
    }
  }));

  var pending = [];
  var begining = ret.src;
  ret.src = total;

  Object.keys(begining).forEach(function(key) {
    pending.push(begining[key]);
  });

  var onAddFileToCompile = function(file) {
    pending.push(file);
  };

  fis.on('compile:add', onAddFileToCompile);
  try {
    while (pending.length) {
      var file = pending.shift();
      fis.compile(file, opt);
    }
  } finally {
    fis.removeListener('compile:add', onAddFileToCompile);
  }

  // -----------
  // 单文件已编译完成

  //project root
  var root = fis.project.getProjectPath();

  //get pack config
  var conf = config.get('pack');
  if (typeof conf === 'undefined') {
    //from fis-pack.json
    var file = root + '/fis-pack.json';
    if (fis.util.isFile(file)) {
      fis.config.set('pack', conf = fis.util.readJSON(file));
    }
  }

  if (typeof conf === 'undefined') {
    conf = buildPack(ret);
  }

  // package callback
  var cb = function(packager, settings, key, type) {
    fis.log.debug('[' + key + '] start');
    fis.emit(type, ret, conf, opt);
    packager(ret, conf, settings, opt);
    fis.log.debug('[' + key + '] end');
  };

  var packager = _.applyMatches('::package', fis.media().getSortedMatches());

  ['prepackager', 'packager', 'spriter', 'postpackager'].forEach(function(name) {

    if (packager[name]) {
      // fis.match 中配置的优先,所以,这里直接覆盖就行了。
      fis.media().set('modules.' + name, packager[name]);
    }

    fis.util.pipe(name, cb, opt[name]);
  });

  // Object.keys(ret.pkg).forEach(function(subpath) {
  //   var file = ret.pkg[subpath];
  //   collect(file, 'pkg');
  // });

  // filling the resource map file, resourceMapFile is a reference file object
  resourceMapFiles.forEach(function(file) {
    file.setContent(
      file.getContent()
      .replace(
        /\b__RESOURCE_MAP__\b/g,
        JSON.stringify(ret.map, null, file.optimizer ? null : 4)
      )
    );
  });
  fis.emit('release:end', ret);

  //done
  if (callback) {
    callback(ret);
  }
};

/*
 * 根据用户配置的 fis.match 生成 pack 表。
 */
function buildPack(ret) {
  var src = ret.src;
  var pack = {};

  Object.keys(src).forEach(function(subpath) {
    var file = src[subpath];
    var packTo = file.packTo;

    if (!packTo || file.release === false || file.isPartial) {
      return;
    }

    pack[packTo] = pack[packTo] || [];
    pack[packTo].push(file.subpath);
  });

  return pack;
}