提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

609 lines
16 KiB

  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const util = require("util");
  7. const SortableSet = require("./util/SortableSet");
  8. const {
  9. compareChunks,
  10. compareIterables,
  11. compareLocations
  12. } = require("./util/comparators");
  13. /** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
  14. /** @typedef {import("./Chunk")} Chunk */
  15. /** @typedef {import("./ChunkGraph")} ChunkGraph */
  16. /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
  17. /** @typedef {import("./Entrypoint")} Entrypoint */
  18. /** @typedef {import("./Module")} Module */
  19. /** @typedef {import("./ModuleGraph")} ModuleGraph */
  20. /** @typedef {{ module: Module | null, loc: DependencyLocation, request: string }} OriginRecord */
  21. /**
  22. * @typedef {object} RawChunkGroupOptions
  23. * @property {number=} preloadOrder
  24. * @property {number=} prefetchOrder
  25. * @property {("low" | "high" | "auto")=} fetchPriority
  26. */
  27. /** @typedef {RawChunkGroupOptions & { name?: string | null }} ChunkGroupOptions */
  28. let debugId = 5000;
  29. /**
  30. * @template T
  31. * @param {SortableSet<T>} set set to convert to array.
  32. * @returns {T[]} the array format of existing set
  33. */
  34. const getArray = (set) => [...set];
  35. /**
  36. * A convenience method used to sort chunks based on their id's
  37. * @param {ChunkGroup} a first sorting comparator
  38. * @param {ChunkGroup} b second sorting comparator
  39. * @returns {1|0|-1} a sorting index to determine order
  40. */
  41. const sortById = (a, b) => {
  42. if (a.id < b.id) return -1;
  43. if (b.id < a.id) return 1;
  44. return 0;
  45. };
  46. /**
  47. * @param {OriginRecord} a the first comparator in sort
  48. * @param {OriginRecord} b the second comparator in sort
  49. * @returns {1|-1|0} returns sorting order as index
  50. */
  51. const sortOrigin = (a, b) => {
  52. const aIdent = a.module ? a.module.identifier() : "";
  53. const bIdent = b.module ? b.module.identifier() : "";
  54. if (aIdent < bIdent) return -1;
  55. if (aIdent > bIdent) return 1;
  56. return compareLocations(a.loc, b.loc);
  57. };
  58. class ChunkGroup {
  59. /**
  60. * Creates an instance of ChunkGroup.
  61. * @param {string | ChunkGroupOptions=} options chunk group options passed to chunkGroup
  62. */
  63. constructor(options) {
  64. if (typeof options === "string") {
  65. options = { name: options };
  66. } else if (!options) {
  67. options = { name: undefined };
  68. }
  69. /** @type {number} */
  70. this.groupDebugId = debugId++;
  71. this.options = /** @type {ChunkGroupOptions} */ (options);
  72. /** @type {SortableSet<ChunkGroup>} */
  73. this._children = new SortableSet(undefined, sortById);
  74. /** @type {SortableSet<ChunkGroup>} */
  75. this._parents = new SortableSet(undefined, sortById);
  76. /** @type {SortableSet<ChunkGroup>} */
  77. this._asyncEntrypoints = new SortableSet(undefined, sortById);
  78. this._blocks = new SortableSet();
  79. /** @type {Chunk[]} */
  80. this.chunks = [];
  81. /** @type {OriginRecord[]} */
  82. this.origins = [];
  83. /** Indices in top-down order */
  84. /**
  85. * @private
  86. * @type {Map<Module, number>}
  87. */
  88. this._modulePreOrderIndices = new Map();
  89. /** Indices in bottom-up order */
  90. /**
  91. * @private
  92. * @type {Map<Module, number>}
  93. */
  94. this._modulePostOrderIndices = new Map();
  95. /** @type {number | undefined} */
  96. this.index = undefined;
  97. }
  98. /**
  99. * when a new chunk is added to a chunkGroup, addingOptions will occur.
  100. * @param {ChunkGroupOptions} options the chunkGroup options passed to addOptions
  101. * @returns {void}
  102. */
  103. addOptions(options) {
  104. for (const key of /** @type {(keyof ChunkGroupOptions)[]} */ (
  105. Object.keys(options)
  106. )) {
  107. if (this.options[key] === undefined) {
  108. /** @type {ChunkGroupOptions[keyof ChunkGroupOptions]} */
  109. (this.options[key]) = options[key];
  110. } else if (this.options[key] !== options[key]) {
  111. if (key.endsWith("Order")) {
  112. const orderKey =
  113. /** @type {Exclude<keyof ChunkGroupOptions, "name" | "fetchPriority">} */
  114. (key);
  115. this.options[orderKey] = Math.max(
  116. /** @type {number} */
  117. (this.options[orderKey]),
  118. /** @type {number} */
  119. (options[orderKey])
  120. );
  121. } else {
  122. throw new Error(
  123. `ChunkGroup.addOptions: No option merge strategy for ${key}`
  124. );
  125. }
  126. }
  127. }
  128. }
  129. /**
  130. * returns the name of current ChunkGroup
  131. * @returns {ChunkGroupOptions["name"]} returns the ChunkGroup name
  132. */
  133. get name() {
  134. return this.options.name;
  135. }
  136. /**
  137. * sets a new name for current ChunkGroup
  138. * @param {string | undefined} value the new name for ChunkGroup
  139. * @returns {void}
  140. */
  141. set name(value) {
  142. this.options.name = value;
  143. }
  144. /* istanbul ignore next */
  145. /**
  146. * get a uniqueId for ChunkGroup, made up of its member Chunk debugId's
  147. * @returns {string} a unique concatenation of chunk debugId's
  148. */
  149. get debugId() {
  150. return Array.from(this.chunks, (x) => x.debugId).join("+");
  151. }
  152. /**
  153. * get a unique id for ChunkGroup, made up of its member Chunk id's
  154. * @returns {string} a unique concatenation of chunk ids
  155. */
  156. get id() {
  157. return Array.from(this.chunks, (x) => x.id).join("+");
  158. }
  159. /**
  160. * Performs an unshift of a specific chunk
  161. * @param {Chunk} chunk chunk being unshifted
  162. * @returns {boolean} returns true if attempted chunk shift is accepted
  163. */
  164. unshiftChunk(chunk) {
  165. const oldIdx = this.chunks.indexOf(chunk);
  166. if (oldIdx > 0) {
  167. this.chunks.splice(oldIdx, 1);
  168. this.chunks.unshift(chunk);
  169. } else if (oldIdx < 0) {
  170. this.chunks.unshift(chunk);
  171. return true;
  172. }
  173. return false;
  174. }
  175. /**
  176. * inserts a chunk before another existing chunk in group
  177. * @param {Chunk} chunk Chunk being inserted
  178. * @param {Chunk} before Placeholder/target chunk marking new chunk insertion point
  179. * @returns {boolean} return true if insertion was successful
  180. */
  181. insertChunk(chunk, before) {
  182. const oldIdx = this.chunks.indexOf(chunk);
  183. const idx = this.chunks.indexOf(before);
  184. if (idx < 0) {
  185. throw new Error("before chunk not found");
  186. }
  187. if (oldIdx >= 0 && oldIdx > idx) {
  188. this.chunks.splice(oldIdx, 1);
  189. this.chunks.splice(idx, 0, chunk);
  190. } else if (oldIdx < 0) {
  191. this.chunks.splice(idx, 0, chunk);
  192. return true;
  193. }
  194. return false;
  195. }
  196. /**
  197. * add a chunk into ChunkGroup. Is pushed on or prepended
  198. * @param {Chunk} chunk chunk being pushed into ChunkGroupS
  199. * @returns {boolean} returns true if chunk addition was successful.
  200. */
  201. pushChunk(chunk) {
  202. const oldIdx = this.chunks.indexOf(chunk);
  203. if (oldIdx >= 0) {
  204. return false;
  205. }
  206. this.chunks.push(chunk);
  207. return true;
  208. }
  209. /**
  210. * @param {Chunk} oldChunk chunk to be replaced
  211. * @param {Chunk} newChunk New chunk that will be replaced with
  212. * @returns {boolean | undefined} returns true if the replacement was successful
  213. */
  214. replaceChunk(oldChunk, newChunk) {
  215. const oldIdx = this.chunks.indexOf(oldChunk);
  216. if (oldIdx < 0) return false;
  217. const newIdx = this.chunks.indexOf(newChunk);
  218. if (newIdx < 0) {
  219. this.chunks[oldIdx] = newChunk;
  220. return true;
  221. }
  222. if (newIdx < oldIdx) {
  223. this.chunks.splice(oldIdx, 1);
  224. return true;
  225. } else if (newIdx !== oldIdx) {
  226. this.chunks[oldIdx] = newChunk;
  227. this.chunks.splice(newIdx, 1);
  228. return true;
  229. }
  230. }
  231. /**
  232. * @param {Chunk} chunk chunk to remove
  233. * @returns {boolean} returns true if chunk was removed
  234. */
  235. removeChunk(chunk) {
  236. const idx = this.chunks.indexOf(chunk);
  237. if (idx >= 0) {
  238. this.chunks.splice(idx, 1);
  239. return true;
  240. }
  241. return false;
  242. }
  243. /**
  244. * @returns {boolean} true, when this chunk group will be loaded on initial page load
  245. */
  246. isInitial() {
  247. return false;
  248. }
  249. /**
  250. * @param {ChunkGroup} group chunk group to add
  251. * @returns {boolean} returns true if chunk group was added
  252. */
  253. addChild(group) {
  254. const size = this._children.size;
  255. this._children.add(group);
  256. return size !== this._children.size;
  257. }
  258. /**
  259. * @returns {ChunkGroup[]} returns the children of this group
  260. */
  261. getChildren() {
  262. return this._children.getFromCache(getArray);
  263. }
  264. getNumberOfChildren() {
  265. return this._children.size;
  266. }
  267. get childrenIterable() {
  268. return this._children;
  269. }
  270. /**
  271. * @param {ChunkGroup} group the chunk group to remove
  272. * @returns {boolean} returns true if the chunk group was removed
  273. */
  274. removeChild(group) {
  275. if (!this._children.has(group)) {
  276. return false;
  277. }
  278. this._children.delete(group);
  279. group.removeParent(this);
  280. return true;
  281. }
  282. /**
  283. * @param {ChunkGroup} parentChunk the parent group to be added into
  284. * @returns {boolean} returns true if this chunk group was added to the parent group
  285. */
  286. addParent(parentChunk) {
  287. if (!this._parents.has(parentChunk)) {
  288. this._parents.add(parentChunk);
  289. return true;
  290. }
  291. return false;
  292. }
  293. /**
  294. * @returns {ChunkGroup[]} returns the parents of this group
  295. */
  296. getParents() {
  297. return this._parents.getFromCache(getArray);
  298. }
  299. getNumberOfParents() {
  300. return this._parents.size;
  301. }
  302. /**
  303. * @param {ChunkGroup} parent the parent group
  304. * @returns {boolean} returns true if the parent group contains this group
  305. */
  306. hasParent(parent) {
  307. return this._parents.has(parent);
  308. }
  309. get parentsIterable() {
  310. return this._parents;
  311. }
  312. /**
  313. * @param {ChunkGroup} chunkGroup the parent group
  314. * @returns {boolean} returns true if this group has been removed from the parent
  315. */
  316. removeParent(chunkGroup) {
  317. if (this._parents.delete(chunkGroup)) {
  318. chunkGroup.removeChild(this);
  319. return true;
  320. }
  321. return false;
  322. }
  323. /**
  324. * @param {Entrypoint} entrypoint entrypoint to add
  325. * @returns {boolean} returns true if entrypoint was added
  326. */
  327. addAsyncEntrypoint(entrypoint) {
  328. const size = this._asyncEntrypoints.size;
  329. this._asyncEntrypoints.add(entrypoint);
  330. return size !== this._asyncEntrypoints.size;
  331. }
  332. get asyncEntrypointsIterable() {
  333. return this._asyncEntrypoints;
  334. }
  335. /**
  336. * @returns {AsyncDependenciesBlock[]} an array containing the blocks
  337. */
  338. getBlocks() {
  339. return this._blocks.getFromCache(getArray);
  340. }
  341. getNumberOfBlocks() {
  342. return this._blocks.size;
  343. }
  344. /**
  345. * @param {AsyncDependenciesBlock} block block
  346. * @returns {boolean} true, if block exists
  347. */
  348. hasBlock(block) {
  349. return this._blocks.has(block);
  350. }
  351. /**
  352. * @returns {Iterable<AsyncDependenciesBlock>} blocks
  353. */
  354. get blocksIterable() {
  355. return this._blocks;
  356. }
  357. /**
  358. * @param {AsyncDependenciesBlock} block a block
  359. * @returns {boolean} false, if block was already added
  360. */
  361. addBlock(block) {
  362. if (!this._blocks.has(block)) {
  363. this._blocks.add(block);
  364. return true;
  365. }
  366. return false;
  367. }
  368. /**
  369. * @param {Module | null} module origin module
  370. * @param {DependencyLocation} loc location of the reference in the origin module
  371. * @param {string} request request name of the reference
  372. * @returns {void}
  373. */
  374. addOrigin(module, loc, request) {
  375. this.origins.push({
  376. module,
  377. loc,
  378. request
  379. });
  380. }
  381. /**
  382. * @returns {string[]} the files contained this chunk group
  383. */
  384. getFiles() {
  385. const files = new Set();
  386. for (const chunk of this.chunks) {
  387. for (const file of chunk.files) {
  388. files.add(file);
  389. }
  390. }
  391. return [...files];
  392. }
  393. /**
  394. * @returns {void}
  395. */
  396. remove() {
  397. // cleanup parents
  398. for (const parentChunkGroup of this._parents) {
  399. // remove this chunk from its parents
  400. parentChunkGroup._children.delete(this);
  401. // cleanup "sub chunks"
  402. for (const chunkGroup of this._children) {
  403. /**
  404. * remove this chunk as "intermediary" and connect
  405. * it "sub chunks" and parents directly
  406. */
  407. // add parent to each "sub chunk"
  408. chunkGroup.addParent(parentChunkGroup);
  409. // add "sub chunk" to parent
  410. parentChunkGroup.addChild(chunkGroup);
  411. }
  412. }
  413. /**
  414. * we need to iterate again over the children
  415. * to remove this from the child's parents.
  416. * This can not be done in the above loop
  417. * as it is not guaranteed that `this._parents` contains anything.
  418. */
  419. for (const chunkGroup of this._children) {
  420. // remove this as parent of every "sub chunk"
  421. chunkGroup._parents.delete(this);
  422. }
  423. // remove chunks
  424. for (const chunk of this.chunks) {
  425. chunk.removeGroup(this);
  426. }
  427. }
  428. sortItems() {
  429. this.origins.sort(sortOrigin);
  430. }
  431. /**
  432. * Sorting predicate which allows current ChunkGroup to be compared against another.
  433. * Sorting values are based off of number of chunks in ChunkGroup.
  434. * @param {ChunkGraph} chunkGraph the chunk graph
  435. * @param {ChunkGroup} otherGroup the chunkGroup to compare this against
  436. * @returns {-1|0|1} sort position for comparison
  437. */
  438. compareTo(chunkGraph, otherGroup) {
  439. if (this.chunks.length > otherGroup.chunks.length) return -1;
  440. if (this.chunks.length < otherGroup.chunks.length) return 1;
  441. return compareIterables(compareChunks(chunkGraph))(
  442. this.chunks,
  443. otherGroup.chunks
  444. );
  445. }
  446. /**
  447. * @param {ModuleGraph} moduleGraph the module graph
  448. * @param {ChunkGraph} chunkGraph the chunk graph
  449. * @returns {Record<string, ChunkGroup[]>} mapping from children type to ordered list of ChunkGroups
  450. */
  451. getChildrenByOrders(moduleGraph, chunkGraph) {
  452. /** @type {Map<string, {order: number, group: ChunkGroup}[]>} */
  453. const lists = new Map();
  454. for (const childGroup of this._children) {
  455. for (const key of Object.keys(childGroup.options)) {
  456. if (key.endsWith("Order")) {
  457. const name = key.slice(0, key.length - "Order".length);
  458. let list = lists.get(name);
  459. if (list === undefined) {
  460. lists.set(name, (list = []));
  461. }
  462. list.push({
  463. order:
  464. /** @type {number} */
  465. (
  466. childGroup.options[/** @type {keyof ChunkGroupOptions} */ (key)]
  467. ),
  468. group: childGroup
  469. });
  470. }
  471. }
  472. }
  473. /** @type {Record<string, ChunkGroup[]>} */
  474. const result = Object.create(null);
  475. for (const [name, list] of lists) {
  476. list.sort((a, b) => {
  477. const cmp = b.order - a.order;
  478. if (cmp !== 0) return cmp;
  479. return a.group.compareTo(chunkGraph, b.group);
  480. });
  481. result[name] = list.map((i) => i.group);
  482. }
  483. return result;
  484. }
  485. /**
  486. * Sets the top-down index of a module in this ChunkGroup
  487. * @param {Module} module module for which the index should be set
  488. * @param {number} index the index of the module
  489. * @returns {void}
  490. */
  491. setModulePreOrderIndex(module, index) {
  492. this._modulePreOrderIndices.set(module, index);
  493. }
  494. /**
  495. * Gets the top-down index of a module in this ChunkGroup
  496. * @param {Module} module the module
  497. * @returns {number | undefined} index
  498. */
  499. getModulePreOrderIndex(module) {
  500. return this._modulePreOrderIndices.get(module);
  501. }
  502. /**
  503. * Sets the bottom-up index of a module in this ChunkGroup
  504. * @param {Module} module module for which the index should be set
  505. * @param {number} index the index of the module
  506. * @returns {void}
  507. */
  508. setModulePostOrderIndex(module, index) {
  509. this._modulePostOrderIndices.set(module, index);
  510. }
  511. /**
  512. * Gets the bottom-up index of a module in this ChunkGroup
  513. * @param {Module} module the module
  514. * @returns {number | undefined} index
  515. */
  516. getModulePostOrderIndex(module) {
  517. return this._modulePostOrderIndices.get(module);
  518. }
  519. /* istanbul ignore next */
  520. checkConstraints() {
  521. const chunk = this;
  522. for (const child of chunk._children) {
  523. if (!child._parents.has(chunk)) {
  524. throw new Error(
  525. `checkConstraints: child missing parent ${chunk.debugId} -> ${child.debugId}`
  526. );
  527. }
  528. }
  529. for (const parentChunk of chunk._parents) {
  530. if (!parentChunk._children.has(chunk)) {
  531. throw new Error(
  532. `checkConstraints: parent missing child ${parentChunk.debugId} <- ${chunk.debugId}`
  533. );
  534. }
  535. }
  536. }
  537. }
  538. ChunkGroup.prototype.getModuleIndex = util.deprecate(
  539. ChunkGroup.prototype.getModulePreOrderIndex,
  540. "ChunkGroup.getModuleIndex was renamed to getModulePreOrderIndex",
  541. "DEP_WEBPACK_CHUNK_GROUP_GET_MODULE_INDEX"
  542. );
  543. ChunkGroup.prototype.getModuleIndex2 = util.deprecate(
  544. ChunkGroup.prototype.getModulePostOrderIndex,
  545. "ChunkGroup.getModuleIndex2 was renamed to getModulePostOrderIndex",
  546. "DEP_WEBPACK_CHUNK_GROUP_GET_MODULE_INDEX_2"
  547. );
  548. module.exports = ChunkGroup;