ConcatSource.js 12 KB

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