PageRenderTime 544ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/c9.static/cdn.js

https://gitlab.com/c9-mirror/core
JavaScript | 281 lines | 242 code | 35 blank | 4 comment | 47 complexity | 4e783deb257db2cd558dc1c1d198bc4d MD5 | raw file
  1. "use strict";
  2. main.consumes = [
  3. "connect",
  4. "connect.cors",
  5. "connect.static",
  6. "cdn.build"
  7. ];
  8. main.provides = [];
  9. module.exports = main;
  10. function main(options, imports, register) {
  11. var connect = imports.connect;
  12. var build = imports["cdn.build"];
  13. var connectStatic = imports["connect.static"];
  14. var fs = require("fs");
  15. var path = require("path");
  16. var send = require("send");
  17. var mkdirp = require("mkdirp");
  18. var atomic = require("c9/atomic");
  19. var error = require("http-error");
  20. var frontdoor = require("frontdoor");
  21. var cacheFiles = options.cacheFiles;
  22. var api = frontdoor();
  23. connect.use(api);
  24. var section = api.section("static");
  25. var resolveModulePath = require("architect-build/module-deps").resolveModulePath;
  26. connectStatic.getRequireJsConfig().useCache = options.useBrowserCache;
  27. section.post("/__check__", [function(req, res, next) {
  28. req.params.hash = "any";
  29. next();
  30. }, prepare, function(req, res, next) {
  31. function FsQ() {
  32. this.buffer = [];
  33. this.process = function() {};
  34. this.active = 0;
  35. this.ended = false;
  36. this.maxActive = 100;
  37. }
  38. FsQ.prototype.write = function(arr) {
  39. this.buffer.push.apply(this.buffer, arr);
  40. this.take();
  41. };
  42. FsQ.prototype.take = function(arr) {
  43. while (this.buffer.length && this.active < this.maxActive) {
  44. this.process(this.buffer.pop());
  45. this.active++;
  46. }
  47. };
  48. FsQ.prototype.oneDone = function() {
  49. this.active--;
  50. if (!this.active)
  51. this.take();
  52. if (!this.active && this.ended)
  53. this.end();
  54. };
  55. res.writeHead(200, {
  56. "Content-Type": "application/javascript",
  57. "Cache-Control": "no-cache, no-store"
  58. });
  59. req.setEncoding("utf8");
  60. var buffer = "";
  61. var t = Date.now();
  62. var q = new FsQ();
  63. var lastCssChange = 0;
  64. var compiledSkins = {};
  65. q.process = function(e) {
  66. var parts = e.split(" ");
  67. var id = parts[1];
  68. var etag = parts[0];
  69. if (!id || /^https?:\/\//.test(id))
  70. return q.oneDone();
  71. var path = resolveModulePath(id, req.pathConfig.pathMap);
  72. if (path == id && !/^(\/|\w:)/.test(path)) {
  73. path = build.cacheDir + "/" + path;
  74. if (/^\w+\/skin\//.test(id)) {
  75. compiledSkins[id] = -1;
  76. }
  77. }
  78. fs.stat(path, function(err, s) {
  79. if (!err) {
  80. var mt = s.mtime.valueOf();
  81. var etagNew = '"' + s.size +"-" + mt + '"';
  82. if (etag !== etagNew) {
  83. err = true;
  84. }
  85. }
  86. if (compiledSkins[id]) {
  87. compiledSkins[id] = mt || -1;
  88. }
  89. else if (err) {
  90. if (/\.(css|less)/.test(id))
  91. lastCssChange = Math.max(lastCssChange, s ? s.mtime.valueOf() : Infinity);
  92. res.write(id + "\n");
  93. }
  94. q.oneDone();
  95. });
  96. };
  97. q.end = function() {
  98. if (!q.buffer.length && !q.active) {
  99. if (compiledSkins) {
  100. Object.keys(compiledSkins).forEach(function(key) {
  101. if (compiledSkins[key] < lastCssChange) {
  102. res.write(key + "\n");
  103. fs.unlink(build.cacheDir + "/" + key, function() {
  104. console.info("Deleting old skin", key);
  105. });
  106. }
  107. });
  108. }
  109. res.write("\n");
  110. res.end();
  111. console.info("Checking cache state took:", t - Date.now(), "ms");
  112. }
  113. else {
  114. q.ended = true;
  115. }
  116. };
  117. function onData(e) {
  118. var parts = (buffer + e).split("\n");
  119. buffer = parts.pop();
  120. q.write(parts);
  121. // console.log(i++);
  122. }
  123. function onEnd(e) {
  124. console.log("end", t - Date.now());
  125. q.end();
  126. }
  127. if (req.body) {
  128. // TODO disable automatic buffering in connect
  129. onData(Object.keys(req.body)[0]);
  130. onEnd();
  131. } else {
  132. req.on("end", onEnd);
  133. req.on("data", onData);
  134. }
  135. }]);
  136. // section.use(foreverCache());
  137. section.use(imports["connect.cors"].cors("*"));
  138. section.use(connect.getModule().compress());
  139. section.get("/:hash/config/:name", [prepare, function(req, res, next) {
  140. var name = req.params.name.replace(/\.js$/, "");
  141. var file = path.join(build.cacheDir, req.params.hash, "config", name + ".js");
  142. sendCached(file, req, res, next, function(callback) {
  143. build.buildConfig(name, req.pathConfig, function(err, result) {
  144. callback(err, result && result.code || "");
  145. });
  146. });
  147. }]);
  148. section.get("/:hash/skin/:name/:color", [prepare, function(req, res, next) {
  149. var color = req.params.color.replace(/\.css$/, "");
  150. var file = path.join(build.cacheDir, req.params.hash, "skin", req.params.name, color + ".css");
  151. sendCached(file, req, res, next, function(callback) {
  152. build.buildSkin(req.params.name, color, req.pathConfig, function(err, result) {
  153. callback(err, result && result.code || "");
  154. });
  155. });
  156. }]);
  157. section.get("/:hash/modules/:module*", [prepare, function(req, res, next) {
  158. var module = req.params.module.replace(/^\//, "").replace(/\.js$/, "");
  159. var file = path.join(build.cacheDir, req.params.hash, "modules", module + ".js");
  160. sendCached(file, req, res, next, function(callback) {
  161. build.buildModule(module, req.pathConfig, function(err, result) {
  162. callback(err, result && result.code || "");
  163. });
  164. });
  165. }]);
  166. section.get("/:hash/worker/:module*", [prepare, function(req, res, next) {
  167. var module = req.params.module.replace(/^\//, "").replace(/\.js$/, "");
  168. var file = path.join(build.cacheDir, req.params.hash, "worker", module + ".js");
  169. sendCached(file, req, res, next, function(callback) {
  170. build.buildWorker(module, req.pathConfig, function(err, result) {
  171. callback(err, result && result.code || "");
  172. });
  173. });
  174. }]);
  175. section.get("/:hash/static/:path*", [prepare, function(req, res, next) {
  176. send(req, req.params.path.replace(/^\//, ""))
  177. .root(path.join(build.cacheDir, req.params.hash, "static"))
  178. .on('error', onSendError(next))
  179. .pipe(res);
  180. }]);
  181. register();
  182. function sendCached(filename, req, res, next, loader) {
  183. console.log("cache", filename);
  184. fs.exists(filename, function(exists) {
  185. if (exists && cacheFiles) {
  186. console.log("cache hit", filename);
  187. var transfer = send(req, filename);
  188. if (path.sep === "/")
  189. transfer.root("/");
  190. transfer
  191. .on("error", onSendError(next))
  192. .pipe(res);
  193. return;
  194. }
  195. loader(function(err, code) {
  196. if (err) return next(err);
  197. var type = "text/javascript";
  198. if (filename.match(/\.css$/))
  199. type = "text/css";
  200. res.setHeader("Content-Type", type);
  201. var mtime = Math.floor(Date.now() / 1000) * 1000;
  202. res.setHeader("ETAG", '"' + Buffer.byteLength(code) + "-" + mtime + '"');
  203. res.statusCode = 200;
  204. res.end(code);
  205. if (!cacheFiles) return;
  206. mkdirp(path.dirname(filename), function(err) {
  207. if (err)
  208. console.error("Error caching file", filename, err);
  209. atomic.writeFile(filename, code, "utf8", function(err) {
  210. if (err)
  211. return console.error("Caching file", filename, "failed", err);
  212. console.log("File cached at", filename);
  213. // set utime to have consistent etag
  214. fs.utimes(filename, mtime/1000, mtime/1000, function(e) {
  215. if (e) console.error(e);
  216. });
  217. });
  218. });
  219. });
  220. });
  221. }
  222. function onSendError(next) {
  223. return function(err) {
  224. if (err.status == 404)
  225. next(new error.NotFound());
  226. else
  227. next(err);
  228. };
  229. }
  230. function prepare(req, res, next) {
  231. var hash = req.params.hash;
  232. if (!hash.match(/^[a-z0-9]+$/))
  233. return next(new error.NotFound());
  234. build.getPathConfig(hash, function(err, pathConfig) {
  235. if (err) return next(err);
  236. req.pathConfig = pathConfig;
  237. next();
  238. });
  239. }
  240. function foreverCache() {
  241. return function(req, res, next) {
  242. res.setHeader("Cache-Control", "public, max-age=31556926");
  243. next();
  244. };
  245. }
  246. }