ConcatSource.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const RawSource = require("./RawSource");
  7. const Source = require("./Source");
  8. const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
  9. const streamChunks = require("./helpers/streamChunks");
  10. /** @typedef {import("./CompatSource").SourceLike} SourceLike */
  11. /** @typedef {import("./Source").ClearCacheOptions} ClearCacheOptions */
  12. /** @typedef {import("./Source").HashLike} HashLike */
  13. /** @typedef {import("./Source").MapOptions} MapOptions */
  14. /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
  15. /** @typedef {import("./Source").SourceAndMap} SourceAndMap */
  16. /** @typedef {import("./Source").SourceValue} SourceValue */
  17. /** @typedef {import("./helpers/getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */
  18. /** @typedef {import("./helpers/streamChunks").OnChunk} OnChunk */
  19. /** @typedef {import("./helpers/streamChunks").OnName} OnName */
  20. /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
  21. /** @typedef {import("./helpers/streamChunks").Options} Options */
  22. /** @typedef {string | Source | SourceLike} Child */
  23. const stringsAsRawSources = new WeakSet();
  24. class ConcatSource extends Source {
  25. /**
  26. * @param {Child[]} args children
  27. */
  28. constructor(...args) {
  29. super();
  30. /**
  31. * @private
  32. * @type {Child[]}
  33. */
  34. this._children = [];
  35. // Indexed loops avoid the iterator-protocol overhead `for...of`
  36. // pays per element. Hot during webpack's emit when many
  37. // ConcatSources are constructed/flattened.
  38. for (let i = 0, l = args.length; i < l; i++) {
  39. const item = args[i];
  40. if (item instanceof ConcatSource) {
  41. const children = item._children;
  42. for (let j = 0, jl = children.length; j < jl; j++) {
  43. this._children.push(children[j]);
  44. }
  45. } else {
  46. this._children.push(item);
  47. }
  48. }
  49. /**
  50. * @private
  51. * @type {boolean}
  52. */
  53. this._isOptimized = args.length === 0;
  54. }
  55. /**
  56. * @returns {Source[]} children
  57. */
  58. getChildren() {
  59. if (!this._isOptimized) this._optimize();
  60. return /** @type {Source[]} */ (this._children);
  61. }
  62. /**
  63. * @param {Child} item item
  64. * @returns {void}
  65. */
  66. add(item) {
  67. if (item instanceof ConcatSource) {
  68. const children = item._children;
  69. for (let i = 0, l = children.length; i < l; i++) {
  70. this._children.push(children[i]);
  71. }
  72. } else {
  73. this._children.push(item);
  74. }
  75. this._isOptimized = false;
  76. }
  77. /**
  78. * @param {Child[]} items items
  79. * @returns {void}
  80. */
  81. addAllSkipOptimizing(items) {
  82. for (let i = 0, l = items.length; i < l; i++) {
  83. this._children.push(items[i]);
  84. }
  85. }
  86. /**
  87. * @returns {Buffer} buffer
  88. */
  89. buffer() {
  90. return Buffer.concat(this.buffers());
  91. }
  92. /**
  93. * @returns {Buffer[]} buffers
  94. */
  95. buffers() {
  96. if (!this._isOptimized) this._optimize();
  97. const children = /** @type {SourceLike[]} */ (this._children);
  98. const childCount = children.length;
  99. /** @type {Buffer[]} */
  100. const buffers = [];
  101. // Indexed loop + manual splat avoids the iterator allocation per
  102. // child and the inner for-of allocation per child.buffers() call.
  103. // Hot path during webpack's emit on deeply-nested ConcatSources.
  104. for (let ci = 0; ci < childCount; ci++) {
  105. const child = children[ci];
  106. if (typeof child.buffers === "function") {
  107. const childBuffers = child.buffers();
  108. for (let bi = 0, blen = childBuffers.length; bi < blen; bi++) {
  109. buffers.push(childBuffers[bi]);
  110. }
  111. } else if (typeof child.buffer === "function") {
  112. buffers.push(child.buffer());
  113. } else {
  114. const bufferOrString = child.source();
  115. if (Buffer.isBuffer(bufferOrString)) {
  116. buffers.push(bufferOrString);
  117. } else {
  118. // This will not happen
  119. buffers.push(Buffer.from(bufferOrString, "utf8"));
  120. }
  121. }
  122. }
  123. return buffers;
  124. }
  125. /**
  126. * @returns {SourceValue} source
  127. */
  128. source() {
  129. if (!this._isOptimized) this._optimize();
  130. const children = /** @type {Source[]} */ (this._children);
  131. const childCount = children.length;
  132. let source = "";
  133. for (let ci = 0; ci < childCount; ci++) {
  134. source += children[ci].source();
  135. }
  136. return source;
  137. }
  138. /**
  139. * @returns {number} size
  140. */
  141. size() {
  142. if (!this._isOptimized) this._optimize();
  143. const children = /** @type {Source[]} */ (this._children);
  144. const childCount = children.length;
  145. let size = 0;
  146. for (let ci = 0; ci < childCount; ci++) {
  147. size += children[ci].size();
  148. }
  149. return size;
  150. }
  151. /**
  152. * @param {MapOptions=} options map options
  153. * @returns {RawSourceMap | null} map
  154. */
  155. map(options) {
  156. return getMap(this, options);
  157. }
  158. /**
  159. * @param {MapOptions=} options map options
  160. * @returns {SourceAndMap} source and map
  161. */
  162. sourceAndMap(options) {
  163. return getSourceAndMap(this, options);
  164. }
  165. /**
  166. * @param {Options} options options
  167. * @param {OnChunk} onChunk called for each chunk of code
  168. * @param {OnSource} onSource called for each source
  169. * @param {OnName} onName called for each name
  170. * @returns {GeneratedSourceInfo} generated source info
  171. */
  172. streamChunks(options, onChunk, onSource, onName) {
  173. if (!this._isOptimized) this._optimize();
  174. if (this._children.length === 1) {
  175. return /** @type {ConcatSource[]} */ (this._children)[0].streamChunks(
  176. options,
  177. onChunk,
  178. onSource,
  179. onName,
  180. );
  181. }
  182. let currentLineOffset = 0;
  183. let currentColumnOffset = 0;
  184. const sourceMapping = new Map();
  185. const nameMapping = new Map();
  186. const finalSource = Boolean(options && options.finalSource);
  187. let code = "";
  188. let needToCloseMapping = false;
  189. const children = /** @type {Source[]} */ (this._children);
  190. const childCount = children.length;
  191. for (let ci = 0; ci < childCount; ci++) {
  192. const item = children[ci];
  193. /** @type {number[]} */
  194. const sourceIndexMapping = [];
  195. /** @type {number[]} */
  196. const nameIndexMapping = [];
  197. let lastMappingLine = 0;
  198. const { generatedLine, generatedColumn, source } = streamChunks(
  199. item,
  200. options,
  201. // eslint-disable-next-line no-loop-func
  202. (
  203. chunk,
  204. generatedLine,
  205. generatedColumn,
  206. sourceIndex,
  207. originalLine,
  208. originalColumn,
  209. nameIndex,
  210. ) => {
  211. const line = generatedLine + currentLineOffset;
  212. const column =
  213. generatedLine === 1
  214. ? generatedColumn + currentColumnOffset
  215. : generatedColumn;
  216. if (needToCloseMapping) {
  217. if (generatedLine !== 1 || generatedColumn !== 0) {
  218. onChunk(
  219. undefined,
  220. currentLineOffset + 1,
  221. currentColumnOffset,
  222. -1,
  223. -1,
  224. -1,
  225. -1,
  226. );
  227. }
  228. needToCloseMapping = false;
  229. }
  230. const resultSourceIndex =
  231. sourceIndex < 0 || sourceIndex >= sourceIndexMapping.length
  232. ? -1
  233. : sourceIndexMapping[sourceIndex];
  234. let _chunk;
  235. // When using finalSource, we process the entire source code at once at the end, rather than chunk by chunk
  236. if (finalSource) {
  237. if (chunk !== undefined) code += chunk;
  238. } else {
  239. _chunk = chunk;
  240. }
  241. if (resultSourceIndex < 0) {
  242. lastMappingLine = 0;
  243. onChunk(_chunk, line, column, -1, -1, -1, -1);
  244. } else {
  245. // Only compute the remapped name index when the chunk
  246. // actually carries a source mapping; otherwise it is
  247. // unused.
  248. const resultNameIndex =
  249. nameIndex < 0 || nameIndex >= nameIndexMapping.length
  250. ? -1
  251. : nameIndexMapping[nameIndex];
  252. lastMappingLine = generatedLine;
  253. onChunk(
  254. _chunk,
  255. line,
  256. column,
  257. resultSourceIndex,
  258. originalLine,
  259. originalColumn,
  260. resultNameIndex,
  261. );
  262. }
  263. },
  264. (i, source, sourceContent) => {
  265. let globalIndex = sourceMapping.get(source);
  266. if (globalIndex === undefined) {
  267. sourceMapping.set(source, (globalIndex = sourceMapping.size));
  268. onSource(globalIndex, source, sourceContent);
  269. }
  270. sourceIndexMapping[i] = globalIndex;
  271. },
  272. (i, name) => {
  273. let globalIndex = nameMapping.get(name);
  274. if (globalIndex === undefined) {
  275. nameMapping.set(name, (globalIndex = nameMapping.size));
  276. onName(globalIndex, name);
  277. }
  278. nameIndexMapping[i] = globalIndex;
  279. },
  280. );
  281. if (source !== undefined) code += source;
  282. if (
  283. needToCloseMapping &&
  284. (generatedLine !== 1 || generatedColumn !== 0)
  285. ) {
  286. onChunk(
  287. undefined,
  288. currentLineOffset + 1,
  289. currentColumnOffset,
  290. -1,
  291. -1,
  292. -1,
  293. -1,
  294. );
  295. needToCloseMapping = false;
  296. }
  297. if (/** @type {number} */ (generatedLine) > 1) {
  298. currentColumnOffset = /** @type {number} */ (generatedColumn);
  299. } else {
  300. currentColumnOffset += /** @type {number} */ (generatedColumn);
  301. }
  302. needToCloseMapping =
  303. needToCloseMapping ||
  304. (finalSource && lastMappingLine === generatedLine);
  305. currentLineOffset += /** @type {number} */ (generatedLine) - 1;
  306. }
  307. return {
  308. generatedLine: currentLineOffset + 1,
  309. generatedColumn: currentColumnOffset,
  310. source: finalSource ? code : undefined,
  311. };
  312. }
  313. /**
  314. * Release cached data held by this source. clearCache is a memory
  315. * hint: it never affects correctness or output, only how expensive
  316. * the next read is. Subclasses override; the base is a no-op so
  317. * every Source supports the call. Composite sources always recurse
  318. * into wrapped sources. When the same child is reachable via several
  319. * parents (e.g. modules shared across webpack chunks), pass a shared
  320. * `visited` WeakSet so each subtree is walked at most once.
  321. * Not safe to call concurrently with source/map/sourceAndMap/
  322. * streamChunks/updateHash on the same instance.
  323. * @param {ClearCacheOptions=} options selectors
  324. * @param {WeakSet<Source>=} visited de-duplication set shared across calls
  325. * @returns {void}
  326. */
  327. clearCache(options, visited) {
  328. if (visited !== undefined && visited.has(this)) return;
  329. const children = this._children;
  330. const { length } = children;
  331. let v = visited;
  332. if (v === undefined && length > 0) v = new WeakSet();
  333. if (v !== undefined) v.add(this);
  334. for (let i = 0; i < length; i++) {
  335. const child = children[i];
  336. if (typeof child !== "string" && typeof child.clearCache === "function") {
  337. child.clearCache(options, v);
  338. }
  339. }
  340. }
  341. /**
  342. * @param {HashLike} hash hash
  343. * @returns {void}
  344. */
  345. updateHash(hash) {
  346. if (!this._isOptimized) this._optimize();
  347. const children = /** @type {Source[]} */ (this._children);
  348. const childCount = children.length;
  349. hash.update("ConcatSource");
  350. for (let ci = 0; ci < childCount; ci++) {
  351. children[ci].updateHash(hash);
  352. }
  353. }
  354. _optimize() {
  355. const newChildren = [];
  356. let currentString;
  357. /** @type {undefined | string | [string, string] | SourceLike} */
  358. let currentRawSources;
  359. /**
  360. * @param {string} string string
  361. * @returns {void}
  362. */
  363. const addStringToRawSources = (string) => {
  364. if (currentRawSources === undefined) {
  365. currentRawSources = string;
  366. } else if (Array.isArray(currentRawSources)) {
  367. currentRawSources.push(string);
  368. } else {
  369. currentRawSources = [
  370. typeof currentRawSources === "string"
  371. ? currentRawSources
  372. : /** @type {string} */ (currentRawSources.source()),
  373. string,
  374. ];
  375. }
  376. };
  377. /**
  378. * @param {SourceLike} source source
  379. * @returns {void}
  380. */
  381. const addSourceToRawSources = (source) => {
  382. if (currentRawSources === undefined) {
  383. currentRawSources = source;
  384. } else if (Array.isArray(currentRawSources)) {
  385. currentRawSources.push(
  386. /** @type {string} */
  387. (source.source()),
  388. );
  389. } else {
  390. currentRawSources = [
  391. typeof currentRawSources === "string"
  392. ? currentRawSources
  393. : /** @type {string} */ (currentRawSources.source()),
  394. /** @type {string} */
  395. (source.source()),
  396. ];
  397. }
  398. };
  399. const mergeRawSources = () => {
  400. if (Array.isArray(currentRawSources)) {
  401. const rawSource = new RawSource(currentRawSources.join(""));
  402. stringsAsRawSources.add(rawSource);
  403. newChildren.push(rawSource);
  404. } else if (typeof currentRawSources === "string") {
  405. const rawSource = new RawSource(currentRawSources);
  406. stringsAsRawSources.add(rawSource);
  407. newChildren.push(rawSource);
  408. } else {
  409. newChildren.push(currentRawSources);
  410. }
  411. };
  412. const children = this._children;
  413. for (let ci = 0, cl = children.length; ci < cl; ci++) {
  414. const child = children[ci];
  415. if (typeof child === "string") {
  416. if (currentString === undefined) {
  417. currentString = child;
  418. } else {
  419. currentString += child;
  420. }
  421. } else {
  422. if (currentString !== undefined) {
  423. addStringToRawSources(currentString);
  424. currentString = undefined;
  425. }
  426. if (stringsAsRawSources.has(child)) {
  427. addSourceToRawSources(
  428. /** @type {SourceLike} */
  429. (child),
  430. );
  431. } else {
  432. if (currentRawSources !== undefined) {
  433. mergeRawSources();
  434. currentRawSources = undefined;
  435. }
  436. newChildren.push(child);
  437. }
  438. }
  439. }
  440. if (currentString !== undefined) {
  441. addStringToRawSources(currentString);
  442. }
  443. if (currentRawSources !== undefined) {
  444. mergeRawSources();
  445. }
  446. this._children = newChildren;
  447. this._isOptimized = true;
  448. }
  449. }
  450. module.exports = ConcatSource;