lib/models/instantiate-addons.js

'use strict';

/**
@module ember-cli
*/

const DAGMap = require('dag-map').default;
const logger = require('heimdalljs-logger')('ember-cli:addons-factory');
const heimdall = require('heimdalljs');
const SilentError = require('silent-error');

/**
 * Create instances of a set of "child" addons for a parent addon or project.
 *
 * @method instantiateAddons
 * @param {Object} parent an Addon or Project that is the direct containing object of the list
 *   of children defined in addonPackages.
 * @param {Project} project the project that contains the parent (so either the addon's project
 *   if parent is an addon, or the project itself if it is a project). It is possible when
 *   constructing custom addon instances that the project will actually be undefined--various
 *   addon tests do this, for example.
 * @param {Object} a map of addon name (including scope) to an AddonInfo with the name, path and
 *   'pkg' object for that addon's package.json). These are what is turned into addons.
 */
function instantiateAddons(parent, project, addonPackages) {
  // depending on whether this is really a project or an addon, the 'name' property may be a getter.
  let parentName = typeof parent.name === 'function' ? parent.name() : parent.name;

  logger.info('instantiateAddons for: ', parentName);

  let addonNames = Object.keys(addonPackages || {});

  if (addonNames.length === 0) {
    logger.info('    no addons');
    return [];
  } else {
    logger.info('    addon names are:', addonNames);
  }

  let initializeAddonsToken = heimdall.start(`${parentName}: initializeAddons`);
  let graph = new DAGMap();
  let addonInfo, emberAddonConfig;

  addonNames.forEach((name) => {
    addonInfo = addonPackages[name];
    emberAddonConfig = addonInfo.pkg['ember-addon'];

    graph.add(name, addonInfo, emberAddonConfig.before, emberAddonConfig.after);
  });

  let addons = [];
  let timings = new Map();

  graph.each((key, value) => {
    let addonInfo = value;
    if (addonInfo) {
      let initializeAddonToken = heimdall.start({
        name: `initialize ${addonInfo.name}`,
        addonName: addonInfo.name,
        addonInitializationNode: true,
      });

      let start = Date.now();

      let pkgInfo = parent.packageInfoCache.getEntry(addonInfo.path);

      if (!pkgInfo || !pkgInfo.valid) {
        throw new SilentError(
          `The \`${addonInfo.pkg.name}\` addon could not be found at \`${addonInfo.path}\` or was otherwise invalid.`
        );
      }

      // get an instance of the addon. If that fails it will throw.
      let addon = pkgInfo.getAddonInstance(parent, project);

      timings.set(addon, Date.now() - start);

      initializeAddonToken.stop();

      addons.push(addon);
    }
  });

  logger.info(
    ' addon info %o',
    addons.map((addon) => ({
      name: addon.name,
      initializeTotalMillis: timings.get(addon),
    }))
  );

  initializeAddonsToken.stop();

  return addons;
}

module.exports = instantiateAddons;