@rocket/js 0.0.0 → 0.1.0

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 (340) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +224 -2
  3. package/dist-types/exports/MainMenu.d.ts +2 -0
  4. package/dist-types/exports/MainMenu.d.ts.map +1 -0
  5. package/dist-types/exports/PageData.d.ts +2 -0
  6. package/dist-types/exports/PageData.d.ts.map +1 -0
  7. package/dist-types/exports/RocketCodeBlock.d.ts +2 -0
  8. package/dist-types/exports/RocketCodeBlock.d.ts.map +1 -0
  9. package/dist-types/exports/RocketIcon.d.ts +2 -0
  10. package/dist-types/exports/RocketIcon.d.ts.map +1 -0
  11. package/dist-types/exports/RocketJsDemo.d.ts +2 -0
  12. package/dist-types/exports/RocketJsDemo.d.ts.map +1 -0
  13. package/dist-types/exports/RocketRequestDemo.d.ts +2 -0
  14. package/dist-types/exports/RocketRequestDemo.d.ts.map +1 -0
  15. package/dist-types/exports/SocialPreviewPlayground.d.ts +2 -0
  16. package/dist-types/exports/SocialPreviewPlayground.d.ts.map +1 -0
  17. package/dist-types/exports/adapters/netlify.d.ts +2 -0
  18. package/dist-types/exports/adapters/netlify.d.ts.map +1 -0
  19. package/dist-types/exports/asyncMessage.d.ts +2 -0
  20. package/dist-types/exports/asyncMessage.d.ts.map +1 -0
  21. package/dist-types/exports/component-hydration.d.ts +2 -0
  22. package/dist-types/exports/component-hydration.d.ts.map +1 -0
  23. package/dist-types/exports/components/web-awesome.d.ts +3 -0
  24. package/dist-types/exports/components/web-awesome.d.ts.map +1 -0
  25. package/dist-types/exports/components.d.ts +2 -0
  26. package/dist-types/exports/components.d.ts.map +1 -0
  27. package/dist-types/exports/config.d.ts +2 -0
  28. package/dist-types/exports/config.d.ts.map +1 -0
  29. package/dist-types/exports/debounce.d.ts +2 -0
  30. package/dist-types/exports/debounce.d.ts.map +1 -0
  31. package/dist-types/exports/define/RocketCodeBlock.d.ts +2 -0
  32. package/dist-types/exports/define/RocketCodeBlock.d.ts.map +1 -0
  33. package/dist-types/exports/define/RocketIcon.d.ts +2 -0
  34. package/dist-types/exports/define/RocketIcon.d.ts.map +1 -0
  35. package/dist-types/exports/define/RocketJsDemo.d.ts +2 -0
  36. package/dist-types/exports/define/RocketJsDemo.d.ts.map +1 -0
  37. package/dist-types/exports/define/RocketRequestDemo.d.ts +2 -0
  38. package/dist-types/exports/define/RocketRequestDemo.d.ts.map +1 -0
  39. package/dist-types/exports/define/menus.d.ts +2 -0
  40. package/dist-types/exports/define/menus.d.ts.map +1 -0
  41. package/dist-types/exports/extractCode.d.ts +2 -0
  42. package/dist-types/exports/extractCode.d.ts.map +1 -0
  43. package/dist-types/exports/globalData.d.ts +2 -0
  44. package/dist-types/exports/globalData.d.ts.map +1 -0
  45. package/dist-types/exports/hydration/hydrationLoader.d.ts +2 -0
  46. package/dist-types/exports/hydration/hydrationLoader.d.ts.map +1 -0
  47. package/dist-types/exports/icons.d.ts +2 -0
  48. package/dist-types/exports/icons.d.ts.map +1 -0
  49. package/dist-types/exports/layout-helper.d.ts +2 -0
  50. package/dist-types/exports/layout-helper.d.ts.map +1 -0
  51. package/dist-types/exports/layout.d.ts +2 -0
  52. package/dist-types/exports/layout.d.ts.map +1 -0
  53. package/dist-types/exports/layouts/atlasDoc.d.ts +2 -0
  54. package/dist-types/exports/layouts/atlasDoc.d.ts.map +1 -0
  55. package/dist-types/exports/layouts/atlasHero.d.ts +2 -0
  56. package/dist-types/exports/layouts/atlasHero.d.ts.map +1 -0
  57. package/dist-types/exports/layouts/atlasNotFound.d.ts +2 -0
  58. package/dist-types/exports/layouts/atlasNotFound.d.ts.map +1 -0
  59. package/dist-types/exports/loaded-page-module.d.ts +2 -0
  60. package/dist-types/exports/loaded-page-module.d.ts.map +1 -0
  61. package/dist-types/exports/markdownHook.d.ts +2 -0
  62. package/dist-types/exports/markdownHook.d.ts.map +1 -0
  63. package/dist-types/exports/menu.d.ts +2 -0
  64. package/dist-types/exports/menu.d.ts.map +1 -0
  65. package/dist-types/exports/menus.d.ts +6 -0
  66. package/dist-types/exports/menus.d.ts.map +1 -0
  67. package/dist-types/exports/page-runtime.d.ts +2 -0
  68. package/dist-types/exports/page-runtime.d.ts.map +1 -0
  69. package/dist-types/exports/pages.d.ts +2 -0
  70. package/dist-types/exports/pages.d.ts.map +1 -0
  71. package/dist-types/exports/resolve.d.ts +2 -0
  72. package/dist-types/exports/resolve.d.ts.map +1 -0
  73. package/dist-types/exports/ssr.d.ts +2 -0
  74. package/dist-types/exports/ssr.d.ts.map +1 -0
  75. package/dist-types/exports/standalone-demo-url.d.ts +2 -0
  76. package/dist-types/exports/standalone-demo-url.d.ts.map +1 -0
  77. package/dist-types/exports/transform.d.ts +2 -0
  78. package/dist-types/exports/transform.d.ts.map +1 -0
  79. package/dist-types/exports/types/hydration.d.ts +23 -0
  80. package/dist-types/exports/types/hydration.d.ts.map +1 -0
  81. package/dist-types/exports/types/rocket.d.ts +504 -0
  82. package/dist-types/exports/types/rocket.d.ts.map +1 -0
  83. package/dist-types/exports/types.d.ts +3 -0
  84. package/dist-types/exports/types.d.ts.map +1 -0
  85. package/dist-types/exports/wds-plugin.d.ts +2 -0
  86. package/dist-types/exports/wds-plugin.d.ts.map +1 -0
  87. package/dist-types/src/PageData.d.ts +82 -0
  88. package/dist-types/src/PageData.d.ts.map +1 -0
  89. package/dist-types/src/RocketCodeBlock.d.ts +64 -0
  90. package/dist-types/src/RocketCodeBlock.d.ts.map +1 -0
  91. package/dist-types/src/RocketIcon.d.ts +35 -0
  92. package/dist-types/src/RocketIcon.d.ts.map +1 -0
  93. package/dist-types/src/RocketJsDemo.d.ts +59 -0
  94. package/dist-types/src/RocketJsDemo.d.ts.map +1 -0
  95. package/dist-types/src/RocketJsDemo.test-browser.d.ts +3 -0
  96. package/dist-types/src/RocketJsDemo.test-browser.d.ts.map +1 -0
  97. package/dist-types/src/RocketRequestDemo.d.ts +57 -0
  98. package/dist-types/src/RocketRequestDemo.d.ts.map +1 -0
  99. package/dist-types/src/RocketRequestDemo.test-browser.d.ts +3 -0
  100. package/dist-types/src/RocketRequestDemo.test-browser.d.ts.map +1 -0
  101. package/dist-types/src/SocialPreviewPlayground.d.ts +102 -0
  102. package/dist-types/src/SocialPreviewPlayground.d.ts.map +1 -0
  103. package/dist-types/src/adapters/netlify.d.ts +54 -0
  104. package/dist-types/src/adapters/netlify.d.ts.map +1 -0
  105. package/dist-types/src/asyncMessage.d.ts +14 -0
  106. package/dist-types/src/asyncMessage.d.ts.map +1 -0
  107. package/dist-types/src/cli/RocketBuild.d.ts +78 -0
  108. package/dist-types/src/cli/RocketBuild.d.ts.map +1 -0
  109. package/dist-types/src/cli/RocketCli.d.ts +17 -0
  110. package/dist-types/src/cli/RocketCli.d.ts.map +1 -0
  111. package/dist-types/src/cli/RocketInit.d.ts +22 -0
  112. package/dist-types/src/cli/RocketInit.d.ts.map +1 -0
  113. package/dist-types/src/cli/RocketStart.d.ts +13 -0
  114. package/dist-types/src/cli/RocketStart.d.ts.map +1 -0
  115. package/dist-types/src/cli/cli.d.ts +3 -0
  116. package/dist-types/src/cli/cli.d.ts.map +1 -0
  117. package/dist-types/src/component-hydration.d.ts +26 -0
  118. package/dist-types/src/component-hydration.d.ts.map +1 -0
  119. package/dist-types/src/components/FeatureList.d.ts +15 -0
  120. package/dist-types/src/components/FeatureList.d.ts.map +1 -0
  121. package/dist-types/src/components/Footer.d.ts +17 -0
  122. package/dist-types/src/components/Footer.d.ts.map +1 -0
  123. package/dist-types/src/components/Header.d.ts +6 -0
  124. package/dist-types/src/components/Header.d.ts.map +1 -0
  125. package/dist-types/src/components/RocketDrawer.d.ts +20 -0
  126. package/dist-types/src/components/RocketDrawer.d.ts.map +1 -0
  127. package/dist-types/src/components/RocketSocialLink.d.ts +30 -0
  128. package/dist-types/src/components/RocketSocialLink.d.ts.map +1 -0
  129. package/dist-types/src/components.d.ts +5 -0
  130. package/dist-types/src/components.d.ts.map +1 -0
  131. package/dist-types/src/config.d.ts +6 -0
  132. package/dist-types/src/config.d.ts.map +1 -0
  133. package/dist-types/src/debounce.d.ts +8 -0
  134. package/dist-types/src/debounce.d.ts.map +1 -0
  135. package/dist-types/src/defaultSocialPreviewTemplate.d.ts +31 -0
  136. package/dist-types/src/defaultSocialPreviewTemplate.d.ts.map +1 -0
  137. package/dist-types/src/development-page-module-loader.d.ts +15 -0
  138. package/dist-types/src/development-page-module-loader.d.ts.map +1 -0
  139. package/dist-types/src/extractCode.d.ts +5 -0
  140. package/dist-types/src/extractCode.d.ts.map +1 -0
  141. package/dist-types/src/hydration/evaluate.d.ts +20 -0
  142. package/dist-types/src/hydration/evaluate.d.ts.map +1 -0
  143. package/dist-types/src/hydration/extractStrategies.d.ts +5 -0
  144. package/dist-types/src/hydration/extractStrategies.d.ts.map +1 -0
  145. package/dist-types/src/hydration/hydrationLoader.d.ts +64 -0
  146. package/dist-types/src/hydration/hydrationLoader.d.ts.map +1 -0
  147. package/dist-types/src/icons.d.ts +170 -0
  148. package/dist-types/src/icons.d.ts.map +1 -0
  149. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts +45 -0
  150. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts.map +1 -0
  151. package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts +7 -0
  152. package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts.map +1 -0
  153. package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts +5 -0
  154. package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts.map +1 -0
  155. package/dist-types/src/layouts/layout-helper.d.ts +16 -0
  156. package/dist-types/src/layouts/layout-helper.d.ts.map +1 -0
  157. package/dist-types/src/layouts/layout.d.ts +9 -0
  158. package/dist-types/src/layouts/layout.d.ts.map +1 -0
  159. package/dist-types/src/loaded-page-module.d.ts +51 -0
  160. package/dist-types/src/loaded-page-module.d.ts.map +1 -0
  161. package/dist-types/src/main.d.ts +2 -0
  162. package/dist-types/src/main.d.ts.map +1 -0
  163. package/dist-types/src/markdownCompiler.d.ts +22 -0
  164. package/dist-types/src/markdownCompiler.d.ts.map +1 -0
  165. package/dist-types/src/markdownHook.d.ts +6 -0
  166. package/dist-types/src/markdownHook.d.ts.map +1 -0
  167. package/dist-types/src/menu.d.ts +22 -0
  168. package/dist-types/src/menu.d.ts.map +1 -0
  169. package/dist-types/src/menus/MainMenu.d.ts +23 -0
  170. package/dist-types/src/menus/MainMenu.d.ts.map +1 -0
  171. package/dist-types/src/menus/RocketMenu.d.ts +23 -0
  172. package/dist-types/src/menus/RocketMenu.d.ts.map +1 -0
  173. package/dist-types/src/menus/RocketNextPage.d.ts +18 -0
  174. package/dist-types/src/menus/RocketNextPage.d.ts.map +1 -0
  175. package/dist-types/src/menus/RocketPreviousPage.d.ts +18 -0
  176. package/dist-types/src/menus/RocketPreviousPage.d.ts.map +1 -0
  177. package/dist-types/src/menus/RocketToc.d.ts +54 -0
  178. package/dist-types/src/menus/RocketToc.d.ts.map +1 -0
  179. package/dist-types/src/menus/pageNavigation.d.ts +41 -0
  180. package/dist-types/src/menus/pageNavigation.d.ts.map +1 -0
  181. package/dist-types/src/page-pagination.d.ts +69 -0
  182. package/dist-types/src/page-pagination.d.ts.map +1 -0
  183. package/dist-types/src/page-runtime.d.ts +110 -0
  184. package/dist-types/src/page-runtime.d.ts.map +1 -0
  185. package/dist-types/src/pages.d.ts +10 -0
  186. package/dist-types/src/pages.d.ts.map +1 -0
  187. package/dist-types/src/publicAssets.d.ts +70 -0
  188. package/dist-types/src/publicAssets.d.ts.map +1 -0
  189. package/dist-types/src/requestDemoMetadata.d.ts +19 -0
  190. package/dist-types/src/requestDemoMetadata.d.ts.map +1 -0
  191. package/dist-types/src/resolve.d.ts +7 -0
  192. package/dist-types/src/resolve.d.ts.map +1 -0
  193. package/dist-types/src/siteDiscoverability.d.ts +33 -0
  194. package/dist-types/src/siteDiscoverability.d.ts.map +1 -0
  195. package/dist-types/src/siteHeadMetadata.d.ts +20 -0
  196. package/dist-types/src/siteHeadMetadata.d.ts.map +1 -0
  197. package/dist-types/src/socialPreviewImages.d.ts +186 -0
  198. package/dist-types/src/socialPreviewImages.d.ts.map +1 -0
  199. package/dist-types/src/socialPreviewTemplatePreview.d.ts +22 -0
  200. package/dist-types/src/socialPreviewTemplatePreview.d.ts.map +1 -0
  201. package/dist-types/src/ssr.d.ts +6 -0
  202. package/dist-types/src/ssr.d.ts.map +1 -0
  203. package/dist-types/src/standalone-demo-url.d.ts +60 -0
  204. package/dist-types/src/standalone-demo-url.d.ts.map +1 -0
  205. package/dist-types/src/static-page-module-loader.d.ts +15 -0
  206. package/dist-types/src/static-page-module-loader.d.ts.map +1 -0
  207. package/dist-types/src/transform.d.ts +10 -0
  208. package/dist-types/src/transform.d.ts.map +1 -0
  209. package/dist-types/src/urlLifecycle.d.ts +23 -0
  210. package/dist-types/src/urlLifecycle.d.ts.map +1 -0
  211. package/dist-types/src/wds-plugin.d.ts +14 -0
  212. package/dist-types/src/wds-plugin.d.ts.map +1 -0
  213. package/docs/assets/home-background.svg +1 -0
  214. package/docs/assets/prism-one-light.css +368 -0
  215. package/docs/assets/rocket-logo-dark-with-text-below.svg +8 -0
  216. package/docs/assets/rocket-logo-dark-with-text.svg +7 -0
  217. package/docs/assets/rocket-logo-dark.svg +7 -0
  218. package/docs/assets/rocket-logo-light-with-text-below.svg +14 -0
  219. package/docs/assets/rocket-logo-light-with-text.svg +13 -0
  220. package/docs/assets/rocket-logo-light.svg +12 -0
  221. package/docs/assets/rocket-text-no-logo.svg +3 -0
  222. package/exports/MainMenu.js +1 -0
  223. package/exports/PageData.js +1 -0
  224. package/exports/RocketCodeBlock.js +1 -0
  225. package/exports/RocketIcon.js +1 -0
  226. package/exports/RocketJsDemo.js +1 -0
  227. package/exports/RocketRequestDemo.js +1 -0
  228. package/exports/SocialPreviewPlayground.js +1 -0
  229. package/exports/adapters/netlify.js +1 -0
  230. package/exports/asyncMessage.js +1 -0
  231. package/exports/component-hydration.js +1 -0
  232. package/exports/components/web-awesome.js +63 -0
  233. package/exports/components.js +1 -0
  234. package/exports/config.js +1 -0
  235. package/exports/debounce.js +1 -0
  236. package/exports/define/RocketCodeBlock.js +2 -0
  237. package/exports/define/RocketIcon.js +3 -0
  238. package/exports/define/RocketJsDemo.js +5 -0
  239. package/exports/define/RocketRequestDemo.js +5 -0
  240. package/exports/define/menus.js +14 -0
  241. package/exports/extractCode.js +1 -0
  242. package/exports/globalData.js +1 -0
  243. package/exports/hydration/hydrationLoader.js +1 -0
  244. package/exports/icons.js +8 -0
  245. package/exports/layout-helper.js +1 -0
  246. package/exports/layout.js +1 -0
  247. package/exports/layouts/_atlas.css +3 -0
  248. package/exports/layouts/atlasDoc.js +5 -0
  249. package/exports/layouts/atlasHero.js +1 -0
  250. package/exports/layouts/atlasNotFound.js +4 -0
  251. package/exports/loaded-page-module.js +5 -0
  252. package/exports/markdownHook.js +4 -0
  253. package/exports/menu.js +4 -0
  254. package/exports/menus.js +5 -0
  255. package/exports/page-runtime.js +5 -0
  256. package/exports/pages.js +1 -0
  257. package/exports/resolve.js +1 -0
  258. package/exports/ssr.js +1 -0
  259. package/exports/standalone-demo-url.js +10 -0
  260. package/exports/transform.js +1 -0
  261. package/exports/types/hydration.ts +26 -0
  262. package/exports/types/rocket.ts +598 -0
  263. package/exports/types.ts +71 -0
  264. package/exports/wds-plugin.js +1 -0
  265. package/package.json +192 -9
  266. package/src/PageData.js +244 -0
  267. package/src/RocketCodeBlock.js +516 -0
  268. package/src/RocketIcon.js +291 -0
  269. package/src/RocketJsDemo.js +397 -0
  270. package/src/RocketJsDemo.test-browser.js +228 -0
  271. package/src/RocketRequestDemo.js +439 -0
  272. package/src/RocketRequestDemo.test-browser.js +301 -0
  273. package/src/SocialPreviewPlayground.js +573 -0
  274. package/src/adapters/netlify.js +814 -0
  275. package/src/asyncMessage.js +21 -0
  276. package/src/cli/RocketBuild.js +545 -0
  277. package/src/cli/RocketCli.js +47 -0
  278. package/src/cli/RocketInit.js +323 -0
  279. package/src/cli/RocketStart.js +79 -0
  280. package/src/cli/cli.js +7 -0
  281. package/src/component-hydration.js +86 -0
  282. package/src/components/FeatureList.js +114 -0
  283. package/src/components/Footer.js +116 -0
  284. package/src/components/Header.js +122 -0
  285. package/src/components/RocketDrawer.js +193 -0
  286. package/src/components/RocketSocialLink.js +128 -0
  287. package/src/components/assets/discord.svg +7 -0
  288. package/src/components/assets/github.svg +4 -0
  289. package/src/components/assets/gitlab.svg +1 -0
  290. package/src/components/assets/info.txt +1 -0
  291. package/src/components/assets/license.svg +3 -0
  292. package/src/components/assets/npm.svg +5 -0
  293. package/src/components/assets/slack.svg +5 -0
  294. package/src/components/assets/telegram.svg +4 -0
  295. package/src/components/assets/twitter.svg +1 -0
  296. package/src/components.js +15 -0
  297. package/src/config.js +319 -0
  298. package/src/debounce.js +21 -0
  299. package/src/defaultSocialPreviewTemplate.js +118 -0
  300. package/src/development-page-module-loader.js +29 -0
  301. package/src/extractCode.js +41 -0
  302. package/src/hydration/evaluate.js +54 -0
  303. package/src/hydration/extractStrategies.js +91 -0
  304. package/src/hydration/hydrationLoader.js +330 -0
  305. package/src/icons.js +883 -0
  306. package/src/layouts/atlas/atlasDoc.css +877 -0
  307. package/src/layouts/atlas/atlasDocLayout.js +288 -0
  308. package/src/layouts/atlas/atlasHero.css +774 -0
  309. package/src/layouts/atlas/atlasHeroLayout.js +346 -0
  310. package/src/layouts/atlas/atlasNotFound.css +365 -0
  311. package/src/layouts/atlas/atlasNotFoundLayout.js +69 -0
  312. package/src/layouts/layout-helper.js +92 -0
  313. package/src/layouts/layout.js +62 -0
  314. package/src/loaded-page-module.js +97 -0
  315. package/src/main.js +55 -0
  316. package/src/markdownCompiler.js +303 -0
  317. package/src/markdownHook.js +148 -0
  318. package/src/menu.js +210 -0
  319. package/src/menus/MainMenu.js +58 -0
  320. package/src/menus/RocketMenu.js +191 -0
  321. package/src/menus/RocketNextPage.js +25 -0
  322. package/src/menus/RocketPreviousPage.js +29 -0
  323. package/src/menus/RocketToc.js +309 -0
  324. package/src/menus/pageNavigation.js +285 -0
  325. package/src/page-pagination.js +241 -0
  326. package/src/page-runtime.js +481 -0
  327. package/src/pages.js +537 -0
  328. package/src/publicAssets.js +336 -0
  329. package/src/requestDemoMetadata.js +97 -0
  330. package/src/resolve.js +15 -0
  331. package/src/siteDiscoverability.js +184 -0
  332. package/src/siteHeadMetadata.js +69 -0
  333. package/src/socialPreviewImages.js +482 -0
  334. package/src/socialPreviewTemplatePreview.js +352 -0
  335. package/src/ssr.js +14 -0
  336. package/src/standalone-demo-url.js +134 -0
  337. package/src/static-page-module-loader.js +29 -0
  338. package/src/transform.js +633 -0
  339. package/src/urlLifecycle.js +57 -0
  340. package/src/wds-plugin.js +302 -0
