Skip to content

Instantly share code, notes, and snippets.

@Raynos
Created June 13, 2013 01:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Raynos/eaab3b4bffe5b211840e to your computer and use it in GitHub Desktop.
Save Raynos/eaab3b4bffe5b211840e to your computer and use it in GitHub Desktop.
var zlib = require("zlib")
var crypto = require("crypto")
var isGzip = /\bgzip\b/
module.exports = CompiledFiles
function CompiledFiles(config) {
config = config || {}
var contentType = config.contentType || "text/plain"
var sendError = config.sendError || defaultSendError
var compile = config.compile
var findResource = config.findResource
if (!config.findResource) {
throw new Error("Must pass in a resource finding function")
}
if (!config.compile) {
throw new Error("Must pass in a compile function")
}
return function ServeFile(opts) {
opts = opts || {}
var cache = opts.cache
var gzip = opts.gzip
var cacheControl = opts.cacheControl || "max-age=300, must-revalidate"
var compiledCache = {}
return function routeHandler(req, res) {
var location = findResource(req, opts)
res.setHeader("content-type", contentType)
if (cache && compiledCache[location]) {
return sendResource(req, res, compiledCache[location])
}
compile(location, opts, prepareResource)
function prepareResource(err, payload) {
if (err) {
return sendError(req, res, err)
}
if (gzip) {
zlib.gzip(payload, function (err, buffer) {
if (err) {
return sendError(req, res, err)
}
sendGzipPlain({ plain: payload, gzip: buffer })
})
} else {
sendGzipPlain({ plain: payload })
}
}
function sendGzipPlain(types) {
var hash = crypto
.createHash("md5")
.update(types.plain)
.digest("hex")
var chunk = new FileChunk(types.plain, types.gzip, hash)
if (cache) {
compiledCache[location] = chunk
}
sendResource(req, res, chunk)
}
}
function sendResource(req, res, chunk) {
var reqEtag = req.headers["if-none-match"] || ""
if (cache && reqEtag === chunk.hash) {
res.statusCode = 304
return res.end()
}
res.statusCode = 200
if (cache) {
res.setHeader("Etag", chunk.hash)
res.setHeader("Cache-Control", cacheControl)
}
var acceptEncoding = req.headers["accept-encoding"] || ""
if (gzip && chunk.gzip && acceptEncoding.match(isGzip)) {
res.setHeader("content-encoding", "gzip")
return res.end(chunk.gzip)
}
return res.end(chunk.plain)
}
}
}
function defaultSendError(req, res, err) {
res.statusCode = 500
res.end(err.message)
}
/* findResource takes a Request, the opts passed to the ServeFile
function and a callback.
It's responsibility is to return a location on disk that the
Request is asking for.
*/
function FileChunk(plain, gzip, hash) {
this.plain = plain || null
this.gzip = gzip || null
this.hash = hash || null
}
var resolve = require("resolve")
var path = require("path")
var url = require("url")
var bundle = require("./bundle")
var CompiledFiles = require("./lib/compiled-files")
// var getLocation = require("./lib/get-location")
// /js/:appName
module.exports = CompiledFiles({
// this is the function to compile the resource.
// the location is the value returned by findResource
// you should pass a String to the callback
compile: function (location, opts, callback) {
resolve(location, function (err, fileUri) {
bundle(fileUri, opts, callback)
})
},
// This is the logic of how to display errors to the user
sendError: function sendError(req, res, err) {
return res.end("(" + function (err) {
throw new Error(err)
} + "(" + JSON.stringify(err.message) + "))")
},
// CompiledFiles will set this content type on the response
contentType: "application/javascript",
/*
findResource takes a Request, the opts passed to the ServeFile
function and a callback.
It's responsibility is to return a location on disk that the
Request is asking for.
*/
findResource: function findResource(req, res, opts) {
var pathname = url.parse(req.url).pathname
var parts = pathname.split("/")
return path.join(opts.root, parts[parts.length - 1])
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment