@atlaspack/inspector 0.0.19-canary.3998

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +126 -0
  3. package/bin/atlaspack-inspector.js +2 -0
  4. package/lib/backend/cli.js +4 -0
  5. package/lib/backend/config/logger.js +16 -0
  6. package/lib/backend/config/middleware/cacheDataMiddleware.js +60 -0
  7. package/lib/backend/config/middleware/errorHandlingMiddleware.js +17 -0
  8. package/lib/backend/config/middleware/errorHandlingMiddleware.test.js +44 -0
  9. package/lib/backend/config/middleware/loggingMiddleware.js +22 -0
  10. package/lib/backend/controllers/BundleGraphController.js +42 -0
  11. package/lib/backend/controllers/CacheDataController.js +143 -0
  12. package/lib/backend/controllers/FrontendAssetsController.js +61 -0
  13. package/lib/backend/controllers/TreeMapController.js +108 -0
  14. package/lib/backend/controllers/mcp/InspectorMCP.js +231 -0
  15. package/lib/backend/controllers/mcp/InspectorMCPController.js +16 -0
  16. package/lib/backend/errors/HTTPError.js +16 -0
  17. package/lib/backend/errors/HTTPError.test.js +21 -0
  18. package/lib/backend/index.js +177 -0
  19. package/lib/backend/services/AnalyticsService.js +86 -0
  20. package/lib/backend/services/LazyValue.js +33 -0
  21. package/lib/backend/services/LazyValue.test.js +12 -0
  22. package/lib/backend/services/buildJsonGraph.js +86 -0
  23. package/lib/backend/services/buildTreemap.js +140 -0
  24. package/lib/backend/services/buildTreemapBundle.test.js +298 -0
  25. package/lib/backend/services/findSourceCodeUrl.js +107 -0
  26. package/lib/backend/services/findSourceCodeUrl.test.js +216 -0
  27. package/lib/backend/services/getCacheStats.js +36 -0
  28. package/lib/backend/services/getCacheStats.test.js +162 -0
  29. package/lib/backend/services/getDisplayName.js +18 -0
  30. package/lib/backend/services/getDisplayName.test.js +71 -0
  31. package/lib/backend/services/loadCacheData.js +209 -0
  32. package/lib/backend/services/loadCacheData.test.js +79 -0
  33. package/lib/backend/testing/TemporaryDirectory.js +46 -0
  34. package/lib/frontend/node_modules/@atlaspack/packager-js/lib/dev-prelude.js +145 -0
  35. package/lib/frontend/node_modules/@atlaspack/packager-js/src/dev-prelude.js +145 -0
  36. package/package.json +75 -0
  37. package/screenshots/bottom-up.png +0 -0
  38. package/screenshots/cache-inspector.png +0 -0
  39. package/screenshots/treemap.png +0 -0
  40. package/src/backend/README.md +14 -0
  41. package/src/backend/cli.ts +3 -0
  42. package/src/backend/config/logger.ts +14 -0
  43. package/src/backend/config/middleware/cacheDataMiddleware.ts +94 -0
  44. package/src/backend/config/middleware/errorHandlingMiddleware.test.ts +52 -0
  45. package/src/backend/config/middleware/errorHandlingMiddleware.ts +20 -0
  46. package/src/backend/config/middleware/loggingMiddleware.ts +24 -0
  47. package/src/backend/controllers/BundleGraphController.ts +73 -0
  48. package/src/backend/controllers/CacheDataController.ts +187 -0
  49. package/src/backend/controllers/FrontendAssetsController.ts +28 -0
  50. package/src/backend/controllers/TreeMapController.ts +143 -0
  51. package/src/backend/controllers/mcp/InspectorMCP.ts +311 -0
  52. package/src/backend/controllers/mcp/InspectorMCPController.ts +17 -0
  53. package/src/backend/errors/HTTPError.test.ts +19 -0
  54. package/src/backend/errors/HTTPError.ts +14 -0
  55. package/src/backend/globals.d.ts +9 -0
  56. package/src/backend/index.ts +271 -0
  57. package/src/backend/services/AnalyticsService.ts +118 -0
  58. package/src/backend/services/LazyValue.test.ts +13 -0
  59. package/src/backend/services/LazyValue.ts +29 -0
  60. package/src/backend/services/buildJsonGraph.ts +124 -0
  61. package/src/backend/services/buildTreemap.ts +273 -0
  62. package/src/backend/services/buildTreemapBundle.test.ts +348 -0
  63. package/src/backend/services/findSourceCodeUrl.test.ts +228 -0
  64. package/src/backend/services/findSourceCodeUrl.ts +146 -0
  65. package/src/backend/services/getCacheStats.test.ts +169 -0
  66. package/src/backend/services/getCacheStats.ts +46 -0
  67. package/src/backend/services/getDisplayName.test.ts +84 -0
  68. package/src/backend/services/getDisplayName.ts +20 -0
  69. package/src/backend/services/loadCacheData.test.ts +101 -0
  70. package/src/backend/services/loadCacheData.ts +294 -0
  71. package/src/backend/testing/TemporaryDirectory.ts +50 -0
  72. package/src/frontend/.atlaspackrc +4 -0
  73. package/src/frontend/.eslintrc.json +19 -0
  74. package/src/frontend/dist/atlassian-dark-brand-refresh.91b786da.js +2 -0
  75. package/src/frontend/dist/atlassian-dark-brand-refresh.91b786da.js.map +1 -0
  76. package/src/frontend/dist/atlassian-dark-future.59ebadca.js +2 -0
  77. package/src/frontend/dist/atlassian-dark-future.59ebadca.js.map +1 -0
  78. package/src/frontend/dist/atlassian-dark-increased-contrast.ff6775f2.js +2 -0
  79. package/src/frontend/dist/atlassian-dark-increased-contrast.ff6775f2.js.map +1 -0
  80. package/src/frontend/dist/atlassian-dark.ad679134.js +2 -0
  81. package/src/frontend/dist/atlassian-dark.ad679134.js.map +1 -0
  82. package/src/frontend/dist/atlassian-legacy-dark.8aa27f7f.js +2 -0
  83. package/src/frontend/dist/atlassian-legacy-dark.8aa27f7f.js.map +1 -0
  84. package/src/frontend/dist/atlassian-legacy-light.2eb372ce.js +2 -0
  85. package/src/frontend/dist/atlassian-legacy-light.2eb372ce.js.map +1 -0
  86. package/src/frontend/dist/atlassian-light-brand-refresh.fadcab0a.js +2 -0
  87. package/src/frontend/dist/atlassian-light-brand-refresh.fadcab0a.js.map +1 -0
  88. package/src/frontend/dist/atlassian-light-future.612afe8a.js +2 -0
  89. package/src/frontend/dist/atlassian-light-future.612afe8a.js.map +1 -0
  90. package/src/frontend/dist/atlassian-light-increased-contrast.7161cd79.js +2 -0
  91. package/src/frontend/dist/atlassian-light-increased-contrast.7161cd79.js.map +1 -0
  92. package/src/frontend/dist/atlassian-light.bc343d4c.js +2 -0
  93. package/src/frontend/dist/atlassian-light.bc343d4c.js.map +1 -0
  94. package/src/frontend/dist/atlassian-shape.b92d69c0.js +2 -0
  95. package/src/frontend/dist/atlassian-shape.b92d69c0.js.map +1 -0
  96. package/src/frontend/dist/atlassian-spacing.60ddd8e7.js +2 -0
  97. package/src/frontend/dist/atlassian-spacing.60ddd8e7.js.map +1 -0
  98. package/src/frontend/dist/atlassian-typography-adg3.f88947f6.js +2 -0
  99. package/src/frontend/dist/atlassian-typography-adg3.f88947f6.js.map +1 -0
  100. package/src/frontend/dist/atlassian-typography-modernized.42016c51.js +2 -0
  101. package/src/frontend/dist/atlassian-typography-modernized.42016c51.js.map +1 -0
  102. package/src/frontend/dist/atlassian-typography-refreshed.ec0d111b.js +2 -0
  103. package/src/frontend/dist/atlassian-typography-refreshed.ec0d111b.js.map +1 -0
  104. package/src/frontend/dist/atlassian-typography.66d7e8f4.js +2 -0
  105. package/src/frontend/dist/atlassian-typography.66d7e8f4.js.map +1 -0
  106. package/src/frontend/dist/badge-light.7e55986a.png +0 -0
  107. package/src/frontend/dist/custom-theme.4680282a.js +2 -0
  108. package/src/frontend/dist/custom-theme.4680282a.js.map +1 -0
  109. package/src/frontend/dist/drag-handle.136830d3.js +2 -0
  110. package/src/frontend/dist/drag-handle.136830d3.js.map +1 -0
  111. package/src/frontend/dist/drag-handle.63bdb345.css +2 -0
  112. package/src/frontend/dist/drag-handle.63bdb345.css.map +1 -0
  113. package/src/frontend/dist/index.a41fafce.css +2 -0
  114. package/src/frontend/dist/index.a41fafce.css.map +1 -0
  115. package/src/frontend/dist/index.a4ce2b12.js +28 -0
  116. package/src/frontend/dist/index.a4ce2b12.js.map +1 -0
  117. package/src/frontend/dist/index.html +1 -0
  118. package/src/frontend/dist/index.runtime.a729d997.js +2 -0
  119. package/src/frontend/dist/index.runtime.a729d997.js.map +1 -0
  120. package/src/frontend/dist/refractor.2c1fd9a1.js +2 -0
  121. package/src/frontend/dist/refractor.2c1fd9a1.js.map +1 -0
  122. package/src/frontend/index.html +11 -0
  123. package/src/frontend/jest.config.js +16 -0
  124. package/src/frontend/package.json +64 -0
  125. package/src/frontend/src/APIError.test.ts +72 -0
  126. package/src/frontend/src/APIError.tsx +29 -0
  127. package/src/frontend/src/AppRoutes.tsx +56 -0
  128. package/src/frontend/src/hack-feature-flags.ts +6 -0
  129. package/src/frontend/src/main.tsx +50 -0
  130. package/src/frontend/src/test/stubCssModule.js +1 -0
  131. package/src/frontend/src/ui/App.module.css +122 -0
  132. package/src/frontend/src/ui/App.module.css.d.ts +8 -0
  133. package/src/frontend/src/ui/AppLayout/AppLayout.tsx +26 -0
  134. package/src/frontend/src/ui/AppLayout/SidebarNavigation/LinkItem.tsx +26 -0
  135. package/src/frontend/src/ui/AppLayout/SidebarNavigation/SidebarNavigation.tsx +45 -0
  136. package/src/frontend/src/ui/AppLayout/TopNavigation/Logo.module.css +12 -0
  137. package/src/frontend/src/ui/AppLayout/TopNavigation/Logo.module.css.d.ts +4 -0
  138. package/src/frontend/src/ui/AppLayout/TopNavigation/Logo.tsx +11 -0
  139. package/src/frontend/src/ui/AppLayout/TopNavigation/TopNavigation.module.css +14 -0
  140. package/src/frontend/src/ui/AppLayout/TopNavigation/TopNavigation.module.css.d.ts +3 -0
  141. package/src/frontend/src/ui/AppLayout/TopNavigation/TopNavigation.tsx +45 -0
  142. package/src/frontend/src/ui/AppLayout/TopNavigation/badge-light.png +0 -0
  143. package/src/frontend/src/ui/AppLayout/TopNavigation/logo-light.png +0 -0
  144. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css +9 -0
  145. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css.d.ts +3 -0
  146. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.test.tsx +15 -0
  147. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.tsx +14 -0
  148. package/src/frontend/src/ui/app/StatsPage.tsx +77 -0
  149. package/src/frontend/src/ui/app/cache/CacheKeysIndexPage.tsx +13 -0
  150. package/src/frontend/src/ui/app/cache/CacheKeysPage.module.css +11 -0
  151. package/src/frontend/src/ui/app/cache/CacheKeysPage.module.css.d.ts +4 -0
  152. package/src/frontend/src/ui/app/cache/CacheKeysPage.tsx +23 -0
  153. package/src/frontend/src/ui/app/cache/[key]/CacheValuePage.tsx +40 -0
  154. package/src/frontend/src/ui/app/cache/ui/CacheKeyList.module.css +40 -0
  155. package/src/frontend/src/ui/app/cache/ui/CacheKeyList.module.css.d.ts +7 -0
  156. package/src/frontend/src/ui/app/cache/ui/CacheKeyList.tsx +187 -0
  157. package/src/frontend/src/ui/app/cache-invalidation/CacheInvalidationPage.tsx +22 -0
  158. package/src/frontend/src/ui/app/cache-invalidation/[fileId]/CacheInvalidationFilePage.tsx +22 -0
  159. package/src/frontend/src/ui/app/cache-invalidation/ui/CacheFileList.module.css +40 -0
  160. package/src/frontend/src/ui/app/cache-invalidation/ui/CacheFileList.module.css.d.ts +7 -0
  161. package/src/frontend/src/ui/app/cache-invalidation/ui/CacheFileList.tsx +185 -0
  162. package/src/frontend/src/ui/app/treemap/BottomPanelResizeState.test.ts +25 -0
  163. package/src/frontend/src/ui/app/treemap/BottomPanelResizeState.tsx +48 -0
  164. package/src/frontend/src/ui/app/treemap/FoamTreemapPage.module.css +24 -0
  165. package/src/frontend/src/ui/app/treemap/FoamTreemapPage.module.css.d.ts +6 -0
  166. package/src/frontend/src/ui/app/treemap/FoamTreemapPage.tsx +47 -0
  167. package/src/frontend/src/ui/app/treemap/controllers/RelatedBundlesController.tsx +41 -0
  168. package/src/frontend/src/ui/app/treemap/controllers/UrlFocusController.tsx +33 -0
  169. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css +24 -0
  170. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css.d.ts +5 -0
  171. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/BottomPanel.tsx +24 -0
  172. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css +13 -0
  173. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css.d.ts +5 -0
  174. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.tsx +53 -0
  175. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/AssetTable.tsx +135 -0
  176. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css +7 -0
  177. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css.d.ts +3 -0
  178. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.tsx +123 -0
  179. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableModel.tsx +18 -0
  180. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css +20 -0
  181. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css.d.ts +6 -0
  182. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.tsx +79 -0
  183. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.test.ts +19 -0
  184. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.ts +24 -0
  185. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css +20 -0
  186. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css.d.ts +5 -0
  187. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.tsx +42 -0
  188. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css +29 -0
  189. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css.d.ts +6 -0
  190. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.tsx +107 -0
  191. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css +7 -0
  192. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css.d.ts +3 -0
  193. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.tsx +20 -0
  194. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/SourceCodeURL.tsx +5 -0
  195. package/src/frontend/src/ui/app/treemap/ui/BundleGraphRenderer.module.css +13 -0
  196. package/src/frontend/src/ui/app/treemap/ui/BundleGraphRenderer.module.css.d.ts +4 -0
  197. package/src/frontend/src/ui/app/treemap/ui/BundleGraphRenderer.tsx +95 -0
  198. package/src/frontend/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css +6 -0
  199. package/src/frontend/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css.d.ts +3 -0
  200. package/src/frontend/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.tsx +49 -0
  201. package/src/frontend/src/ui/app/treemap/ui/SigmaGraph.module.css +5 -0
  202. package/src/frontend/src/ui/app/treemap/ui/SigmaGraph.module.css.d.ts +3 -0
  203. package/src/frontend/src/ui/app/treemap/ui/SigmaGraph.tsx +80 -0
  204. package/src/frontend/src/ui/app/treemap/ui/Treemap.tsx +14 -0
  205. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css +32 -0
  206. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css.d.ts +5 -0
  207. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.tsx +24 -0
  208. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css +14 -0
  209. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css.d.ts +4 -0
  210. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.tsx +271 -0
  211. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css +15 -0
  212. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css.d.ts +4 -0
  213. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.tsx +111 -0
  214. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.test.ts +27 -0
  215. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.ts +21 -0
  216. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/useMouseMoveController.ts +20 -0
  217. package/src/frontend/src/ui/globals.css +26 -0
  218. package/src/frontend/src/ui/globals.css.d.ts +1 -0
  219. package/src/frontend/src/ui/globals.d.ts +9 -0
  220. package/src/frontend/src/ui/model/ViewModel.test.ts +31 -0
  221. package/src/frontend/src/ui/model/ViewModel.ts +62 -0
  222. package/src/frontend/src/ui/not-found/NotFoundPage.module.css +7 -0
  223. package/src/frontend/src/ui/not-found/NotFoundPage.module.css.d.ts +3 -0
  224. package/src/frontend/src/ui/not-found/NotFoundPage.tsx +9 -0
  225. package/src/frontend/src/ui/types/Graph.tsx +12 -0
  226. package/src/frontend/src/ui/util/ErrorBoundary.module.css +3 -0
  227. package/src/frontend/src/ui/util/ErrorBoundary.module.css.d.ts +3 -0
  228. package/src/frontend/src/ui/util/ErrorBoundary.test.tsx +65 -0
  229. package/src/frontend/src/ui/util/ErrorBoundary.tsx +75 -0
  230. package/src/frontend/src/ui/util/colorPalette.tsx +122 -0
  231. package/src/frontend/src/ui/util/formatBytes.test.ts +13 -0
  232. package/src/frontend/src/ui/util/formatBytes.tsx +9 -0
  233. package/src/frontend/src/ui/util/getRandomDarkerColor.tsx +31 -0
  234. package/src/frontend/tsconfig.json +12 -0
  235. package/src/frontend/yarn.lock +0 -0