package/src/icons.js ADDED
@@ -0,0 +1,883 @@
1
+ /** Runs on: server */
2
+ import { glob, readFile } from 'node:fs/promises';
3
+ import { createHash } from 'node:crypto';
4
+ import { readFileSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { createRequire } from 'node:module';
7
+ import * as parse5 from 'parse5';
8
+
9
+ const require = createRequire(import.meta.url);
10
+
11
+ const ICON_LOADING_VALUES = new Set(['auto', 'server', 'client']);
12
+ const ICON_REFERENCE_CONFIG_FIELDS = new Set(['library', 'name']);
13
+ const ROCKET_ICON_ASSET_PREFIX = '/_rocket/icons/';
14
+ const ROCKET_ICON_DEFINE_MODULE_PATH = '/_rocket/rocket-icon.js';
15
+ const ROCKET_ICON_CLASS_MODULE_PATH = '/_rocket/RocketIcon.js';
16
+ const SHADOW_ICON_STYLE =
17
+ ':host{display:inline-block;width:1em;height:1em;vertical-align:-0.125em;line-height:1}' +
18
+ ':host([hidden]){display:none}' +
19
+ 'span[part="icon"]{display:inline-flex;width:100%;height:100%;line-height:1}' +
20
+ 'span[part="icon"]>svg{display:block;width:100%;height:100%}';
21
+
22
+ /** @type {Map<string, Promise<Map<string, string>>>} */
23
+ const libraryIndexCache = new Map();
24
+
25
+ /**
26
+ * @param {string} packageName
27
+ * @param {string} files
28
+ * @returns {import('@rocket/js/types.js').IconLibrarySource}
29
+ */
30
+ export function iconsFromPackage(packageName, files) {
31
+ return {
32
+ type: 'package',
33
+ packageName: readNonEmptyString(packageName, 'Icon package name'),
34
+ files: readNonEmptyString(files, 'Icon package source files'),
35
+ };
36
+ }
37
+
38
+ /**
39
+ * @param {string} files
40
+ * @returns {import('@rocket/js/types.js').IconLibrarySource}
41
+ */
42
+ export function iconsFromPath(files) {
43
+ return {
44
+ type: 'path',
45
+ files: readNonEmptyString(files, 'Icon path source files'),
46
+ };
47
+ }
48
+
49
+ export class IconAssetStore {
50
+ constructor() {
51
+ /** @type {Map<string, { url: string; svg: string; library: string; name: string }>} */
52
+ this.assets = new Map();
53
+ this.needsRuntime = false;
54
+ }
55
+
56
+ /**
57
+ * @param {{ library: string; name: string; svg: string }} icon
58
+ */
59
+ addIcon({ library, name, svg }) {
60
+ const url = iconAssetUrl({ library, name, svg });
61
+ if (!this.assets.has(url)) {
62
+ this.assets.set(url, { url, svg, library, name });
63
+ }
64
+ return url;
65
+ }
66
+
67
+ /**
68
+ * @param {string} url
69
+ */
70
+ get(url) {
71
+ return this.assets.get(url);
72
+ }
73
+
74
+ outputs() {
75
+ return [...this.assets.values()];
76
+ }
77
+ }
78
+
79
+ export function createIconAssetStore() {
80
+ return new IconAssetStore();
81
+ }
82
+
83
+ export function rocketIconRuntimeOutputs() {
84
+ return [
85
+ {
86
+ path: ROCKET_ICON_DEFINE_MODULE_PATH,
87
+ type: 'application/javascript',
88
+ data:
89
+ `import { RocketIcon } from '${ROCKET_ICON_CLASS_MODULE_PATH}';\n` +
90
+ "if (!customElements.get('rocket-icon')) {\n" +
91
+ " customElements.define('rocket-icon', RocketIcon);\n" +
92
+ '}\n',
93
+ },
94
+ {
95
+ path: ROCKET_ICON_CLASS_MODULE_PATH,
96
+ type: 'application/javascript',
97
+ data: readFileSync(new URL('./RocketIcon.js', import.meta.url), 'utf8'),
98
+ },
99
+ ];
100
+ }
101
+
102
+ /**
103
+ * @param {string} requestPath
104
+ * @param {{
105
+ * iconLibraries?: import('@rocket/js/types.js').IconLibrariesConfig;
106
+ * defaultIconLibrary?: string;
107
+ * layoutIconLibraries?: import('@rocket/js/types.js').IconLibrariesConfig | Map<string, import('@rocket/js/types.js').NormalizedIconLibraryConfig>;
108
+ * layoutDefaultIconLibrary?: string;
109
+ * }} [options]
110
+ * @returns {Promise<{ url: string; svg: string; library: string; name: string } | undefined>}
111
+ */
112
+ export async function resolveRocketIconAsset(requestPath, options = {}) {
113
+ const match = /^\/_rocket\/icons\/([^/]+)\/(.+)\.([a-f0-9]{12})\.svg$/.exec(requestPath);
114
+ if (!match) {
115
+ return undefined;
116
+ }
117
+
118
+ const librarySegment = match[1];
119
+ const resolver = createIconResolver({
120
+ layoutIconLibraries: options.layoutIconLibraries,
121
+ layoutDefaultIconLibrary: options.layoutDefaultIconLibrary,
122
+ projectIconLibraries: options.iconLibraries,
123
+ projectDefaultIconLibrary: options.defaultIconLibrary,
124
+ });
125
+
126
+ for (const [library, config] of resolver.libraries) {
127
+ if (sanitizePathSegment(library) !== librarySegment) {
128
+ continue;
129
+ }
130
+ const icons = await indexIconLibrary(library, config);
131
+ for (const [name, svg] of icons) {
132
+ const url = iconAssetUrl({ library, name, svg });
133
+ if (url === requestPath) {
134
+ return { url, svg, library, name };
135
+ }
136
+ }
137
+ }
138
+
139
+ return undefined;
140
+ }
141
+
142
+ /**
143
+ * @param {unknown} iconLibraries
144
+ */
145
+ export function validateIconLibrariesConfig(iconLibraries) {
146
+ normalizeIconLibrariesConfig(iconLibraries, 'Icon Library Configuration');
147
+ }
148
+
149
+ /**
150
+ * @param {string} html
151
+ * @param {{
152
+ * iconLibraries?: import('@rocket/js/types.js').IconLibrariesConfig;
153
+ * defaultIconLibrary?: string;
154
+ * layoutIconLibraries?: import('@rocket/js/types.js').IconLibrariesConfig | Map<string, import('@rocket/js/types.js').NormalizedIconLibraryConfig>;
155
+ * layoutDefaultIconLibrary?: string;
156
+ * pageData?: {
157
+ * _iconLibraries?: Map<string, import('@rocket/js/types.js').NormalizedIconLibraryConfig>;
158
+ * _defaultIconLibrary?: string;
159
+ * _iconReferences?: import('@rocket/js/types.js').IconReferenceConfig[];
160
+ * _hydrationScript?: string;
161
+ * _hasBrowserLoadedComponents?: boolean;
162
+ * };
163
+ * iconAssetStore?: IconAssetStore;
164
+ * }} [options]
165
+ * @returns {Promise<string>}
166
+ */
167
+ export async function finalizeRocketIcons(html, options = {}) {
168
+ const lowerHtml = html.toLowerCase();
169
+ const hasIconHosts = lowerHtml.includes('<rocket-icon');
170
+ const hasIconLoadingPolicy =
171
+ lowerHtml.includes('icon-loading-region') || lowerHtml.includes('icon-server-budget');
172
+ const extraIconReferences = options.pageData?._iconReferences || [];
173
+ const hasExtraIconReferences = extraIconReferences.length > 0;
174
+ if (
175
+ !hasIconHosts &&
176
+ !hasIconLoadingPolicy &&
177
+ !pageNeedsDeferredIconRuntime(options.pageData) &&
178
+ !hasExtraIconReferences
179
+ ) {
180
+ return html;
181
+ }
182
+
183
+ const resolver = createIconResolver({
184
+ layoutIconLibraries: options.layoutIconLibraries || options.pageData?._iconLibraries,
185
+ layoutDefaultIconLibrary:
186
+ options.layoutDefaultIconLibrary || options.pageData?._defaultIconLibrary,
187
+ projectIconLibraries: options.iconLibraries,
188
+ projectDefaultIconLibrary: options.defaultIconLibrary,
189
+ });
190
+ const iconAssetStore = options.iconAssetStore || createIconAssetStore();
191
+ /** @type {Map<string, { library: string; name: string; svg?: string }>} */
192
+ const iconReferences = new Map();
193
+ let hasClientIcon = false;
194
+
195
+ const replacements = await finalizeRocketIconHosts(html, resolver);
196
+ for (const replacement of replacements) {
197
+ const renderedIcon = replacement.renderedIcon;
198
+ iconReferences.set(iconReferenceKey(renderedIcon.library, renderedIcon.name), {
199
+ library: renderedIcon.library,
200
+ name: renderedIcon.name,
201
+ svg: renderedIcon.svg || undefined,
202
+ });
203
+ if (renderedIcon.iconLoading === 'client') {
204
+ hasClientIcon = true;
205
+ }
206
+ }
207
+ for (const iconReference of extraIconReferences) {
208
+ const library = resolver.resolveLibrary(iconReference.library, iconReference.name);
209
+ const key = iconReferenceKey(library, iconReference.name);
210
+ if (!iconReferences.has(key)) {
211
+ iconReferences.set(key, {
212
+ library,
213
+ name: iconReference.name,
214
+ });
215
+ }
216
+ }
217
+
218
+ const output = applyHtmlReplacements(html, replacements);
219
+
220
+ const needsManifest =
221
+ hasClientIcon || hasExtraIconReferences || pageNeedsDeferredIconRuntime(options.pageData);
222
+ if (!needsManifest) {
223
+ return output;
224
+ }
225
+
226
+ /** @type {Record<string, string>} */
227
+ const manifestIcons = {};
228
+ for (const [key, reference] of iconReferences) {
229
+ const svg = reference.svg || (await resolver.loadIcon(reference.library, reference.name));
230
+ manifestIcons[key] = iconAssetStore.addIcon({
231
+ library: reference.library,
232
+ name: reference.name,
233
+ svg,
234
+ });
235
+ }
236
+ iconAssetStore.needsRuntime = true;
237
+
238
+ return injectIconManifest(output, {
239
+ defaultLibrary: resolver.defaultLibrary,
240
+ icons: manifestIcons,
241
+ });
242
+ }
243
+
244
+ /**
245
+ * @param {unknown} iconLibraries
246
+ * @param {string} owner
247
+ * @returns {Map<string, import('@rocket/js/types.js').NormalizedIconLibraryConfig>}
248
+ */
249
+ export function normalizeIconLibrariesConfig(iconLibraries, owner) {
250
+ const normalized = new Map();
251
+ if (iconLibraries === undefined) {
252
+ return normalized;
253
+ }
254
+
255
+ const entries = iconLibraryEntries(iconLibraries, owner);
256
+ for (const [name, config] of entries) {
257
+ if (typeof name !== 'string' || name.trim() === '') {
258
+ throw new Error(`Invalid ${owner}: Icon Library names must be non-empty strings.`);
259
+ }
260
+ if (normalized.has(name)) {
261
+ throw new Error(`Invalid ${owner}: duplicate Icon Library "${name}".`);
262
+ }
263
+ normalized.set(name, {
264
+ sources: normalizeIconLibrarySources(config, `${owner} "${name}"`),
265
+ });
266
+ }
267
+ return normalized;
268
+ }
269
+
270
+ /**
271
+ * @param {unknown} iconReferences
272
+ * @param {string} owner
273
+ * @returns {import('@rocket/js/types.js').IconReferenceConfig[]}
274
+ */
275
+ export function normalizeIconReferencesConfig(iconReferences, owner) {
276
+ if (iconReferences === undefined) {
277
+ return [];
278
+ }
279
+ if (!Array.isArray(iconReferences)) {
280
+ throw new Error(`Invalid ${owner}: must be an array.`);
281
+ }
282
+
283
+ /** @type {import('@rocket/js/types.js').IconReferenceConfig[]} */
284
+ const normalized = [];
285
+ const seen = new Set();
286
+ for (const [index, iconReference] of iconReferences.entries()) {
287
+ const field = `${owner}[${index}]`;
288
+ if (!isPlainRecord(iconReference)) {
289
+ throw new Error(`Invalid ${field}: must be an object.`);
290
+ }
291
+ for (const key of Object.keys(iconReference)) {
292
+ if (!ICON_REFERENCE_CONFIG_FIELDS.has(key)) {
293
+ throw new Error(`Invalid ${field}.${key}: is not a known Icon Reference field.`);
294
+ }
295
+ }
296
+
297
+ const name = readNonEmptyString(iconReference.name, `${field}.name`).trim();
298
+ const library =
299
+ iconReference.library === undefined
300
+ ? undefined
301
+ : readNonEmptyString(iconReference.library, `${field}.library`).trim();
302
+ const dedupeKey = `${library || ''}:${name}`;
303
+ if (!seen.has(dedupeKey)) {
304
+ seen.add(dedupeKey);
305
+ normalized.push({
306
+ ...(library ? { library } : {}),
307
+ name,
308
+ });
309
+ }
310
+ }
311
+ return normalized;
312
+ }
313
+
314
+ /**
315
+ * @typedef {{ remaining?: number }} IconLoadingRegion
316
+ * @typedef {{
317
+ * start: number;
318
+ * end: number;
319
+ * html: string;
320
+ * renderedIcon: Awaited<ReturnType<typeof renderRocketIconHost>>;
321
+ * }} IconHostReplacement
322
+ */
323
+
324
+ /**
325
+ * @param {string} html
326
+ * @param {ReturnType<typeof createIconResolver>} resolver
327
+ * @returns {Promise<IconHostReplacement[]>}
328
+ */
329
+ async function finalizeRocketIconHosts(html, resolver) {
330
+ const document = parse5.parse(html, { sourceCodeLocationInfo: true });
331
+ /** @type {IconHostReplacement[]} */
332
+ const replacements = [];
333
+
334
+ /**
335
+ * @param {import('parse5').DefaultTreeAdapterTypes.Node} node
336
+ * @param {IconLoadingRegion | undefined} region
337
+ */
338
+ async function visitNode(node, region) {
339
+ if (!isElementNode(node)) {
340
+ await visitChildren(node, region);
341
+ return;
342
+ }
343
+
344
+ if (node.tagName === 'rocket-icon') {
345
+ const replacement = await finalizeRocketIconElement(html, node, resolver, region);
346
+ replacements.push(replacement);
347
+ return;
348
+ }
349
+
350
+ const childRegion = iconLoadingRegionForElement(node) || region;
351
+ await visitChildren(node, childRegion);
352
+ }
353
+
354
+ /**
355
+ * @param {import('parse5').DefaultTreeAdapterTypes.Node} node
356
+ * @param {IconLoadingRegion | undefined} region
357
+ */
358
+ async function visitChildren(node, region) {
359
+ if (!('childNodes' in node)) {
360
+ return;
361
+ }
362
+ for (const child of node.childNodes) {
363
+ await visitNode(child, region);
364
+ }
365
+ if (isTemplateElement(node)) {
366
+ await visitNode(node.content, region);
367
+ }
368
+ }
369
+
370
+ await visitNode(document, undefined);
371
+ replacements.sort((first, second) => first.start - second.start);
372
+ return replacements;
373
+ }
374
+
375
+ /**
376
+ * @param {string} html
377
+ * @param {import('parse5').DefaultTreeAdapterTypes.Element} node
378
+ * @param {ReturnType<typeof createIconResolver>} resolver
379
+ * @param {IconLoadingRegion | undefined} region
380
+ * @returns {Promise<IconHostReplacement>}
381
+ */
382
+ async function finalizeRocketIconElement(html, node, resolver, region) {
383
+ const location = node.sourceCodeLocation;
384
+ const startTag = location?.startTag;
385
+ if (!location || !startTag) {
386
+ throw new Error('rocket-icon host is missing a source location in rendered HTML.');
387
+ }
388
+ if (!location.endTag) {
389
+ throw new Error('rocket-icon hosts must use an explicit closing </rocket-icon> tag.');
390
+ }
391
+
392
+ const attributes = attributesForElement(node);
393
+ const effectiveIconLoading = effectiveLoadingForIcon(attributes, region);
394
+ const renderedIcon = await renderRocketIconHost(
395
+ html.slice(startTag.startOffset, startTag.endOffset),
396
+ attributes,
397
+ resolver,
398
+ effectiveIconLoading,
399
+ );
400
+
401
+ return {
402
+ start: location.startOffset,
403
+ end: location.endOffset,
404
+ html: renderedIcon.html,
405
+ renderedIcon,
406
+ };
407
+ }
408
+
409
+ /**
410
+ * @param {Map<string, string | true>} attributes
411
+ * @param {IconLoadingRegion | undefined} region
412
+ */
413
+ function effectiveLoadingForIcon(attributes, region) {
414
+ const iconLoading = iconLoadingForAttributes(attributes);
415
+ if (iconLoading !== 'auto') {
416
+ return iconLoading;
417
+ }
418
+ if (!region || region.remaining === undefined) {
419
+ return 'server';
420
+ }
421
+ if (region.remaining > 0) {
422
+ region.remaining -= 1;
423
+ return 'server';
424
+ }
425
+ return 'client';
426
+ }
427
+
428
+ /**
429
+ * @param {import('parse5').DefaultTreeAdapterTypes.Element} node
430
+ * @returns {IconLoadingRegion | undefined}
431
+ */
432
+ function iconLoadingRegionForElement(node) {
433
+ const attributes = attributesForElement(node);
434
+ if (!attributes.has('icon-loading-region')) {
435
+ return undefined;
436
+ }
437
+ const budgetAttribute = attributes.get('icon-server-budget');
438
+ if (budgetAttribute === undefined) {
439
+ return {};
440
+ }
441
+ if (typeof budgetAttribute !== 'string' || !/^\d+$/.test(budgetAttribute)) {
442
+ throw new Error(
443
+ `Invalid icon-server-budget ${JSON.stringify(
444
+ budgetAttribute === true ? '' : budgetAttribute,
445
+ )}. Expected a non-negative integer.`,
446
+ );
447
+ }
448
+ return { remaining: Number(budgetAttribute) };
449
+ }
450
+
451
+ /**
452
+ * @param {import('parse5').DefaultTreeAdapterTypes.Element} node
453
+ * @returns {Map<string, string | true>}
454
+ */
455
+ function attributesForElement(node) {
456
+ const attributes = new Map();
457
+ for (const attribute of node.attrs) {
458
+ attributes.set(attribute.name.toLowerCase(), attribute.value);
459
+ }
460
+ return attributes;
461
+ }
462
+
463
+ /**
464
+ * @param {import('parse5').DefaultTreeAdapterTypes.Node} node
465
+ * @returns {node is import('parse5').DefaultTreeAdapterTypes.Element}
466
+ */
467
+ function isElementNode(node) {
468
+ return 'tagName' in node && typeof node.tagName === 'string';
469
+ }
470
+
471
+ /**
472
+ * @param {import('parse5').DefaultTreeAdapterTypes.Node} node
473
+ * @returns {node is import('parse5').DefaultTreeAdapterTypes.Template}
474
+ */
475
+ function isTemplateElement(node) {
476
+ return isElementNode(node) && node.tagName === 'template' && 'content' in node;
477
+ }
478
+
479
+ /**
480
+ * @param {string} html
481
+ * @param {IconHostReplacement[]} replacements
482
+ */
483
+ function applyHtmlReplacements(html, replacements) {
484
+ let output = '';
485
+ let cursor = 0;
486
+ for (const replacement of replacements) {
487
+ output += html.slice(cursor, replacement.start);
488
+ output += replacement.html;
489
+ cursor = replacement.end;
490
+ }
491
+ return output + html.slice(cursor);
492
+ }
493
+
494
+ /**
495
+ * @param {string} startTag
496
+ * @param {Map<string, string | true>} attributes
497
+ * @param {ReturnType<typeof createIconResolver>} resolver
498
+ * @param {string} [effectiveIconLoading]
499
+ */
500
+ async function renderRocketIconHost(startTag, attributes, resolver, effectiveIconLoading) {
501
+ const name = stringAttribute(attributes, 'name')?.trim();
502
+ if (!name) {
503
+ throw new Error('rocket-icon requires a non-empty name attribute.');
504
+ }
505
+
506
+ const iconLoading = iconLoadingForAttributes(attributes);
507
+ if (!ICON_LOADING_VALUES.has(iconLoading)) {
508
+ throw new Error(
509
+ `Invalid rocket-icon icon-loading ${JSON.stringify(
510
+ iconLoading,
511
+ )}. Expected "auto", "server", or "client".`,
512
+ );
513
+ }
514
+
515
+ const libraryAttribute = stringAttribute(attributes, 'library');
516
+ const library = resolver.resolveLibrary(libraryAttribute, name);
517
+ const resolvedIconLoading = effectiveIconLoading || iconLoading;
518
+ const svg = resolvedIconLoading === 'client' ? '' : await resolver.loadIcon(library, name);
519
+
520
+ return {
521
+ html: `${startTag}${shadowTemplate(svg)}</rocket-icon>`,
522
+ iconLoading: resolvedIconLoading,
523
+ library,
524
+ name,
525
+ svg,
526
+ };
527
+ }
528
+
529
+ /**
530
+ * @param {{
531
+ * layoutIconLibraries?: unknown;
532
+ * layoutDefaultIconLibrary?: string;
533
+ * projectIconLibraries?: unknown;
534
+ * projectDefaultIconLibrary?: string;
535
+ * }} options
536
+ */
537
+ function createIconResolver({
538
+ layoutIconLibraries,
539
+ layoutDefaultIconLibrary,
540
+ projectIconLibraries,
541
+ projectDefaultIconLibrary,
542
+ }) {
543
+ const layoutLibraries = normalizeIconLibrariesConfig(
544
+ layoutIconLibraries,
545
+ 'Layout Icon Libraries',
546
+ );
547
+ const projectLibraries = normalizeIconLibrariesConfig(
548
+ projectIconLibraries,
549
+ 'Project Icon Library Configuration',
550
+ );
551
+ for (const name of projectLibraries.keys()) {
552
+ if (layoutLibraries.has(name)) {
553
+ throw new Error(
554
+ `Icon Library "${name}" is supplied by both project configuration and the active layout.`,
555
+ );
556
+ }
557
+ }
558
+
559
+ const libraries = new Map([...layoutLibraries, ...projectLibraries]);
560
+ const defaultLibrary = selectDefaultIconLibrary({
561
+ libraries,
562
+ layoutDefaultIconLibrary,
563
+ projectDefaultIconLibrary,
564
+ });
565
+
566
+ return {
567
+ defaultLibrary,
568
+ libraries,
569
+ /**
570
+ * @param {string | undefined} explicitLibrary
571
+ * @param {string} iconName
572
+ */
573
+ resolveLibrary(explicitLibrary, iconName) {
574
+ if (explicitLibrary !== undefined) {
575
+ const trimmed = explicitLibrary.trim();
576
+ if (!trimmed) {
577
+ throw new Error(`rocket-icon library must be non-empty when provided.`);
578
+ }
579
+ if (!libraries.has(trimmed)) {
580
+ throw new Error(`Unknown Icon Library "${trimmed}" for rocket-icon "${iconName}".`);
581
+ }
582
+ return trimmed;
583
+ }
584
+ if (defaultLibrary) {
585
+ return defaultLibrary;
586
+ }
587
+ if (libraries.size === 0) {
588
+ throw new Error(
589
+ `rocket-icon "${iconName}" has no Icon Library. Configure iconLibraries or provide library.`,
590
+ );
591
+ }
592
+ throw new Error(
593
+ `Ambiguous unqualified rocket-icon "${iconName}". Provide library or configure defaultIconLibrary.`,
594
+ );
595
+ },
596
+ /**
597
+ * @param {string} library
598
+ * @param {string} iconName
599
+ */
600
+ async loadIcon(library, iconName) {
601
+ const config = libraries.get(library);
602
+ if (!config) {
603
+ throw new Error(`Unknown Icon Library "${library}" for rocket-icon "${iconName}".`);
604
+ }
605
+ const icons = await indexIconLibrary(library, config);
606
+ const svg = icons.get(iconName);
607
+ if (svg === undefined) {
608
+ throw new Error(`Icon "${iconName}" was not found in Icon Library "${library}".`);
609
+ }
610
+ return svg;
611
+ },
612
+ };
613
+ }
614
+
615
+ /**
616
+ * @param {object} options
617
+ * @param {Map<string, import('@rocket/js/types.js').NormalizedIconLibraryConfig>} options.libraries
618
+ * @param {string} [options.layoutDefaultIconLibrary]
619
+ * @param {string} [options.projectDefaultIconLibrary]
620
+ */
621
+ function selectDefaultIconLibrary({
622
+ libraries,
623
+ layoutDefaultIconLibrary,
624
+ projectDefaultIconLibrary,
625
+ }) {
626
+ const configuredDefault = projectDefaultIconLibrary || layoutDefaultIconLibrary;
627
+ if (configuredDefault !== undefined) {
628
+ const defaultName = configuredDefault.trim();
629
+ if (!defaultName) {
630
+ throw new Error('defaultIconLibrary must be a non-empty string.');
631
+ }
632
+ if (!libraries.has(defaultName)) {
633
+ throw new Error(`Default Icon Library "${defaultName}" is not configured.`);
634
+ }
635
+ return defaultName;
636
+ }
637
+ if (libraries.size === 1) {
638
+ return [...libraries.keys()][0];
639
+ }
640
+ return undefined;
641
+ }
642
+
643
+ /**
644
+ * @param {string} libraryName
645
+ * @param {import('@rocket/js/types.js').NormalizedIconLibraryConfig} config
646
+ */
647
+ async function indexIconLibrary(libraryName, config) {
648
+ const cacheKey = `${libraryName}:${JSON.stringify(config.sources)}`;
649
+ let cached = libraryIndexCache.get(cacheKey);
650
+ if (!cached) {
651
+ cached = readIconLibrary(libraryName, config);
652
+ libraryIndexCache.set(cacheKey, cached);
653
+ }
654
+ return cached;
655
+ }
656
+
657
+ /**
658
+ * @param {string} libraryName
659
+ * @param {import('@rocket/js/types.js').NormalizedIconLibraryConfig} config
660
+ */
661
+ async function readIconLibrary(libraryName, config) {
662
+ /** @type {Map<string, string>} */
663
+ const icons = new Map();
664
+ for (const source of config.sources) {
665
+ const files = await iconSourceFiles(source);
666
+ if (files.length === 0) {
667
+ throw new Error(
668
+ `Icon Library "${libraryName}" source ${JSON.stringify(source.files)} matched no SVG files.`,
669
+ );
670
+ }
671
+ for (const file of files) {
672
+ const iconName = path.basename(file, '.svg');
673
+ if (icons.has(iconName)) {
674
+ throw new Error(`Duplicate Icon Name "${iconName}" in Icon Library "${libraryName}".`);
675
+ }
676
+ icons.set(iconName, await readFile(file, 'utf8'));
677
+ }
678
+ }
679
+ return icons;
680
+ }
681
+
682
+ /**
683
+ * @param {import('@rocket/js/types.js').IconLibrarySource} source
684
+ * @returns {Promise<string[]>}
685
+ */
686
+ async function iconSourceFiles(source) {
687
+ const pattern =
688
+ source.type === 'package'
689
+ ? path.join(packageRoot(source.packageName), source.files)
690
+ : path.resolve(source.files);
691
+ const files = [];
692
+ for await (const file of glob(pattern)) {
693
+ if (file.endsWith('.svg')) {
694
+ files.push(path.resolve(file));
695
+ }
696
+ }
697
+ files.sort();
698
+ return files;
699
+ }
700
+
701
+ /**
702
+ * @param {string} packageName
703
+ */
704
+ function packageRoot(packageName) {
705
+ try {
706
+ return path.dirname(require.resolve(`${packageName}/package.json`, { paths: [process.cwd()] }));
707
+ } catch (error) {
708
+ throw new Error(`Could not resolve Icon package ${JSON.stringify(packageName)}.`, {
709
+ cause: error,
710
+ });
711
+ }
712
+ }
713
+
714
+ /**
715
+ * @param {unknown} iconLibraries
716
+ * @param {string} owner
717
+ * @returns {[string, unknown][]}
718
+ */
719
+ function iconLibraryEntries(iconLibraries, owner) {
720
+ if (iconLibraries instanceof Map) {
721
+ return [...iconLibraries.entries()];
722
+ }
723
+ if (!isPlainRecord(iconLibraries)) {
724
+ throw new Error(`Invalid ${owner}: iconLibraries must be an object.`);
725
+ }
726
+ return Object.entries(iconLibraries);
727
+ }
728
+
729
+ /**
730
+ * @param {unknown} config
731
+ * @param {string} field
732
+ * @returns {import('@rocket/js/types.js').IconLibrarySource[]}
733
+ */
734
+ function normalizeIconLibrarySources(config, field) {
735
+ if (isIconLibrarySource(config)) {
736
+ return [normalizeIconLibrarySource(config, field)];
737
+ }
738
+ if (isPlainRecord(config) && Object.prototype.hasOwnProperty.call(config, 'type')) {
739
+ return [normalizeIconLibrarySource(config, field)];
740
+ }
741
+ if (Array.isArray(config)) {
742
+ return config.map(source => normalizeIconLibrarySource(source, field));
743
+ }
744
+ if (isPlainRecord(config) && Object.prototype.hasOwnProperty.call(config, 'sources')) {
745
+ return normalizeIconLibrarySources(config.sources, `${field}.sources`);
746
+ }
747
+ throw new Error(
748
+ `Invalid ${field}: expected an Icon Library Source, an array of sources, or { sources } configuration.`,
749
+ );
750
+ }
751
+
752
+ /**
753
+ * @param {unknown} source
754
+ * @param {string} field
755
+ * @returns {import('@rocket/js/types.js').IconLibrarySource}
756
+ */
757
+ function normalizeIconLibrarySource(source, field) {
758
+ if (!isPlainRecord(source)) {
759
+ throw new Error(`Invalid ${field}: Icon Library Sources must be objects.`);
760
+ }
761
+ if (source.type === 'package') {
762
+ return iconsFromPackage(
763
+ readNonEmptyString(source.packageName, `${field}.packageName`),
764
+ readNonEmptyString(source.files, `${field}.files`),
765
+ );
766
+ }
767
+ if (source.type === 'path') {
768
+ return iconsFromPath(readNonEmptyString(source.files, `${field}.files`));
769
+ }
770
+ throw new Error(`Invalid ${field}: Icon Library Source type must be "package" or "path".`);
771
+ }
772
+
773
+ /**
774
+ * @param {unknown} value
775
+ * @returns {value is import('@rocket/js/types.js').IconLibrarySource}
776
+ */
777
+ function isIconLibrarySource(value) {
778
+ return isPlainRecord(value) && (value.type === 'package' || value.type === 'path');
779
+ }
780
+
781
+ /**
782
+ * @param {string} svg
783
+ */
784
+ function shadowTemplate(svg) {
785
+ return `<template shadowrootmode="open"><style>${SHADOW_ICON_STYLE}</style><span part="icon">${svg}</span></template>`;
786
+ }
787
+
788
+ /**
789
+ * @param {string} library
790
+ * @param {string} name
791
+ */
792
+ function iconReferenceKey(library, name) {
793
+ return `${library}:${name}`;
794
+ }
795
+
796
+ /**
797
+ * @param {{ library: string; name: string; svg: string }} icon
798
+ */
799
+ function iconAssetUrl({ library, name, svg }) {
800
+ const hash = createHash('sha256').update(svg).digest('hex').slice(0, 12);
801
+ return `${ROCKET_ICON_ASSET_PREFIX}${sanitizePathSegment(library)}/${sanitizePathSegment(
802
+ name,
803
+ )}.${hash}.svg`;
804
+ }
805
+
806
+ /**
807
+ * @param {string} html
808
+ * @param {{ defaultLibrary?: string; icons: Record<string, string> }} manifest
809
+ */
810
+ function injectIconManifest(html, manifest) {
811
+ const manifestJson = JSON.stringify(
812
+ manifest.defaultLibrary
813
+ ? { defaultLibrary: manifest.defaultLibrary, icons: manifest.icons }
814
+ : { icons: manifest.icons },
815
+ ).replaceAll('<', '\\u003C');
816
+ const runtimeHtml =
817
+ `<script type="application/json" data-rocket-icon-manifest>${manifestJson}</script>` +
818
+ `<script type="module" data-rocket-icon-runtime>import '${ROCKET_ICON_DEFINE_MODULE_PATH}';</script>`;
819
+ const headClose = html.toLowerCase().indexOf('</head>');
820
+ if (headClose !== -1) {
821
+ return html.slice(0, headClose) + runtimeHtml + html.slice(headClose);
822
+ }
823
+ return html + runtimeHtml;
824
+ }
825
+
826
+ /**
827
+ * @param {{ _hydrationScript?: string; _hasBrowserLoadedComponents?: boolean } | undefined} pageData
828
+ */
829
+ function pageNeedsDeferredIconRuntime(pageData) {
830
+ return Boolean(pageData?._hasBrowserLoadedComponents || pageData?._hydrationScript);
831
+ }
832
+
833
+ /**
834
+ * @param {string} value
835
+ */
836
+ function sanitizePathSegment(value) {
837
+ const sanitized = value
838
+ .trim()
839
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
840
+ .replace(/^-+|-+$/g, '');
841
+ return sanitized || 'icon';
842
+ }
843
+
844
+ /**
845
+ * @param {Map<string, string | true>} attributes
846
+ * @param {string} name
847
+ */
848
+ function stringAttribute(attributes, name) {
849
+ const value = attributes.get(name);
850
+ return typeof value === 'string' ? value : undefined;
851
+ }
852
+
853
+ /**
854
+ * @param {Map<string, string | true>} attributes
855
+ */
856
+ function iconLoadingForAttributes(attributes) {
857
+ return attributes.has('icon-loading')
858
+ ? stringAttribute(attributes, 'icon-loading') || ''
859
+ : 'auto';
860
+ }
861
+
862
+ /**
863
+ * @param {unknown} value
864
+ * @param {string} field
865
+ */
866
+ function readNonEmptyString(value, field) {
867
+ if (typeof value !== 'string' || value.trim() === '') {
868
+ throw new Error(`${field} must be a non-empty string.`);
869
+ }
870
+ return value;
871
+ }
872
+
873
+ /**
874
+ * @param {unknown} value
875
+ * @returns {value is Record<string, unknown>}
876
+ */
877
+ function isPlainRecord(value) {
878
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
879
+ return false;
880
+ }
881
+ const prototype = Object.getPrototypeOf(value);
882
+ return prototype === Object.prototype || prototype === null;
883
+ }