@rocket/js 0.0.0 → 0.1.1

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