Source: context.js


/**
 * Context sub-module
 * 
 * Used as a static object that has a bunch of properties
 * describing the current JS environment and execution context.
 * 
 * @module @lumjs/core/context
 * @property {boolean} hasDefine - A global `define()` function was found.
 * @property {boolean} hasRequire - A global `require()` function was found.
 * @property {boolean} hasExports - A global `exports` variable was found.
 * @property {boolean} hasModule - A global `module` object was found.
 * @property {boolean} hasModuleExports - A `module.exports` exists.
 * @property {boolean} hasSyncedExports - `exports === module.exports`
 * @property {boolean} isNode - An alias to `Node.ok`.
 * @property {boolean} isBrowser - A web-browser environment detected.
 * @property {boolean} isWindow - Is a browser `Window` context.
 * @property {boolean} isWorker - Is a browser `Worker` (sub-types below.)
 * @property {boolean} isServiceWorker - Is a `ServiceWorker` context.
 * @property {boolean} isDedicatedWorker - Is a `DedicatedWorker` context.
 * @property {boolean} isSharedWorker - Is a `SharedWorker` context.
 * @property {object} AMD - Asynchronous Module Definition detection.
 * @property {boolean} AMD.isSupported - The `define.amd` property is set.
 * @property {boolean} AMD.isRequireJS - A global `requirejs` was found.
 * @property {object} CJS - CommonJS detection.
 * @property {boolean} CJS.standard - `hasDefine && hasRequire`
 * @property {boolean} CJS.nodeLike - `CJS.standard && hasExports`
 * @property {boolean} CJS.isLumV5 - Lum.js browser bundler detected.
 * @property {boolean} CJS.isWebpack - Webpack bundler detected.
 * @property {boolean} CJS.inBrowser - `CJS.isLumV5 || CJS.isWebpack`
 * @property {object} Node - Node.js detection.
 * @property {boolean} Node.isSane - `CJS.nodeLike && !CJS.inBrowser`
 * @property {boolean} Node.hasProcess - A global `process` object exists.
 * @property {boolean} Node.ok - `Node.isSane && Node.hasProcess`
 * @property {?string} Node.ver - `process.versions.node ?? null`
 * @property {?object} Node.versions - `process.versions ?? null`
 * @property {Array} Node.args - Command line arguments.
 * @property {string} Node.script - Command line script name.
 * @property {object} Electron - Electron detection.
 * @property {boolean} ok - Electron environment detected.
 * @property {boolean} isDefault - Is this the default app?
 * @property {boolean} isBundled - Is this a bundled app?
 * @property {boolean} isMain - Is this the main renderer frame?
 * @property {?string} type - `process.type`
 * @property {object} root - See {@link module:@lumjs/core/types.root}
 */

const {root,B,F,U,O,isComplex,isObj,def} = require('./types');

const ctx = {root};
const rootHas = what => typeof root[what] !== U;
const cd = def(ctx, true);
const CJS = {};
const AMD = {};
const Node = {};
const Elec = {};
const cjs = def(CJS, true);
const amd = def(AMD, true);
const node = def(Node, true);
const elec = def(Elec, true);
const isLumV5 = typeof module.$lib === O && module.$lib.$cached === module;

cd('hasDefine', typeof define === F)
  ('hasRequire', typeof require === F)
  ('hasExports', typeof exports !== U && isComplex(exports))
  ('hasModule', typeof module === O && module !== null)
  ('hasModuleExports', ctx.hasModule && isComplex(module.exports))
  ('hasSyncedExports', ctx.hasExports && ctx.hasModuleExports 
    && exports === module.exports)

cjs('standard', ctx.hasModule && ctx.hasRequire)
   ('nodeLike', CJS.standard && ctx.hasExports)
   ('isLumV5', CJS.nodeLike && isLumV5)
   ('isWebpack', CJS.nodeLike && require.resolve === require)
   ('inBrowser', CJS.isLumV5 || CJS.isWebpack)

node('isSane', CJS.nodeLike && !CJS.inBrowser)
    ('hasProcess', typeof process === O && process !== null)
    ('ok', Node.isSane && Node.hasProcess)
    ('versions', Node.hasProcess ? process.versions : null)
    ('ver', Node.hasProcess ? process.versions.node : null)
    ('args', {get: function()
    { // Inspired by yargs hideBin() function.
      if (Node.ok)
      {
        const o = (Elec.isBundled) ? 1 : 2;
        return process.argv.slice(o);
      }
      return [];
    }})
    ('script', {get: function()
    { // Inspired by yargs getProcessArgvBin() function.
      if (Node.ok)
      {
        const i = (Elec.isBundled) ? 0 : 1;
        return process.argv[i];
      }
      return '';
    }})

elec('ok', Node.ok && !!Node.versions.electron)
    ('isDefault', Elec.ok && !!process.defaultApp)
    ('isBundled', Elec.ok && !process.defaultApp)
    ('isMain', Elec.ok && !!process.isMainFrame)
    ('type', Elec.ok && process.type)

amd('isSupported', ctx.hasDefine && isObj(define.amd))
   ('isRequireJS', AMD.isSupported && typeof requirejs === F)

cd('CJS', CJS)
  ('AMD', AMD)
  ('Node', Node)
  ('Electron', Elec)
  ('isNode', Node.ok)
  ('isWindow', typeof Window === F && rootHas(window))
  ('isWorker', rootHas('WorkerGlobalScope'))
  ('isServiceWorker', rootHas('ServiceWorkerGlobalScope'))
  ('isDedicatedWorker', rootHas('DedicatedWorkerGlobalScope'))
  ('isSharedWorker', rootHas('SharedWorkerGlobalScope'))
  ('isBrowser', !ctx.isNode && (ctx.isWindow || ctx.isWorker))

/**
 * See if a root-level name is defined.
 * 
 * This adds a `context.has.{name}` boolean property which caches the
 * result so it can be referred to directly.
 * 
 * In any JS environment with the `Proxy` object (which honestly should
 * be all modern ones), you can simple do `context.has.SomeObject` instead
 * of `context.has('SomeObject')` and it will do the Right Thing™.
 * 
 * @param {string} ns - The global function/class/object we're looking for.
 * @returns {boolean} If that global name is defined or not.
 * @alias module:@lumjs/core/context.has
 */
function hasRoot(ns)
{
  if (typeof hasRoot[ns] === B) return hasRoot[ns];
  const result = rootHas(ns);
  def(hasRoot, ns, {value: result, enumerable: true});
  return result;
}

// Build some common has items.
for (const what of ['Proxy','Promise','Reflect','fetch'])
{
  hasRoot(what);
}

if (hasRoot.Proxy)
{ // Make a Proxy-wrapped version of `context.has`
  cd('has', new Proxy(hasRoot, 
  {
    get(t, p) { return (typeof t[p] !== U) ? t[p] : t(p) }
  }));
  // And include the unwrapped version for good measure.
  cd('$has', hasRoot);
}
else
{ // No Proxy support, just directly assign `context.has()`
  cd('has', hasRoot);
}

module.exports = ctx;