index.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. "use strict";
  2. var etag = require("etag");
  3. var fresh = require("fresh");
  4. var fs = require("fs");
  5. var path = require("path");
  6. var zlib = require("zlib");
  7. var minifiedScript = path.join(__dirname, "dist", "index.js");
  8. var unminifiedScript = path.join(__dirname, "dist", "index.js");
  9. /**
  10. * Does the current request support compressed encoding?
  11. * @param {Object} req
  12. * @returns {boolean}
  13. */
  14. function supportsGzip(req) {
  15. var accept = req.headers["accept-encoding"];
  16. return accept && accept.indexOf("gzip") > -1;
  17. }
  18. /**
  19. * Set headers on the response
  20. * @param {Object} res
  21. * @param {String} body
  22. */
  23. function setHeaders(res, body) {
  24. res.setHeader("Cache-Control", "public, max-age=0");
  25. res.setHeader("Content-Type", "text/javascript");
  26. res.setHeader("ETag", etag(body));
  27. }
  28. /**
  29. * @param {Object} req
  30. * @returns {String}
  31. */
  32. function isConditionalGet(req) {
  33. return req.headers["if-none-match"] || req.headers["if-modified-since"];
  34. }
  35. /**
  36. * Return a not-modified response
  37. * @param {Object} res
  38. */
  39. function notModified(res) {
  40. res.removeHeader("Content-Type");
  41. res.statusCode = 304;
  42. res.end();
  43. }
  44. function processItems(items) {
  45. return [].concat(items)
  46. .filter(Boolean)
  47. .reduce((stringOutput, item) => {
  48. if (typeof item === 'string') {
  49. return stringOutput + item;
  50. }
  51. if (typeof item === 'function') {
  52. return stringOutput + item();
  53. }
  54. return stringOutput;
  55. }, "");
  56. }
  57. /**
  58. * Public method for returning either a middleware fn
  59. * or the content as a string
  60. * @param {Object} options
  61. * @param requestBody
  62. * @param {String} type - either `file` or `middleware`
  63. * @returns {*}
  64. */
  65. function init(options, requestBody, type) {
  66. /**
  67. * If the user asked for a file, simply return the string.
  68. */
  69. if (type && type === "file") {
  70. return processItems(requestBody);
  71. }
  72. /**
  73. * Otherwise return a function to be used a middleware
  74. */
  75. return function(req, res) {
  76. /**
  77. * default to using the uncompressed string
  78. * @type {String}
  79. */
  80. var output = processItems(requestBody);
  81. /**
  82. * Set the appropriate headers for caching
  83. */
  84. setHeaders(res, output);
  85. var resHeaders = res.getHeaders ? res.getHeaders() : res._headers;
  86. if (isConditionalGet(req) && fresh(req.headers, resHeaders)) {
  87. return notModified(res);
  88. }
  89. /**
  90. * If gzip is supported, compress the string once
  91. * and save for future requests
  92. */
  93. if (supportsGzip(req)) {
  94. res.setHeader("Content-Encoding", "gzip");
  95. var buf = Buffer.from(output, "utf-8");
  96. zlib.gzip(buf, function(_, result) {
  97. res.end(result);
  98. });
  99. } else {
  100. res.end(output);
  101. }
  102. };
  103. }
  104. module.exports.middleware = init;
  105. module.exports.plugin = init;
  106. module.exports.minified = function() {
  107. return fs.readFileSync(minifiedScript, "utf8");
  108. };
  109. module.exports.unminified = function() {
  110. return fs.readFileSync(unminifiedScript, "utf8");
  111. };