CachedSource.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Source = require("./Source");
  7. const streamChunksOfSourceMap = require("./helpers/streamChunksOfSourceMap");
  8. const streamChunksOfRawSource = require("./helpers/streamChunksOfRawSource");
  9. const streamAndGetSourceAndMap = require("./helpers/streamAndGetSourceAndMap");
  10. const mapToBufferedMap = map => {
  11. if (typeof map !== "object" || !map) return map;
  12. const bufferedMap = Object.assign({}, map);
  13. if (map.mappings) {
  14. bufferedMap.mappings = Buffer.from(map.mappings, "utf-8");
  15. }
  16. if (map.sourcesContent) {
  17. bufferedMap.sourcesContent = map.sourcesContent.map(
  18. str => str && Buffer.from(str, "utf-8")
  19. );
  20. }
  21. return bufferedMap;
  22. };
  23. const bufferedMapToMap = bufferedMap => {
  24. if (typeof bufferedMap !== "object" || !bufferedMap) return bufferedMap;
  25. const map = Object.assign({}, bufferedMap);
  26. if (bufferedMap.mappings) {
  27. map.mappings = bufferedMap.mappings.toString("utf-8");
  28. }
  29. if (bufferedMap.sourcesContent) {
  30. map.sourcesContent = bufferedMap.sourcesContent.map(
  31. buffer => buffer && buffer.toString("utf-8")
  32. );
  33. }
  34. return map;
  35. };
  36. class CachedSource extends Source {
  37. constructor(source, cachedData) {
  38. super();
  39. this._source = source;
  40. this._cachedSourceType = cachedData ? cachedData.source : undefined;
  41. this._cachedSource = undefined;
  42. this._cachedBuffer = cachedData ? cachedData.buffer : undefined;
  43. this._cachedSize = cachedData ? cachedData.size : undefined;
  44. this._cachedMaps = cachedData ? cachedData.maps : new Map();
  45. this._cachedHashUpdate = cachedData ? cachedData.hash : undefined;
  46. }
  47. getCachedData() {
  48. const bufferedMaps = new Map();
  49. for (const pair of this._cachedMaps) {
  50. let cacheEntry = pair[1];
  51. if (cacheEntry.bufferedMap === undefined) {
  52. cacheEntry.bufferedMap = mapToBufferedMap(
  53. this._getMapFromCacheEntry(cacheEntry)
  54. );
  55. }
  56. bufferedMaps.set(pair[0], {
  57. map: undefined,
  58. bufferedMap: cacheEntry.bufferedMap
  59. });
  60. }
  61. // We don't want to cache strings
  62. // So if we have a caches sources
  63. // create a buffer from it and only store
  64. // if it was a Buffer or string
  65. if (this._cachedSource) {
  66. this.buffer();
  67. }
  68. return {
  69. buffer: this._cachedBuffer,
  70. source:
  71. this._cachedSourceType !== undefined
  72. ? this._cachedSourceType
  73. : typeof this._cachedSource === "string"
  74. ? true
  75. : Buffer.isBuffer(this._cachedSource)
  76. ? false
  77. : undefined,
  78. size: this._cachedSize,
  79. maps: bufferedMaps,
  80. hash: this._cachedHashUpdate
  81. };
  82. }
  83. originalLazy() {
  84. return this._source;
  85. }
  86. original() {
  87. if (typeof this._source === "function") this._source = this._source();
  88. return this._source;
  89. }
  90. source() {
  91. const source = this._getCachedSource();
  92. if (source !== undefined) return source;
  93. return (this._cachedSource = this.original().source());
  94. }
  95. _getMapFromCacheEntry(cacheEntry) {
  96. if (cacheEntry.map !== undefined) {
  97. return cacheEntry.map;
  98. } else if (cacheEntry.bufferedMap !== undefined) {
  99. return (cacheEntry.map = bufferedMapToMap(cacheEntry.bufferedMap));
  100. }
  101. }
  102. _getCachedSource() {
  103. if (this._cachedSource !== undefined) return this._cachedSource;
  104. if (this._cachedBuffer && this._cachedSourceType !== undefined) {
  105. return (this._cachedSource = this._cachedSourceType
  106. ? this._cachedBuffer.toString("utf-8")
  107. : this._cachedBuffer);
  108. }
  109. }
  110. buffer() {
  111. if (this._cachedBuffer !== undefined) return this._cachedBuffer;
  112. if (this._cachedSource !== undefined) {
  113. if (Buffer.isBuffer(this._cachedSource)) {
  114. return (this._cachedBuffer = this._cachedSource);
  115. }
  116. return (this._cachedBuffer = Buffer.from(this._cachedSource, "utf-8"));
  117. }
  118. if (typeof this.original().buffer === "function") {
  119. return (this._cachedBuffer = this.original().buffer());
  120. }
  121. const bufferOrString = this.source();
  122. if (Buffer.isBuffer(bufferOrString)) {
  123. return (this._cachedBuffer = bufferOrString);
  124. }
  125. return (this._cachedBuffer = Buffer.from(bufferOrString, "utf-8"));
  126. }
  127. size() {
  128. if (this._cachedSize !== undefined) return this._cachedSize;
  129. if (this._cachedBuffer !== undefined) {
  130. return (this._cachedSize = this._cachedBuffer.length);
  131. }
  132. const source = this._getCachedSource();
  133. if (source !== undefined) {
  134. return (this._cachedSize = Buffer.byteLength(source));
  135. }
  136. return (this._cachedSize = this.original().size());
  137. }
  138. sourceAndMap(options) {
  139. const key = options ? JSON.stringify(options) : "{}";
  140. const cacheEntry = this._cachedMaps.get(key);
  141. // Look for a cached map
  142. if (cacheEntry !== undefined) {
  143. // We have a cached map in some representation
  144. const map = this._getMapFromCacheEntry(cacheEntry);
  145. // Either get the cached source or compute it
  146. return { source: this.source(), map };
  147. }
  148. // Look for a cached source
  149. let source = this._getCachedSource();
  150. // Compute the map
  151. let map;
  152. if (source !== undefined) {
  153. map = this.original().map(options);
  154. } else {
  155. // Compute the source and map together.
  156. const sourceAndMap = this.original().sourceAndMap(options);
  157. source = sourceAndMap.source;
  158. map = sourceAndMap.map;
  159. this._cachedSource = source;
  160. }
  161. this._cachedMaps.set(key, {
  162. map,
  163. bufferedMap: undefined
  164. });
  165. return { source, map };
  166. }
  167. streamChunks(options, onChunk, onSource, onName) {
  168. const key = options ? JSON.stringify(options) : "{}";
  169. if (
  170. this._cachedMaps.has(key) &&
  171. (this._cachedBuffer !== undefined || this._cachedSource !== undefined)
  172. ) {
  173. const { source, map } = this.sourceAndMap(options);
  174. if (map) {
  175. return streamChunksOfSourceMap(
  176. source,
  177. map,
  178. onChunk,
  179. onSource,
  180. onName,
  181. !!(options && options.finalSource),
  182. true
  183. );
  184. } else {
  185. return streamChunksOfRawSource(
  186. source,
  187. onChunk,
  188. onSource,
  189. onName,
  190. !!(options && options.finalSource)
  191. );
  192. }
  193. }
  194. const { result, source, map } = streamAndGetSourceAndMap(
  195. this.original(),
  196. options,
  197. onChunk,
  198. onSource,
  199. onName
  200. );
  201. this._cachedSource = source;
  202. this._cachedMaps.set(key, {
  203. map,
  204. bufferedMap: undefined
  205. });
  206. return result;
  207. }
  208. map(options) {
  209. const key = options ? JSON.stringify(options) : "{}";
  210. const cacheEntry = this._cachedMaps.get(key);
  211. if (cacheEntry !== undefined) {
  212. return this._getMapFromCacheEntry(cacheEntry);
  213. }
  214. const map = this.original().map(options);
  215. this._cachedMaps.set(key, {
  216. map,
  217. bufferedMap: undefined
  218. });
  219. return map;
  220. }
  221. updateHash(hash) {
  222. if (this._cachedHashUpdate !== undefined) {
  223. for (const item of this._cachedHashUpdate) hash.update(item);
  224. return;
  225. }
  226. const update = [];
  227. let currentString = undefined;
  228. const tracker = {
  229. update: item => {
  230. if (typeof item === "string" && item.length < 10240) {
  231. if (currentString === undefined) {
  232. currentString = item;
  233. } else {
  234. currentString += item;
  235. if (currentString.length > 102400) {
  236. update.push(Buffer.from(currentString));
  237. currentString = undefined;
  238. }
  239. }
  240. } else {
  241. if (currentString !== undefined) {
  242. update.push(Buffer.from(currentString));
  243. currentString = undefined;
  244. }
  245. update.push(item);
  246. }
  247. }
  248. };
  249. this.original().updateHash(tracker);
  250. if (currentString !== undefined) {
  251. update.push(Buffer.from(currentString));
  252. }
  253. for (const item of update) hash.update(item);
  254. this._cachedHashUpdate = update;
  255. }
  256. }
  257. module.exports = CachedSource;