index.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /**
  2. * Filesystem cache
  3. *
  4. * Given a file and a transform function, cache the result into files
  5. * or retrieve the previously cached files if the given file is already known.
  6. *
  7. * @see https://github.com/babel/babel-loader/issues/34
  8. * @see https://github.com/babel/babel-loader/pull/41
  9. * @see https://github.com/babel/babel-loader/blob/master/src/fs-cache.js
  10. */
  11. var crypto = require("crypto");
  12. var mkdirp = require("mkdirp");
  13. var findCacheDir = require("find-cache-dir");
  14. var fs = require("fs");
  15. var os = require("os");
  16. var path = require("path");
  17. var zlib = require("zlib");
  18. var defaultCacheDirectory = null; // Lazily instantiated when needed
  19. /**
  20. * Read the contents from the compressed file.
  21. *
  22. * @async
  23. * @params {String} filename
  24. * @params {Function} callback
  25. */
  26. var read = function(filename, callback) {
  27. return fs.readFile(filename, function(err, data) {
  28. if (err) {
  29. return callback(err);
  30. }
  31. return zlib.gunzip(data, function(err, content) {
  32. var result = {};
  33. if (err) {
  34. return callback(err);
  35. }
  36. try {
  37. result = JSON.parse(content);
  38. } catch (e) {
  39. return callback(e);
  40. }
  41. return callback(null, result);
  42. });
  43. });
  44. };
  45. /**
  46. * Write contents into a compressed file.
  47. *
  48. * @async
  49. * @params {String} filename
  50. * @params {String} result
  51. * @params {Function} callback
  52. */
  53. var write = function(filename, result, callback) {
  54. var content = JSON.stringify(result);
  55. return zlib.gzip(content, function(err, data) {
  56. if (err) {
  57. return callback(err);
  58. }
  59. return fs.writeFile(filename, data, callback);
  60. });
  61. };
  62. /**
  63. * Build the filename for the cached file
  64. *
  65. * @params {String} source File source code
  66. * @params {Object} options Options used
  67. *
  68. * @return {String}
  69. */
  70. var filename = function(source, identifier, options) {
  71. var hash = crypto.createHash("SHA1");
  72. var contents = JSON.stringify({
  73. source: source,
  74. options: options,
  75. identifier: identifier
  76. });
  77. hash.end(contents);
  78. return hash.read().toString("hex") + ".json.gz";
  79. };
  80. /**
  81. * Handle the cache
  82. *
  83. * @params {String} directory
  84. * @params {Object} params
  85. * @params {Function} callback
  86. */
  87. var handleCache = function(directory, params, callback) {
  88. var source = params.source;
  89. var options = params.options || {};
  90. var transform = params.transform;
  91. var identifier = params.identifier;
  92. var shouldFallback = typeof params.directory !== "string" &&
  93. directory !== os.tmpdir();
  94. // Make sure the directory exists.
  95. mkdirp(directory, function(err) {
  96. // Fallback to tmpdir if node_modules folder not writable
  97. if (err)
  98. return shouldFallback
  99. ? handleCache(os.tmpdir(), params, callback)
  100. : callback(err);
  101. var file = path.join(directory, filename(source, identifier, options));
  102. return read(file, function(err, content) {
  103. var result = {};
  104. // No errors mean that the file was previously cached
  105. // we just need to return it
  106. if (!err) return callback(null, content);
  107. // Otherwise just transform the file
  108. // return it to the user asap and write it in cache
  109. try {
  110. result = transform(source, options);
  111. } catch (error) {
  112. return callback(error);
  113. }
  114. return write(file, result, function(err) {
  115. // Fallback to tmpdir if node_modules folder not writable
  116. if (err)
  117. return shouldFallback
  118. ? handleCache(os.tmpdir(), params, callback)
  119. : callback(err);
  120. callback(null, result);
  121. });
  122. });
  123. });
  124. };
  125. /**
  126. * Retrieve file from cache, or create a new one for future reads
  127. *
  128. * @async
  129. * @param {Object} params
  130. * @param {String} params.directory Directory to store cached files
  131. * @param {String} params.identifier Unique identifier to bust cache
  132. * @param {String} params.source Original contents of the file to be cached
  133. * @param {Object} params.options Options to be given to the transform fn
  134. * @param {Function} params.transform Function that will transform the
  135. * original file and whose result will be
  136. * cached
  137. *
  138. * @param {Function<err, result>} callback
  139. *
  140. * @example
  141. *
  142. * cache({
  143. * directory: '.tmp/cache',
  144. * identifier: 'babel-loader-cachefile',
  145. * source: *source code from file*,
  146. * options: {
  147. * experimental: true,
  148. * runtime: true
  149. * },
  150. * transform: function(source, options) {
  151. * var content = *do what you need with the source*
  152. * return content
  153. * }
  154. * }, function(err, result) {
  155. *
  156. * })
  157. */
  158. module.exports = function createFsCache(name) {
  159. return function(params, callback) {
  160. var directory;
  161. if (typeof params.directory === "string") {
  162. directory = params.directory;
  163. } else {
  164. if (defaultCacheDirectory === null) {
  165. defaultCacheDirectory = findCacheDir({
  166. name: name
  167. }) ||
  168. os.tmpdir();
  169. }
  170. directory = defaultCacheDirectory;
  171. }
  172. handleCache(directory, params, callback);
  173. };
  174. };