- # install.js

- ¶

      (function (global, undefined) {
        if (global.makeInstaller) {
          return;
        }

        function makeInstaller(options) {
          var root = new File({});

- ¶

  Set up a simple queue for tracking required modules with unmet dependencies. See also `queueAppend` and `queueFlush`.

          var q = root.q = {};
          q.h = q.t = {}; // Queue head, queue tail.

- ¶

  Configurable function for deferring queue flushes.

          q.d = options && options.defer || function (fn) {
            setTimeout(fn, 0);
          };

          return function install(tree) {
            if (isObject(tree)) {
              fileMergeContents(root, tree);
              queueFlush(root.q);
            }
            return root.r;
          };
        }

        global.makeInstaller = makeInstaller;

        if (typeof exports === "object") {
          exports.makeInstaller = makeInstaller;
        }

        var extensions = ["", ".js", ".json"];
        var MISSING = {};
        var hasOwn = MISSING.hasOwnProperty;
        var Ap = Array.prototype;

        function getOwn(obj, key) {
          return hasOwn.call(obj, key) && obj[key];
        }

        function isObject(value) {
          return value && typeof value === "object";
        }

        function isFunction(value) {
          return typeof value === "function";
        }

        function isString(value) {
          return typeof value === "string";
        }

        function queueAppend(q, file) {

- ¶

  Property names shortened to shave bytes: `.t` means `.tail`, `.h` means `.head`, `.n` means `.next`, and `.f` means `.file`.

          q.t = q.t.n = { f: file };
          if (q.h.n === q.t) {

- ¶

  If the queue contains only one File (the one we just added), go ahead and schedule a flush.

            queueFlush(q);
          }
        }

        function queueFlush(q) {

- ¶

  The `q.p` property is set to indicate a flush is pending.

          q.p || (q.p = true, q.d(function () {
            q.p = undefined;
            var next = q.h.n;
            if (next && fileReady(next.f)) {
              queueFlush(q); // Schedule the next flush.
              q.h = next;
              fileEvaluate(next.f);
            }
          }));
        }

- ¶

  These `unbound{Require,Ensure}` functions need to be bound to File objects before they can be used. See `makeRequire`.

        function unboundRequire(id) {
          var result = fileEvaluate(fileResolve(this, id));
          if (result === MISSING) {
            throw new Error("Cannot find module '" + id + "'");
          }
          return result;
        }

        function unboundEnsure() {

- ¶

  Flatten arguments into an array containing relative module identifier strings and an optional callback function, then coerce that array into a callback function with a `.d` property.

          var flatArgs = Ap.concat.apply(Ap, arguments);
          var callback = ensureObjectOrFunction(flatArgs);

- ¶

  Note that `queueAppend` schedules a flush if there are no other callbacks waiting in the queue.

          queueAppend(this.q, new File(callback, this));
        }

        function makeRequire(file) {
          var require = unboundRequire.bind(file);
          require.ensure = unboundEnsure.bind(file);

- ¶

  TODO Consider adding `require.promise`.

          return require;
        }

- ¶

  File objects represent either directories or modules that have been installed. When a `File` respresents a directory, its `.c` (contents) property is an object containing the names of the files (or directories) that it contains. When a `File` represents a module, its `.c` property is a function that can be invoked with the appropriate `(require, exports, module)` arguments to evaluate the module. The `.p` (parent) property of a File is either a directory `File` or `null`. Note that a child may claim another `File` as its parent even if the parent does not have an entry for that child in its `.c` object. This is important for implementing anonymous files, and preventing child modules from using `../relative/identifier` syntax to examine unrelated modules.

        function File(contents, /*optional:*/ parent, name) {
          var file = this;

- ¶

  Link to the parent file.

          file.p = parent = parent || null;

          if (name) {

- ¶

  If this file was created with `name`, join it with `parent.id` to generate a module identifier.

            file.id = (parent && parent.id || "") + "/" + name;
          }

- ¶

  Queue for tracking required modules with unmet dependencies, inherited from the `parent`.

          file.q = parent && parent.q;

- ¶

  Each directory has its own bound version of the `require` function that can resolve relative identifiers. Non-directory Files inherit the require function of their parent directories, so we don’t have to create a new require function every time we evaluate a module.

          file.r = isObject(contents)
            ? makeRequire(file)
            : parent && parent.r;

- ¶

  Set the initial value of `file.c` (the “contents” of the File).

          fileMergeContents(file, contents);

- ¶

  When the file is a directory, `file.ready` is an object mapping module identifiers to boolean ready statuses. This information can be shared by all files in the directory, because module resolution always has the same results for all files in a given directory.

          file.ready = fileIsDirectory(file) && {};
        }

- ¶

  A file is ready if all of its dependencies are installed and ready.

        function fileReady(file) {
          var result = !! file;
          var module = file && file.c;
          var deps = isFunction(module) && module.d;
          if (deps && ! getOwn(module, "seen")) {
            module.seen = true;
            var parentReadyCache = file.p.ready;
            result = Object.keys(deps).every(function (dep) {

- ¶

  By storing the results of these lookups in `parentReadyCache`, we benefit when any other file in the same directory resolves the same identifier.

              return parentReadyCache[dep] =
                parentReadyCache[dep] ||
                fileReady(fileResolve(file.p, dep));
            });
            module.seen = undefined;
          }
          return result;
        }

        function fileEvaluate(file) {
          var module = file && file.c;
          if (isFunction(module)) {
            if (! hasOwn.call(module, "exports")) {
              module.id = file.id;
              module.call(global, file.r, module.exports = {}, module);
            }
            return module.exports;
          }
          return MISSING;
        }

        function fileIsDirectory(file) {
          return isObject(file.c);
        }

        function fileMergeContents(file, contents) {
          if ((contents = ensureObjectOrFunction(contents))) {
            var fileContents = file.c = file.c || (
              isFunction(contents) ? contents : {}
            );

            if (isObject(contents) && fileIsDirectory(file)) {
              Object.keys(contents).forEach(function (key) {
                var child = getOwn(fileContents, key);
                if (child) {
                  fileMergeContents(child, contents[key]);
                } else {
                  fileContents[key] = new File(contents[key], file, key);
                }
              });
            }
          }
        };

        function ensureObjectOrFunction(contents) {

- ¶

  If contents is an array of strings and functions, return the last function with a `.d` property containing all the strings.

          if (Array.isArray(contents)) {
            var deps = {};
            var func;

            contents.forEach(function (item) {
              if (isString(item)) {
                deps[item] = false; // Initially unsatisfied.
              } else if (isFunction(item)) {
                func = item;
              }
            });

- ¶

  If no function was found in the array, provide a default function that simply requires each dependency (really common case).

            contents = func || function (module, require) {
              Object.keys(deps).forEach(require);
            };

            contents.d = deps;

          } else if (isFunction(contents)) {

- ¶

  If contents is already a function, make sure it has deps.

            contents.d = contents.d || {};

          } else if (! isObject(contents)) {

- ¶

  If contents is neither an array nor a function nor an object, just give up and return null.

            contents = null;
          }

          return contents;
        }

        function fileAppendIdPart(file, part, isLastPart) {

- ¶

  Always append relative to a directory.

          while (file && ! fileIsDirectory(file)) {
            file = file.p;
          }

          if (! file || ! part || part === ".") {
            return file;
          }

          if (part === "..") {
            return file.p;
          }

          for (var e = 0; e < extensions.length; ++e) {
            var withExtension = part + extensions[e];

            var child = getOwn(file.c, withExtension);
            if (child) {
              return child;
            }

            if (! isLastPart) {

- ¶

  Only consider multiple file extensions if this part is the last part of a module identifier, and not `.` or `..`.

              break;
            }
          }
        };

        function fileAppendId(file, id) {
          var parts = id.split("/");

- ¶

  Use `Array.prototype.every` to terminate iteration early if `fileAppendIdPart` returns a falsy value.

          parts.every(function (part, i) {
            return file = fileAppendIdPart(file, part, i === parts.length - 1);
          });
          return file;
        };

        function fileGetRoot(file) {
          return file && fileGetRoot(file.p) || file;
        }

        function fileResolve(file, id) {
          file =

- ¶

  Absolute module identifiers (i.e. those that begin with a `/` character) are interpreted relative to the root directory, which is a slight deviation from Node, which has access to the entire file system.

            id.charAt(0) === "/" ? fileAppendId(fileGetRoot(file), id) :

- ¶

  Relative module identifiers are interpreted relative to the current file, naturally.

            id.charAt(0) === "." ? fileAppendId(file, id) :

- ¶

  Top-level module identifiers are interpreted as referring to packages in `node_modules` directories.

            nodeModulesLookup(file, id);

- ¶

  If the identifier resolves to a directory, we use the same logic as Node to find an `index.js` or `package.json` file to evaluate.

          while (file && fileIsDirectory(file)) {

- ¶

  If `package.json` does not exist, `fileEvaluate` will return the `MISSING` object, which has no `.main` property.

            var pkg = fileEvaluate(fileAppendIdPart(file, "package.json"));
            file = pkg && isString(pkg.main) &&
              fileAppendId(file, pkg.main) || // Might resolve to another directory!
              fileAppendIdPart(file, "index.js");
          }

          return file;
        };

        function nodeModulesLookup(file, id) {
          return fileIsDirectory(file) &&
            fileAppendId(file, "node_modules/" + id) ||
            (file.p && nodeModulesLookup(file.p, id));
        }
      })("object" === typeof global ? global :
         "object" === typeof window ? window :
         "object" === typeof self ? self : this);