util.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. "use strict";
  2. /**
  3. * Copyright (c) 2015-present, Waysact Pty Ltd
  4. *
  5. * This source code is licensed under the MIT license found in the
  6. * LICENSE file in the root directory of this source tree.
  7. */
  8. Object.defineProperty(exports, "__esModule", { value: true });
  9. exports.getChunkToManifestMap = exports.buildTopologicallySortedChunkGraph = exports.generateSriHashPlaceholders = exports.notNil = exports.findChunks = exports.makePlaceholder = exports.computeIntegrity = exports.placeholderPrefix = exports.normalizePath = exports.getTagSrc = exports.assert = exports.sriHashVariableReference = void 0;
  10. const crypto_1 = require("crypto");
  11. const path_1 = require("path");
  12. exports.sriHashVariableReference = "__webpack_require__.sriHashes";
  13. function assert(value, message) {
  14. if (!value) {
  15. throw new Error(message);
  16. }
  17. }
  18. exports.assert = assert;
  19. function getTagSrc(tag) {
  20. if (!["script", "link"].includes(tag.tagName) || !tag.attributes) {
  21. return undefined;
  22. }
  23. if (typeof tag.attributes.href === "string") {
  24. return tag.attributes.href;
  25. }
  26. if (typeof tag.attributes.src === "string") {
  27. return tag.attributes.src;
  28. }
  29. return undefined;
  30. }
  31. exports.getTagSrc = getTagSrc;
  32. const normalizePath = (p) => p.replace(/\?.*$/, "").split(path_1.sep).join("/");
  33. exports.normalizePath = normalizePath;
  34. exports.placeholderPrefix = "*-*-*-CHUNK-SRI-HASH-";
  35. const computeIntegrity = (hashFuncNames, source) => {
  36. const result = hashFuncNames
  37. .map((hashFuncName) => hashFuncName +
  38. "-" +
  39. crypto_1.createHash(hashFuncName)
  40. .update(typeof source === "string" ? Buffer.from(source, "utf-8") : source)
  41. .digest("base64"))
  42. .join(" ");
  43. return result;
  44. };
  45. exports.computeIntegrity = computeIntegrity;
  46. const makePlaceholder = (hashFuncNames, id) => {
  47. const placeholder = `${exports.placeholderPrefix}${id}`;
  48. const filler = exports.computeIntegrity(hashFuncNames, placeholder);
  49. return exports.placeholderPrefix + filler.substring(exports.placeholderPrefix.length);
  50. };
  51. exports.makePlaceholder = makePlaceholder;
  52. function findChunks(chunk) {
  53. const allChunks = new Set();
  54. const groupsVisited = new Set();
  55. function addIfNotExist(set, item) {
  56. if (set.has(item))
  57. return true;
  58. set.add(item);
  59. return false;
  60. }
  61. (function recurseChunk(childChunk) {
  62. function recurseGroup(group) {
  63. if (addIfNotExist(groupsVisited, group.id))
  64. return;
  65. group.chunks.forEach(recurseChunk);
  66. group.childrenIterable.forEach(recurseGroup);
  67. }
  68. if (addIfNotExist(allChunks, childChunk))
  69. return;
  70. Array.from(childChunk.groupsIterable).forEach(recurseGroup);
  71. })(chunk);
  72. return allChunks;
  73. }
  74. exports.findChunks = findChunks;
  75. function notNil(value) {
  76. return value !== null && value !== undefined;
  77. }
  78. exports.notNil = notNil;
  79. function generateSriHashPlaceholders(chunks, hashFuncNames) {
  80. return Array.from(chunks).reduce((sriHashes, depChunk) => {
  81. if (depChunk.id) {
  82. sriHashes[depChunk.id] = exports.makePlaceholder(hashFuncNames, depChunk.id);
  83. }
  84. return sriHashes;
  85. }, {});
  86. }
  87. exports.generateSriHashPlaceholders = generateSriHashPlaceholders;
  88. function* intersect(sets) {
  89. const { value: initialSet } = sets[Symbol.iterator]().next();
  90. if (!initialSet) {
  91. return;
  92. }
  93. initialSetLoop: for (const item of initialSet) {
  94. for (const set of sets) {
  95. if (!set.has(item)) {
  96. continue initialSetLoop;
  97. }
  98. }
  99. yield item;
  100. }
  101. }
  102. function* map(items, fn) {
  103. for (const item of items) {
  104. yield fn(item);
  105. }
  106. }
  107. function* flatMap(collections, fn) {
  108. for (const item of collections) {
  109. for (const result of fn(item)) {
  110. yield result;
  111. }
  112. }
  113. }
  114. /**
  115. * Tarjan's strongly connected components algorithm
  116. * https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
  117. */
  118. function createDAGfromGraph({ vertices, edges, }) {
  119. var _a;
  120. let index = 0;
  121. const stack = [];
  122. const vertexMetadata = new Map(map(vertices, (vertex) => [vertex, {}]));
  123. const stronglyConnectedComponents = new Set();
  124. function strongConnect(vertex) {
  125. var _a, _b;
  126. // Set the depth index for v to the smallest unused index
  127. const vertexData = vertexMetadata.get(vertex);
  128. assert(vertexData, "Vertex metadata missing");
  129. vertexData.index = index;
  130. vertexData.lowlink = index;
  131. index++;
  132. stack.push(vertex);
  133. vertexData.onstack = true;
  134. for (const child of (_a = edges.get(vertex)) !== null && _a !== void 0 ? _a : []) {
  135. const childData = vertexMetadata.get(child);
  136. assert(childData, "Child vertex metadata missing");
  137. if (childData.index === undefined) {
  138. // Child has not yet been visited; recurse on it
  139. strongConnect(child);
  140. vertexData.lowlink = Math.min(vertexData.lowlink, (_b = childData.lowlink) !== null && _b !== void 0 ? _b : Infinity);
  141. }
  142. else if (childData.onstack) {
  143. // Child is in stack and hence in the current SCC
  144. // If child is not on stack, then (vertex, child) is an edge pointing to an SCC already found and must be ignored
  145. // Note: The next line may look odd - but is correct.
  146. // It says childData.index not childData.lowlink; that is deliberate and from the original paper
  147. vertexData.lowlink = Math.min(vertexData.lowlink, childData.index);
  148. }
  149. }
  150. // If vertex is a root node, pop the stack and generate an SCC
  151. if (vertexData.index === vertexData.lowlink) {
  152. const newStronglyConnectedComponent = { nodes: new Set() };
  153. let currentNode;
  154. do {
  155. currentNode = stack.pop();
  156. assert(currentNode, "Working stack was empty");
  157. const metadata = vertexMetadata.get(currentNode);
  158. assert(metadata, "All nodes on stack should have metadata");
  159. metadata.onstack = false;
  160. newStronglyConnectedComponent.nodes.add(currentNode);
  161. } while (currentNode !== vertex);
  162. stronglyConnectedComponents.add(newStronglyConnectedComponent);
  163. }
  164. }
  165. for (const vertex of vertices) {
  166. const data = vertexMetadata.get(vertex);
  167. assert(data, "Vertex metadata not found");
  168. if (data.index === undefined) {
  169. strongConnect(vertex);
  170. }
  171. }
  172. // Now that all SCCs have been identified, rebuild the graph
  173. const vertexToSCCMap = new Map();
  174. const sccEdges = new Map();
  175. for (const scc of stronglyConnectedComponents) {
  176. for (const vertex of scc.nodes) {
  177. vertexToSCCMap.set(vertex, scc);
  178. }
  179. }
  180. for (const scc of stronglyConnectedComponents) {
  181. const childSCCNodes = new Set();
  182. for (const vertex of scc.nodes) {
  183. for (const childVertex of (_a = edges.get(vertex)) !== null && _a !== void 0 ? _a : []) {
  184. const childScc = vertexToSCCMap.get(childVertex);
  185. if (childScc && childScc !== scc) {
  186. childSCCNodes.add(childScc);
  187. }
  188. }
  189. }
  190. sccEdges.set(scc, childSCCNodes);
  191. }
  192. return { vertices: stronglyConnectedComponents, edges: sccEdges };
  193. }
  194. // This implementation assumes a directed acyclic graph (such as one produced by createDAGfromGraph),
  195. // and does not attempt to detect cycles
  196. function topologicalSort({ vertices, edges }) {
  197. const sortedItems = [];
  198. const seenNodes = new Set();
  199. function visit(node) {
  200. var _a;
  201. if (seenNodes.has(node)) {
  202. return;
  203. }
  204. seenNodes.add(node);
  205. for (const child of (_a = edges.get(node)) !== null && _a !== void 0 ? _a : []) {
  206. visit(child);
  207. }
  208. sortedItems.push(node);
  209. }
  210. for (const vertex of vertices) {
  211. visit(vertex);
  212. }
  213. return sortedItems;
  214. }
  215. function buildTopologicallySortedChunkGraph(chunks) {
  216. var _a;
  217. const vertices = new Set();
  218. const edges = new Map();
  219. // Chunks should have *all* chunks, not simply entry chunks
  220. for (const vertex of chunks) {
  221. if (vertices.has(vertex)) {
  222. continue;
  223. }
  224. vertices.add(vertex);
  225. edges.set(vertex, new Set());
  226. for (const vertexGroup of vertex.groupsIterable) {
  227. for (const childGroup of vertexGroup.childrenIterable) {
  228. for (const childChunk of childGroup.chunks) {
  229. (_a = edges.get(vertex)) === null || _a === void 0 ? void 0 : _a.add(childChunk);
  230. }
  231. }
  232. }
  233. }
  234. const dag = createDAGfromGraph({ vertices, edges });
  235. const sortedVertices = topologicalSort(dag);
  236. const chunkToSccMap = new Map(flatMap(dag.vertices, (scc) => map(scc.nodes, (chunk) => [chunk, scc])));
  237. return [sortedVertices, dag, chunkToSccMap];
  238. }
  239. exports.buildTopologicallySortedChunkGraph = buildTopologicallySortedChunkGraph;
  240. function getChunkToManifestMap(chunks) {
  241. var _a;
  242. const [sortedVertices, , chunkToSccMap] = buildTopologicallySortedChunkGraph(chunks);
  243. // This map tracks which hashes a chunk group has in its manifest and the intersection
  244. // of all its parents (and intersection of all their parents, etc.)
  245. // This is meant as a guarantee that the hash for a given chunk is handled by a chunk group
  246. // or its parents regardless of the tree traversal used from the roots
  247. const hashesByChunkGroupAndParents = new Map();
  248. // A map of what child chunks a given chunk should contain hashes for
  249. const chunkManifest = new Map();
  250. function intersectSets(setsToIntersect) {
  251. return new Set(intersect(setsToIntersect));
  252. }
  253. function findIntersectionOfParentSets(chunk) {
  254. var _a;
  255. const setsToIntersect = [];
  256. for (const group of chunk.groupsIterable) {
  257. for (const parent of group.parentsIterable) {
  258. setsToIntersect.push((_a = hashesByChunkGroupAndParents.get(parent)) !== null && _a !== void 0 ? _a : new Set());
  259. }
  260. }
  261. return intersectSets(setsToIntersect);
  262. }
  263. function getChildChunksToAddToChunkManifest(chunk) {
  264. var _a;
  265. const childChunks = new Set();
  266. const chunkSCC = chunkToSccMap.get(chunk);
  267. for (const chunkGroup of chunk.groupsIterable) {
  268. if (chunkGroup.chunks[chunkGroup.chunks.length - 1] !== chunk) {
  269. // Only add sri hashes for one chunk per chunk group,
  270. // where the last chunk in the group is the primary chunk
  271. continue;
  272. }
  273. for (const childGroup of chunkGroup.childrenIterable) {
  274. for (const childChunk of childGroup.chunks) {
  275. const childChunkSCC = chunkToSccMap.get(childChunk);
  276. if (childChunkSCC === chunkSCC) {
  277. // Don't include your own SCC.
  278. // Your parent will have the hashes for your SCC siblings
  279. continue;
  280. }
  281. for (const childChunkSccNode of (_a = childChunkSCC === null || childChunkSCC === void 0 ? void 0 : childChunkSCC.nodes) !== null && _a !== void 0 ? _a : []) {
  282. childChunks.add(childChunkSccNode);
  283. }
  284. }
  285. }
  286. }
  287. const parentManifest = findIntersectionOfParentSets(chunk);
  288. for (const manifestEntry of parentManifest) {
  289. childChunks.delete(manifestEntry);
  290. }
  291. return childChunks;
  292. }
  293. // We want to walk from the root nodes down to the leaves
  294. for (let i = sortedVertices.length - 1; i >= 0; i--) {
  295. const scc = sortedVertices[i];
  296. for (const chunk of scc.nodes) {
  297. const manifest = getChildChunksToAddToChunkManifest(chunk);
  298. const combinedParentManifest = findIntersectionOfParentSets(chunk);
  299. for (const chunk of manifest) {
  300. if (combinedParentManifest.has(chunk)) {
  301. manifest.delete(chunk);
  302. }
  303. else {
  304. combinedParentManifest.add(chunk);
  305. }
  306. }
  307. chunkManifest.set(chunk, manifest);
  308. for (const group of chunk.groupsIterable) {
  309. // Get intersection of all parent manifests
  310. const groupCombinedManifest = intersectSets(map(group.parentsIterable, (parent) => { var _a; return (_a = hashesByChunkGroupAndParents.get(parent)) !== null && _a !== void 0 ? _a : new Set(); }));
  311. // Add this chunk's manifest
  312. for (const chunk of manifest) {
  313. groupCombinedManifest.add(chunk);
  314. }
  315. // Add any existing manifests part of the group
  316. for (const chunk of (_a = hashesByChunkGroupAndParents.get(group)) !== null && _a !== void 0 ? _a : new Set()) {
  317. groupCombinedManifest.add(chunk);
  318. }
  319. hashesByChunkGroupAndParents.set(group, groupCombinedManifest);
  320. }
  321. }
  322. }
  323. return [sortedVertices, chunkManifest];
  324. }
  325. exports.getChunkToManifestMap = getChunkToManifestMap;
  326. //# sourceMappingURL=util.js.map