interlockjs/interlock

View on GitHub
src/compile/modules/compile.js

Summary

Maintainability
A
0 mins
Test Coverage
/* eslint-disable no-use-before-define */
import path from "path";

import { assign, chain, fromPairs } from "lodash";
import Promise from "bluebird";
import { pluggable } from "pluggable";

import resolveModule from "./resolve";
import loadModule from "./load";
import hashModule from "./hash";
import generateModuleId from "./generate-id";
import parseModule from "./parse";
import transformModule from "./transform";
import updateReferences from "./update-references";


/**
 * Because the `compileModule` and `generateDependencies` functions interact
 * recursively, defining a stand-in pluggable for `compileModule` allows for
 * plugins to utilize `compileModule` from within an overridden `generateDependencies`.
 *
 * For true behavior, please see documentation for `compileModule`.
 *
 * @return {Promise}         Resolves to compiled module.
 */
const compileModuleR = pluggable(function compileModuleR () {
  return compileModule.apply(this, arguments);
});

/**
 * Given a module whose dependency references (like require strings) have been
 * determined, recursively compile all dependencies and return the module with
 * new dependency properties.
 *
 * @param  {Object}  module  Module for whom dependencies should be compiled.
 *
 * @return {Object}          Module with new dependency properties.
 */
const generateDependencies = pluggable(function generateDependencies (module) {

  // Given a require string or similar absolute/relative path reference, resolve
  // that reference and compile the dependency (recursively).
  const getDependency = (requireStr, contextPath, contextNs, contextNsRoot) => {
    return this.resolveModule(requireStr, contextPath, contextNs, contextNsRoot)
      .then(dependency => this.compileModuleR(dependency))
      .then(childModule => [requireStr, childModule]);
  };

  // Given an array of compiled module dependencies, generate a recursively flattened
  // list of all module dependencies.
  const getDeepDependencies = dependencies => chain(dependencies)
    .map(([, dep]) => dep.deepDependencies.concat(dep))
    .flatten()
    .value();

  const contextPath = path.dirname(module.path);
  const directDependencies = Promise.all(module.synchronousRequires.map(
    requireStr => getDependency(requireStr, contextPath, module.ns, module.nsRoot)
  ));

  return Promise.all([directDependencies, directDependencies.then(getDeepDependencies)])
    .then(([depTuples, deepDependencies]) => assign({}, module, {
      // De-dupe any (deep-)dependencies by their hash.
      deepDependencies: chain(deepDependencies).keyBy("hash").values().value(),
      dependencies: chain(depTuples)
        .map(([, dependency]) => dependency)
        .keyBy("hash")
        .values()
        .value(),
      // Generate a mapping between the original require strings and the modules
      // they resolved to.
      dependenciesByInternalRef: fromPairs(depTuples)
    }));
}, { resolveModule, compileModuleR });

/**
 * Given an unprocess module that has been loaded from disk, return a promise
 * that resolves to the same module in a processed/compiled state, and whose
 * dependencies have also been processed/compiled.
 *
 * @param  {Object}  module  Seed module.
 *
 * @return {Promise}         Resolves to compiled module.
 */
const compileModule = pluggable(function compileModule (module) {
  const modulesByAbsPath = this.cache.modulesByAbsPath;

  if (module.path in modulesByAbsPath) {
    return modulesByAbsPath[module.path];
  }

  return modulesByAbsPath[module.path] = this.loadModule(module)
    .then(this.parseModule)
    .then(this.transformModule)
    .then(this.generateDependencies)
    .then(this.hashModule)
    .then(this.generateModuleId)
    .then(this.updateReferences);
}, {
  loadModule,
  parseModule,
  transformModule,
  generateDependencies,
  hashModule,
  generateModuleId,
  updateReferences
});

/**
 * Given one or more module seeds, traverse their dependency graph, collecting any and
 * all dependency modules, and then parse, transform, and hash those modules.  Return
 * a promise that resolves to the full set of modules, once they have been correctly
 * gathered and compiled.
 *
 * @param  {Array}    moduleSeeds  Module seeds, i.e. modules that have not yet been
 *                                 populated with properties such as ast, `dependencies`,
 *                                 etc.  Module objects _should_ have path, rawSource,
 *                                 and namespace values.
 *
 * @return {Promise}               Resolves to array of all compiled modules.
 */
const compileModules = pluggable(function compileModules (moduleSeeds) {
  return Promise.all(moduleSeeds.map(this.compileModule.bind(this)))
    .then(compiledSeedModules => chain(compiledSeedModules)
      .map(seedModule => seedModule.deepDependencies.concat(seedModule))
      .flatten()
      .uniq()
      .value()
    );
}, { compileModule });

export default compileModules;