@@ -0,0 +1,273 @@
1
+ /* eslint-disable monorepo/no-internal-import */
2
+ // @ts-expect-error TS2749
3
+ import type BundleGraph from '@atlaspack/core/lib/BundleGraph.js';
4
+ // @ts-expect-error TS2749
5
+ import {requestTypes} from '@atlaspack/core/lib/RequestTracker.js';
6
+ // @ts-expect-error TS2749
7
+ import type RequestTracker from '@atlaspack/core/lib/RequestTracker.js';
8
+ // @ts-expect-error TS2749
9
+ import type {RequestGraphNode} from '@atlaspack/core/lib/RequestTracker.js';
10
+ // @ts-expect-error TS2749
11
+ import type {Node} from '@atlaspack/core/lib/types.js';
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import {logger} from '../config/logger';
15
+
16
+ /**
17
+ * An asset tree node.
18
+ *
19
+ * This is the file-tree structure of the bundle. Each file and directory is a node in the
20
+ * tree.
21
+ *
22
+ * Directories' sizes are the sum of their children's sizes.
23
+ *
24
+ * Sizes are read from disk, not from the bundle itself.
25
+ */
26
+ export type AssetTreeNode = {
27
+ /**
28
+ * ID of this file.
29
+ */
30
+ id: string;
31
+ /**
32
+ * Children of this node (empty is this is a file).
33
+ *
34
+ * This is the next path component in the file-path leading to each child.
35
+ */
36
+ children: Record<string, AssetTreeNode>;
37
+ /**
38
+ * Size of this node in bytes.
39
+ */
40
+ size: number;
41
+ /**
42
+ * The full file-path of this node.
43
+ */
44
+ path: string;
45
+ };
46
+
47
+ /**
48
+ * A bundle in the tree-map.
49
+ */
50
+ export interface TreemapBundle {
51
+ /**
52
+ * The bundle `id` for tree-map purposes.
53
+ */
54
+ id: string;
55
+ /**
56
+ * A display name for the UI
57
+ */
58
+ displayName: string;
59
+ /**
60
+ * Raw bundle node from the bundle graph.
61
+ */
62
+ bundle: any;
63
+ /**
64
+ * Size of the bundle in bytes, as read from disk.
65
+ */
66
+ size: number;
67
+ /**
68
+ * The file path of the bundle on disk.
69
+ */
70
+ filePath: string;
71
+ /**
72
+ * The file-tree for the bundle.
73
+ */
74
+ assetTree: AssetTreeNode;
75
+ }
76
+
77
+ /**
78
+ * The tree-map model.
79
+ */
80
+ export interface Treemap {
81
+ /**
82
+ * Each bundle in the application build.
83
+ */
84
+ bundles: Array<TreemapBundle>;
85
+ /**
86
+ * Total size of all bundles in bytes.
87
+ */
88
+ totalSize: number;
89
+ }
90
+
91
+ /**
92
+ * In order to find the sizes of bundles, we look-up write bundle requests for each bundle
93
+ * in this build, then read the sizes of each file from disk.
94
+ */
95
+ export function getWriteBundleRequestsByBundleId(
96
+ requestTracker: RequestTracker,
97
+ ): Map<string, RequestGraphNode> {
98
+ const writeBundleRequests = requestTracker.graph.nodes.filter(
99
+ (requestNode: RequestGraphNode) =>
100
+ requestNode &&
101
+ requestNode.type === 1 &&
102
+ requestNode.requestType === requestTypes.write_bundle_request,
103
+ );
104
+
105
+ return new Map(
106
+ writeBundleRequests.map((request: RequestGraphNode) => [
107
+ request.result?.bundleId ?? '',
108
+ request,
109
+ ]),
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Builds a tree-map model for a bundle graph.
115
+ *
116
+ * The tree-map is a tree structure starting at the bundle and having layers for
117
+ * each asset sub-directory in its asset tree.
118
+ *
119
+ * Asset sizes are calculated from the asset size stats, which should represent
120
+ * sizes post-transformation, but before minification.
121
+ *
122
+ * Bundle sizes are read from the bundle files on disk.
123
+ *
124
+ * The sub-directories are sized to the size of their children.
125
+ */
126
+ export function buildTreemapBundle({
127
+ writeBundleRequestsByBundleId,
128
+ node,
129
+ projectRoot,
130
+ repositoryRoot,
131
+ bundleGraph,
132
+ }: {
133
+ writeBundleRequestsByBundleId: Map<string, RequestGraphNode>;
134
+ node: Node;
135
+ projectRoot: string;
136
+ repositoryRoot: string;
137
+ bundleGraph: BundleGraph;
138
+ }): TreemapBundle {
139
+ const writeBundleRequest = writeBundleRequestsByBundleId.get(node.id);
140
+ const {size, filePath} = findBundleInfo({
141
+ writeBundleRequest,
142
+ projectRoot,
143
+ repositoryRoot,
144
+ node,
145
+ });
146
+
147
+ // this is the directory structure
148
+ const assetTree: AssetTreeNode = {
149
+ id: `${node.id}::/`,
150
+ path: '',
151
+ children: {},
152
+ size: 0,
153
+ };
154
+
155
+ const assets: any[] = [];
156
+ bundleGraph.traverseAssets(node.value, (asset: any) => {
157
+ assets.push(asset);
158
+ });
159
+
160
+ for (const asset of assets) {
161
+ const filePath = path.isAbsolute(asset.filePath)
162
+ ? path.relative(repositoryRoot, asset.filePath)
163
+ : asset.filePath;
164
+ const parts = filePath.split('/');
165
+ const assetSize = asset.stats.size;
166
+ let current = assetTree;
167
+
168
+ let currentSubpath = '';
169
+ for (let part of parts) {
170
+ currentSubpath += '/' + part;
171
+ current.size += assetSize;
172
+ current.children[part] = current.children[part] ?? {
173
+ id: `${node.id}::${currentSubpath}`,
174
+ path: currentSubpath,
175
+ children: {},
176
+ size: 0,
177
+ };
178
+ current = current.children[part];
179
+ }
180
+ current.size += assetSize;
181
+ }
182
+
183
+ const treemapBundle = {
184
+ id: node.id,
185
+ displayName: node.value.displayName,
186
+ bundle: node,
187
+ size,
188
+ filePath: filePath ?? '',
189
+ assetTree,
190
+ };
191
+ return treemapBundle;
192
+ }
193
+
194
+ /**
195
+ * Finds the size and file path of a bundle.
196
+ *
197
+ * If the file path is not absolute, it is made absolute by joining it with the project root.
198
+ *
199
+ * If the file path is not found, the size is 0.
200
+ *
201
+ * If the file path is found but the file cannot be read, an error is logged and the size is 0.
202
+ */
203
+ export function findBundleInfo({
204
+ writeBundleRequest,
205
+ projectRoot,
206
+ repositoryRoot,
207
+ node,
208
+ }: {
209
+ writeBundleRequest: RequestGraphNode | undefined;
210
+ projectRoot: string;
211
+ repositoryRoot: string;
212
+ node: Node;
213
+ }): {size: number; filePath: string | null} {
214
+ let filePath = writeBundleRequest?.result?.filePath
215
+ ? path.isAbsolute(writeBundleRequest?.result?.filePath)
216
+ ? writeBundleRequest.result.filePath
217
+ : path.join(projectRoot, writeBundleRequest.result.filePath)
218
+ : null;
219
+ let size = 0;
220
+ try {
221
+ size = filePath ? fs.statSync(filePath).size : 0;
222
+ } catch (e) {
223
+ logger.error(
224
+ {filePath, nodeId: node.id, error: e},
225
+ `Error getting size of bundle at ${filePath}`,
226
+ );
227
+ }
228
+
229
+ filePath = filePath
230
+ ? path.isAbsolute(filePath)
231
+ ? path.relative(repositoryRoot, filePath)
232
+ : filePath
233
+ : null;
234
+
235
+ return {size, filePath};
236
+ }
237
+
238
+ export function buildTreemap({
239
+ projectRoot,
240
+ repositoryRoot,
241
+ bundleGraph,
242
+ requestTracker,
243
+ }: {
244
+ projectRoot: string;
245
+ repositoryRoot: string;
246
+ bundleGraph: BundleGraph;
247
+ requestTracker: RequestTracker;
248
+ }): Treemap {
249
+ const treemap: Treemap = {
250
+ bundles: [],
251
+ totalSize: 0,
252
+ };
253
+
254
+ const writeBundleRequestsByBundleId =
255
+ getWriteBundleRequestsByBundleId(requestTracker);
256
+
257
+ bundleGraph._graph.nodes.forEach((node: Node) => {
258
+ if (node.type === 'bundle') {
259
+ const treemapBundle = buildTreemapBundle({
260
+ writeBundleRequestsByBundleId,
261
+ node,
262
+ projectRoot,
263
+ repositoryRoot,
264
+ bundleGraph,
265
+ });
266
+
267
+ treemap.bundles.push(treemapBundle);
268
+ treemap.totalSize += treemapBundle.size;
269
+ }
270
+ });
271
+
272
+ return treemap;
273
+ }
@@ -0,0 +1,348 @@
1
+ import assert from 'assert';
2
+ import * as sinon from 'sinon';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import {buildTreemapBundle} from './buildTreemap';
6
+
7
+ jest.mock('../config/logger');
8
+
9
+ describe('buildTreemapBundle', function () {
10
+ let sandbox: sinon.SinonSandbox;
11
+ let mockBundleGraph: any;
12
+ let mockNode: any;
13
+ let mockWriteBundleRequestsByBundleId: Map<string, any>;
14
+
15
+ beforeEach(() => {
16
+ sandbox = sinon.createSandbox();
17
+
18
+ // Mock fs.statSync
19
+ sandbox.stub(fs, 'statSync').returns({size: 1024} as any);
20
+
21
+ // Mock path.join
22
+ sandbox
23
+ .stub(path, 'join')
24
+ .callsFake((...paths: string[]) => paths.join('/'));
25
+
26
+ // Mock bundle node
27
+ mockNode = {
28
+ id: 'bundle123',
29
+ type: 'bundle',
30
+ value: {
31
+ displayName: 'main.js',
32
+ },
33
+ };
34
+
35
+ // Mock bundle graph with traverseAssets method
36
+ mockBundleGraph = {
37
+ traverseAssets: sandbox.stub(),
38
+ };
39
+
40
+ // Mock write bundle requests map
41
+ mockWriteBundleRequestsByBundleId = new Map([
42
+ [
43
+ 'bundle123',
44
+ {
45
+ result: {
46
+ bundleId: 'bundle123',
47
+ filePath: 'dist/main.js',
48
+ },
49
+ },
50
+ ],
51
+ ]);
52
+ });
53
+
54
+ afterEach(() => {
55
+ sandbox.restore();
56
+ });
57
+
58
+ it('should build treemap bundle with correct basic properties', function () {
59
+ // Setup mock assets
60
+ const mockAssets = [
61
+ {
62
+ filePath: '/src/index.js',
63
+ stats: {size: 100},
64
+ },
65
+ {
66
+ filePath: '/src/utils.js',
67
+ stats: {size: 50},
68
+ },
69
+ ];
70
+
71
+ // Mock traverseAssets to call callback with each asset
72
+ mockBundleGraph.traverseAssets.callsFake(
73
+ (bundleValue: any, callback: (asset: any) => void) => {
74
+ mockAssets.forEach(callback);
75
+ },
76
+ );
77
+
78
+ const result = buildTreemapBundle({
79
+ writeBundleRequestsByBundleId: mockWriteBundleRequestsByBundleId,
80
+ node: mockNode,
81
+ projectRoot: '/project',
82
+ repositoryRoot: '/project',
83
+ bundleGraph: mockBundleGraph,
84
+ });
85
+
86
+ assert.equal(result.id, 'bundle123');
87
+ assert.equal(result.displayName, 'main.js');
88
+ assert.equal(result.bundle, mockNode);
89
+ assert.equal(result.size, 1024); // from mocked fs.statSync
90
+ assert.equal(result.filePath, 'dist/main.js');
91
+ assert(result.assetTree);
92
+ });
93
+
94
+ it('should handle missing write bundle request', function () {
95
+ const emptyMap = new Map();
96
+
97
+ mockBundleGraph.traverseAssets.callsFake(
98
+ (_bundleValue: any, _callback: (asset: any) => void) => {
99
+ // No assets
100
+ },
101
+ );
102
+
103
+ const result = buildTreemapBundle({
104
+ writeBundleRequestsByBundleId: emptyMap,
105
+ node: mockNode,
106
+ projectRoot: '/project',
107
+ repositoryRoot: '/project',
108
+ bundleGraph: mockBundleGraph,
109
+ });
110
+
111
+ assert.equal(result.size, 0);
112
+ assert.equal(result.filePath, '');
113
+ });
114
+
115
+ it('should build correct asset tree structure', function () {
116
+ const mockAssets = [
117
+ {
118
+ filePath: 'src/components/Button.js',
119
+ stats: {size: 100},
120
+ },
121
+ {
122
+ filePath: 'src/utils/helpers.js',
123
+ stats: {size: 50},
124
+ },
125
+ ];
126
+
127
+ mockBundleGraph.traverseAssets.callsFake(
128
+ (bundleValue: any, callback: (asset: any) => void) => {
129
+ mockAssets.forEach(callback);
130
+ },
131
+ );
132
+
133
+ const result = buildTreemapBundle({
134
+ writeBundleRequestsByBundleId: mockWriteBundleRequestsByBundleId,
135
+ node: mockNode,
136
+ projectRoot: '/project',
137
+ repositoryRoot: '/project',
138
+ bundleGraph: mockBundleGraph,
139
+ });
140
+
141
+ // Check that tree structure exists
142
+ assert(result.assetTree.children.src);
143
+ assert.equal(result.assetTree.path, '');
144
+ assert.equal(result.assetTree.children.src.path, '/src');
145
+
146
+ // Check components subdirectory exists
147
+ assert(result.assetTree.children.src.children.components);
148
+ assert.equal(
149
+ result.assetTree.children.src.children.components.path,
150
+ '/src/components',
151
+ );
152
+
153
+ // Check utils subdirectory exists
154
+ assert(result.assetTree.children.src.children.utils);
155
+ assert.equal(
156
+ result.assetTree.children.src.children.utils.path,
157
+ '/src/utils',
158
+ );
159
+
160
+ assert.equal(result.assetTree.size, 150);
161
+ assert.equal(result.assetTree.children.src.size, 150);
162
+ assert.equal(result.assetTree.children.src.children.components.size, 100);
163
+ assert.equal(result.assetTree.children.src.children.utils.size, 50);
164
+ });
165
+
166
+ it('should handle assets with same directory correctly', function () {
167
+ const mockAssets = [
168
+ {
169
+ filePath: 'lib/module1.js',
170
+ stats: {size: 100},
171
+ },
172
+ {
173
+ filePath: 'lib/module2.js',
174
+ stats: {size: 200},
175
+ },
176
+ ];
177
+
178
+ mockBundleGraph.traverseAssets.callsFake(
179
+ (bundleValue: any, callback: (asset: any) => void) => {
180
+ mockAssets.forEach(callback);
181
+ },
182
+ );
183
+
184
+ const result = buildTreemapBundle({
185
+ writeBundleRequestsByBundleId: mockWriteBundleRequestsByBundleId,
186
+ node: mockNode,
187
+ projectRoot: '/project',
188
+ repositoryRoot: '/project',
189
+ bundleGraph: mockBundleGraph,
190
+ });
191
+
192
+ // lib directory should contain total of all files under it
193
+ assert.equal(result.assetTree.children.lib.size, 300); // 100 + 200
194
+ assert.equal(
195
+ result.assetTree.children.lib.children['module1.js'].size,
196
+ 100,
197
+ );
198
+ assert.equal(
199
+ result.assetTree.children.lib.children['module2.js'].size,
200
+ 200,
201
+ );
202
+ });
203
+
204
+ it('should handle empty assets array', function () {
205
+ mockBundleGraph.traverseAssets.callsFake(
206
+ (_bundleValue: any, _callback: (asset: any) => void) => {
207
+ // No assets
208
+ },
209
+ );
210
+
211
+ const result = buildTreemapBundle({
212
+ writeBundleRequestsByBundleId: mockWriteBundleRequestsByBundleId,
213
+ node: mockNode,
214
+ projectRoot: '/project',
215
+ repositoryRoot: '/project',
216
+ bundleGraph: mockBundleGraph,
217
+ });
218
+
219
+ assert.equal(result.assetTree.size, 0);
220
+ assert.deepEqual(result.assetTree.children, {});
221
+ });
222
+
223
+ it('should handle file path without extension', function () {
224
+ const mockAssets = [
225
+ {
226
+ filePath: 'src/config',
227
+ stats: {size: 25},
228
+ },
229
+ ];
230
+
231
+ mockBundleGraph.traverseAssets.callsFake(
232
+ (_bundleValue: any, callback: (asset: any) => void) => {
233
+ mockAssets.forEach(callback);
234
+ },
235
+ );
236
+
237
+ const result = buildTreemapBundle({
238
+ writeBundleRequestsByBundleId: mockWriteBundleRequestsByBundleId,
239
+ node: mockNode,
240
+ projectRoot: '/project',
241
+ repositoryRoot: '/project',
242
+ bundleGraph: mockBundleGraph,
243
+ });
244
+
245
+ assert.equal(result.assetTree.children.src.children.config.size, 25);
246
+ });
247
+
248
+ it('should call traverseAssets with correct bundle value', function () {
249
+ mockBundleGraph.traverseAssets.callsFake(
250
+ (_bundleValue: any, _callback: (asset: any) => void) => {
251
+ // Just verify it was called
252
+ },
253
+ );
254
+
255
+ buildTreemapBundle({
256
+ writeBundleRequestsByBundleId: mockWriteBundleRequestsByBundleId,
257
+ node: mockNode,
258
+ projectRoot: '/project',
259
+ repositoryRoot: '/project',
260
+ bundleGraph: mockBundleGraph,
261
+ });
262
+
263
+ assert(mockBundleGraph.traverseAssets.calledOnce);
264
+ assert(mockBundleGraph.traverseAssets.calledWith(mockNode.value));
265
+ });
266
+
267
+ it('should calculate directory sizes correctly', function () {
268
+ // Test case matching user's example:
269
+ // lib/child/a.js - 10
270
+ // lib/other/b.js - 10
271
+ // lib/other/c.js - 10
272
+ const mockAssets = [
273
+ {
274
+ filePath: 'lib/child/a.js',
275
+ stats: {size: 10},
276
+ },
277
+ {
278
+ filePath: 'lib/other/b.js',
279
+ stats: {size: 10},
280
+ },
281
+ {
282
+ filePath: 'lib/other/c.js',
283
+ stats: {size: 10},
284
+ },
285
+ ];
286
+
287
+ mockBundleGraph.traverseAssets.callsFake(
288
+ (bundleValue: any, callback: (asset: any) => void) => {
289
+ mockAssets.forEach(callback);
290
+ },
291
+ );
292
+
293
+ const result = buildTreemapBundle({
294
+ writeBundleRequestsByBundleId: mockWriteBundleRequestsByBundleId,
295
+ node: mockNode,
296
+ projectRoot: '/project',
297
+ repositoryRoot: '/project',
298
+ bundleGraph: mockBundleGraph,
299
+ });
300
+
301
+ // Root should contain total of all files
302
+ // assert.equal(result.assetTree.size, 30); // 10+10+10
303
+
304
+ // lib directory should contain total of all files under lib
305
+ assert.equal(result.assetTree.children.lib.size, 30); // 10+10+10
306
+
307
+ // lib/child should contain only a.js
308
+ assert.equal(result.assetTree.children.lib.children.child.size, 10); // 10
309
+
310
+ // lib/other should contain b.js + c.js
311
+ assert.equal(result.assetTree.children.lib.children.other.size, 20); // 10+10
312
+
313
+ // Individual files should have their own sizes
314
+ assert.equal(
315
+ result.assetTree.children.lib.children.child.children['a.js'].size,
316
+ 10,
317
+ );
318
+ assert.equal(
319
+ result.assetTree.children.lib.children.other.children['b.js'].size,
320
+ 10,
321
+ );
322
+ assert.equal(
323
+ result.assetTree.children.lib.children.other.children['c.js'].size,
324
+ 10,
325
+ );
326
+ });
327
+
328
+ it('should handle fs.statSync errors gracefully', function () {
329
+ // Make fs.statSync throw an error
330
+ (fs.statSync as sinon.SinonStub).throws(new Error('File not found'));
331
+
332
+ mockBundleGraph.traverseAssets.callsFake(
333
+ (_bundleValue: any, _callback: (asset: any) => void) => {
334
+ // No assets
335
+ },
336
+ );
337
+
338
+ assert.doesNotThrow(() => {
339
+ buildTreemapBundle({
340
+ writeBundleRequestsByBundleId: mockWriteBundleRequestsByBundleId,
341
+ node: mockNode,
342
+ projectRoot: '/project',
343
+ repositoryRoot: '/project',
344
+ bundleGraph: mockBundleGraph,
345
+ });
346
+ }, Error);
347
+ });
348
+ });