| @@ -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 | |||