Jump To …

Middleware.coffee

module.exports = class Middleware
  constructor: (@options, @compilers, @cache) ->

Handles incoming requests.

  intercept: (req, res, next) ->
    return next() unless req.method is 'GET'
    targetPath = path.join @options.src, parse(req.url).pathname
    return next() if targetPath.slice(-1) is '/'  # ignore directory requests
    cachedTarget = @cache[targetPath]
    if cachedTarget and (!cachedTarget.mtime)  # memory content
      {static, str} = cachedTarget
      return sendFile(res, next, {static, str, targetPath})
    fs.stat targetPath, (err, stats) ->

if the file exists, serve it

      return serveRaw req, res, next, {stats, targetPath} unless err

if the file doesn't exist, see if it can be compiled

      for ext, compiler of compilers
        if compiler.match.test targetPath
          return serveCompiled req, res, next, {compiler, ext, targetPath}

otherwise, pass the request up the Connect stack

      next()

  serveRaw = (req, res, next, {stats, targetPath}) ->
    if cache[targetPath]?.mtime
      unless stats.mtime > cache[targetPath].mtime
        str = cache[targetPath].str
        return sendFile res, next, {str, stats, targetPath}
    fs.readFile targetPath, 'utf8', sendCallback(res, next, {stats, targetPath})

  serveCompiled = (req, res, next, {compiler, ext, targetPath}) ->
    srcPath = targetPath.replace(compiler.match, ".#{ext}")
    fs.stat srcPath, (err, stats) ->
      return next() if err?.code is 'ENOENT'  # no file, no problem!
      return next err if err
      if cache[targetPath]?.mtime
        unless stats.mtime > cache[targetPath].mtime
          if compiler is compilers.styl  # @import-ed files may have changed
            cache[targetPath].str = compiler.compileStr cache[srcPath].str, srcPath
          str = cache[targetPath].str
          return sendFile res, next, {str, stats, targetPath}
      compiler.compile srcPath, sendCallback(res, next, {stats, targetPath})

  sendCallback = (res, next, {static, stats, targetPath}) ->
    (err, str) ->
      return next err if err
      sendFile res, next, {str, static, stats, targetPath}

  sendFile = (res, next, {str, static, stats, targetPath}) ->
    if stats then cache[targetPath] = {mtime: stats.mtime, str}
    res.setHeader 'Content-Type', mime.lookup(targetPath)
    res.setHeader 'Expires', module.exports.FAR_FUTURE_EXPIRES if static
    res.end str

constants

FAR_FUTURE_EXPIRES = "Wed, 01 Feb 2034 12:34:56 GMT"