Jump To …

loader.coffee

assert = require 'assert'
fs     = require 'fs'
path   = require 'path'
vm     = require 'vm'
Module = require 'module'

Loader injects extra behavior into the standard module loader to honor the current convention.

class Loader extends Module
  constructor: (componentName, autorequireParent, convention) ->
    super componentName

    @convention        = convention
    @autorequireParent = autorequireParent

Load a module and return its exports, adhering to the given convention.

  @loadModule: (componentName, modulePath, autorequireParent, convention) ->
    loader = new this(componentName, autorequireParent, convention)
    loader.load(modulePath)

    loader.exports

Unfortunately, Node's Module prototype doesn't break _compile into meaningful chunks, so we're stuck re-implementing parts of it.

Note, however, that we put our foot down and only support sandboxed module evaluation. This allows us to mess with a module's global context with out messing up global for everyone else.

  _compile: (content, filename) ->
    throw new Error 'Compiling a root module is not supported by autorequire.' if @id == '.'

    content = @_cleanContent content
    sandbox = @_buildSandbox filename

    sandbox = @convention.modifySandbox sandbox, this if @convention.modifySandbox
    content = @convention.modifySource  content, this if @convention.modifySource

    vm.runInContext content, sandbox, filename, true

    @sandbox = sandbox
    @exports = @convention.modifyExports @exports, this if @convention.modifyExports

Compatibility Helpers

Performs sanitization on content to mirror the default behavior. Just tweaks the shebang atm.

This mirrors v0.4.11.

  _cleanContent: (content) ->
    content.replace /^\#\!.*/, ''

Builds the default sandbox for a module, mirroring default behavior

This mirrors v0.4.11.

  _buildSandbox: (filename) ->
    sandbox = vm.createContext {}
    for k, v of global
      sandbox[k] = v

    sandbox.require    = @_buildRequire()
    sandbox.exports    = @exports
    sandbox.__filename = filename
    sandbox.__dirname  = path.dirname filename
    sandbox.module     = this
    sandbox.global     = sandbox
    sandbox.root       = root

    sandbox

Builds the require() function for this module, and any properties on to duplicate the default Node behavior.

This mirrors v0.4.11.

  _buildRequire: ->
    self    = this
    require = (path) -> Module._load path, self

    require.resolve = (request) -> Module._resolveFilename(request, self)[1]
    require.paths   = Module._paths
    require.main    = process.mainModule

    require.extensions = Module._extensions
    require.registerExtension = ->
      throw new Error 'require.registerExtension() removed. Use require.extensions instead.'

    require.cache = Module._cache

    require

Overrides the built in load so that we can perform extension-specific behavior.

It will not override an extension that isn't already registered with require.extensions.

This mirrors v0.4.11.

  load: (filename) ->
    assert.ok not @loaded

    @filename = filename
    @paths    = Module._nodeModulePaths path.dirname filename

    extension = path.extname filename
    extension = '.js' if not Module._extensions[extension]

    (@_extensions[extension] || Module._extensions[extension])(this, filename)

    @loaded = true

Extension Specific Helpers

  _extensions:

We want to load coffeescript sources without the wrapper - we're already evaluating them within a context, so they won't leak into the global context. This lets conventions pull defined properties out of the context w/o having to resort to module.exports.

This mirrors coffee-script v1.1.2.

    '.coffee': (module, filename) ->
      content = require('coffee-script').compile fs.readFileSync(filename, 'utf8'),
        filename: filename, bare: true
      module._compile content, filename

module.exports = Loader