@@ -106,3 +106,35 @@ Serve static files. | |||
* `app.get("^/static/.*", webframe.static("web", "/static"))` will serve all | |||
files in the `web` directory under `/static`, so `/static/script.js` will | |||
serve `web/script.js`. | |||
### Transformations - app.transform(extension, mime, function) | |||
Webframe has a way to transform files of one file type to an other, for example | |||
transforming typescript into javascript, or sass into CSS. | |||
Example (assumes you have the node-sass module installed): | |||
``` | |||
var sass = require("node-sass"); | |||
app.transform("sass", "text/css", (path, writeStream) => { | |||
try { | |||
var str = fs.readFileSync(path, "utf8"); | |||
} catch (err) { | |||
ws.status = 404; | |||
return ws.error(err); | |||
} | |||
try { | |||
var result = sass.renderSync({ data: str }); | |||
writeStream.end(result.css); | |||
} catch (err) { | |||
writeStream.error(err); | |||
} | |||
}); | |||
``` | |||
Now, any .sass file served with `webframe.static` will be compiled into CSS, | |||
and sent with the browser with the MIME type "text/css". The result is also | |||
cached, so the expensive sass compilation will only happen the first time the | |||
file is requested. The result will not be cached if you call writeStream.error. |
@@ -53,6 +53,7 @@ class App { | |||
this._routeMap = {}; | |||
this._routes = []; | |||
this._transforms = {}; | |||
// Add scripts | |||
if (options.client_utils) { | |||
@@ -135,7 +136,7 @@ class App { | |||
* Args: | |||
* method: "GET", "POST", "PUT", "DELETE", "ALL" | |||
* path: path, | |||
* func: function | |||
* func: function(request, response) | |||
*/ | |||
route(method, path, middleware, func) { | |||
if (method !== "GET" && method !== "POST" && method !== "PUT" && | |||
@@ -185,6 +186,35 @@ class App { | |||
} | |||
} | |||
/* | |||
* Add a transform. | |||
* Args: | |||
* ext: What extension to transform | |||
* mime: The mime-type the transformed code should be sent as | |||
* func: function(path, writeStream) | |||
* | |||
* writeStream: { | |||
* status: status code (should only be modified before write() or end()) | |||
* headers: HTTP headers (should only be modified before write() or end()) | |||
* write: function(data) - write data | |||
* end: function(data) - write data and end response | |||
* error: function(err) - end response and log error | |||
* } | |||
*/ | |||
transform(ext, mime, func) { | |||
if (typeof ext !== "string") | |||
throw new Error("Expected ext to be string, got "+(typeof ext)+"."); | |||
if (typeof func !== "function") | |||
throw new Error("Expected func to be function, got "+(typeof func)+"."); | |||
if (this._transforms[ext]) | |||
throw new Error("Extension "+ext+" already has a transform."); | |||
this._transforms[ext] = { | |||
func: func, | |||
mime: mime | |||
}; | |||
} | |||
/* | |||
* Add code to be served as /webframe.js | |||
*/ | |||
@@ -201,12 +231,18 @@ class App { | |||
*/ | |||
template(tmpl, args) { return template(tmpl, args); } | |||
/* | |||
* Utility methods for GET/POST/PUT/DELETE/ALL | |||
*/ | |||
get(path, middleware, func) { this.route("GET", path, middleware, func); } | |||
post(path, middleware, func) { this.route("POST", path, middleware, func); } | |||
put(path, middleware, func) { this.route("PUT", path, middleware, func); } | |||
delete(path, middleware, func) { this.route("DELETE", path, middleware, func); } | |||
all(path, middleware, func) { this.route("ALL", path, middleware, func); } | |||
/* | |||
* Logging | |||
*/ | |||
info(str) { | |||
console.log("Info: "+str); | |||
} |
@@ -62,10 +62,80 @@ function sendfile(path, app, pathname, req, res) { | |||
}); | |||
} | |||
var transformCache = {}; | |||
function handleTransform(path, req, res, app) { | |||
var ext = pathlib.extname(req.url).substring(1); | |||
if (ext === "") | |||
return false; | |||
var transform = app._transforms[ext]; | |||
if (!transform) | |||
return false; | |||
if (transformCache[path]) { | |||
var c = transformCache[path]; | |||
res.writeHead(c.status, c.headers); | |||
res.end(c.content); | |||
return true; | |||
} | |||
var data = { content: "", headers: null, status: null }; | |||
var writeStream = { | |||
headersSent: false, | |||
status: 200, | |||
headers: { | |||
"content-type": transform.mime | |||
}, | |||
write: function(d) { | |||
if (!writeStream.headersSent) { | |||
res.writeHead(writeStream.status, writeStream.headers); | |||
writeStream.headersSent = true; | |||
} | |||
if (d != null) { | |||
data.content += d; | |||
res.write(d); | |||
} | |||
}, | |||
end: function(d, nocache) { | |||
writeStream.write(d); | |||
res.end(); | |||
if (!nocache) { | |||
data.status = writeStream.status; | |||
data.headers = writeStream.headers; | |||
transformCache[path] = data; | |||
} | |||
}, | |||
error: function(err) { | |||
if (writeStream.status === 200) | |||
writeStream.status = 500; | |||
app.warning("Transform for "+path+": "+err.toString()); | |||
writeStream.end(null, true); | |||
} | |||
} | |||
transform.func(path, writeStream); | |||
return true; | |||
} | |||
module.exports = function(root, before) { | |||
return function(req, res, app) { | |||
// `req.urlobj.pathname` is too long. | |||
var pn = req.urlobj.pathname; | |||
// Join the web root with the request's path name | |||
var path = pathlib.join(root, pn.replace(before, "")); | |||
// If there's a transform, let that handle it | |||
if (handleTransform(path, req, res, app)) | |||
return; | |||
// Send a file | |||
function send(path) { | |||
sendfile(path, app, pn, req, res); | |||
@@ -79,9 +149,6 @@ module.exports = function(root, before) { | |||
return; | |||
} | |||
// Join the web root with the request's path name | |||
var path = pathlib.join(root, pn.replace(before, "")); | |||
fs.stat(path, (err, stat) => { | |||
// If there's an error stat'ing, just error |