Jump To …

default.coffee

ModuleGroupFactory = require '../module_group_factory'

This convention attempts to adhere to a sane and relatively "standard" set of principles that should work for most node modules.

Of course, there really isn't anything close to a standard naming & package layout style, so you might want to take a look at the other conventions if the defaults don't do it for you. Or write your own!

Any options not specified in any other conventions fall back to those defined in this one.

An Example

Given this project hierarchy:

lib/
  things-and-baubles/
    stuff/
      moo.js
    gizmo.js
    many-doohickey.js
  fizz.js
  fizz_bam.js

The exported module hierarchy will be as follows (assuming that lib/ is autorequired):

{
  thingsAndBaubles: {
    stuff: {
      moo: <module.exports from moo.js>
    },
    gizmo: <module.exports from gizmo.js>
    manyDoohickey: <module.exports from many-doohickey.js>
  },
  fizz: <module.exports from fizz.js>
  fizzBam: <module.exports from fizz_bam.js>
}
class Default

Core Functionality

Given a directory, build and return the root module group for it.

  buildRootModuleGroup: (rootPath) ->
    ModuleGroupFactory.buildModuleGroup @, rootPath

Naming Conventions

Given a directory name and parent path, return the property name that should be used to represent it in the module's hierarchy.

The default is to convert to a camelCase style (splitting on - and _).

  directoryToProperty: (directoryName, parentPath) ->
    @camelCase directoryName

Given a file name and parent path, return the property that should be used to represent it in the module's hierarchy.

The default is to expose the file's exports as a camelCase form of the file name.

  fileToProperty: (fileName, parentPath) ->
    @camelCase @stripFileExtension fileName

Autorequired Source Evaluation

Gives the convention an opportunity to modify the sandbox of an autorequired source file before it is loaded. Returns the sandbox that was modified (in case you want to replace it).

By default, this registers several sets of globals for the module. See [globalLazyLoads] (#section-13).

  modifySandbox: (sandbox, module) ->
    module._globalLazyLoads = @globalLazyLoads(module)
    module._require         = sandbox.require

    sandbox

Gives the convention an opportunity to modify the source of a file before it is loaded. Returns the modified source.

By default, this works in concert with globalLazyLoads and sets up the registered properties to be lazy loaded. We have to drop down to the source level because the module is evaluated in a sandboxed context. The global context is shallow copied when entering or leaving the sandbox, and getter/setter properties are not preserved.

  modifySource: (source, module) ->
    """
    for (var key in module._globalLazyLoads) {
      try {
        Object.defineProperty(global, key, {
          enumerable: false, configurable: true, get: module._globalLazyLoads[key]
        });
      } catch (err) {}
    }
    delete module._globalLazyLoads;
    """ + source

Gives the convention an opportunity to modify the exports before they are returned. The convention can also peek into the state of the sandbox after evaluation if it wishes to pull out any properties defined globally within the evaluated module. Returns the modified exports.

By default, this is a no-op.

  modifyExports: (exports, module) ->
    exports

Autorequired Module Globals

Modules to globally register for autorequiring. All the core node libraries.

Each one will be registered with a name adhering to directoryToProperty. Notably absent is module, as it defines a prototype (and is not a namespaced module).

You probably don't want to override this list. If you wish to add extra modules, define them via extraGlobalAutorequires

  globalModules: [
    'assert', 'buffer', 'child_process', 'constants', 'crypto', 'dgram', 'dns', 'events',
    'freelist', 'fs', 'http', 'https', 'net', 'os', 'path', 'querystring', 'readline', 'repl',
    'stream', 'string_decoder', 'sys', 'timers', 'tls', 'tty', 'url', 'util', 'vm'
  ]

Any additional modules to add to the globalLazyLoads without modifying that list.

  extraGlobalModules: []

Returns an object of properties that should be lazy loaded as global objects in an autorequired source file.

It should be a map of property names to functions (that will be called on first reference, the result is then memoized).

This registers several sets of globals for the module:

  globalLazyLoads: (module) ->
    result = {}

    @appendGlobalModules(result, module)
    @appendSameLevelModules(result, module)
    @appendParentModules(result, module)

    result
  • All globals defined in appendGlobalModules are registered to be autorequired.
  appendGlobalModules: (lazyLoads, module) ->
    for mod in @globalModules.concat @extraGlobalModules
      do (mod) =>
        lazyLoads[@directoryToProperty(mod)] = -> module._require mod
  • All autorequireable properties in the same directory as the current module.
  appendSameLevelModules: (lazyLoads, module) ->
    for key of module.autorequireParent
      unless key == module.id
        do (key) ->
          lazyLoads[key] = -> module.autorequireParent[key]
  • All parent autorequirable properties up to the root autorequire for this package. (When the code is finished)
  appendParentModules: (lazyLoads, module) ->

For example, from within lib/things/gizmo.js (as part of the example outlined above):

  • You can reference any of the core node libraries without requiring them.
  • ManyDoohickey and stuff would be available for referencing.
  • things would also be available in order to traverse up the hierarchy.

Every one of these properties is lazy-loaded.

Helpers & Utility

Strips the extension from a file name.

  stripFileExtension: (fileName) ->
    fileName.match(/(.+?)(\.[^.]*$|$)/)[1]

CamelCaps a path component.

  camelCaps: (pathComponent) ->
    pathComponent.split(/[-_]+/).map((val) -> val[0].toLocaleUpperCase() + val[1..]).join ''

camelCase a path component.

  camelCase: (pathComponent) ->
    result = @camelCaps pathComponent
    result[0].toLocaleLowerCase() + result[1..]

under_score a path component.

  underscore: (pathComponent) ->
    pathComponent.split(/[-_]+/).join '_'

module.exports = Default