@scaffold-ui-kit/preview 0.0.0-stage → 2.3.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 (350) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1868 -2
  3. package/application/host/bootstrap.ts +43 -0
  4. package/application/host/main.ts +31 -0
  5. package/application/host/shell-host.ts +47 -0
  6. package/application/index.html +12 -0
  7. package/application/lib/base-url.ts +15 -0
  8. package/application/lib/no-code-block.ts +7 -0
  9. package/application/lib/no-setup.ts +3 -0
  10. package/application/lib/no-views.ts +6 -0
  11. package/application/preview/code/code.html +12 -0
  12. package/application/preview/code/main.ts +89 -0
  13. package/application/preview/globals.ts +254 -0
  14. package/application/preview/html/main.ts +93 -0
  15. package/application/preview/html/preview.html +12 -0
  16. package/application/preview/react/main.tsx +146 -0
  17. package/application/preview/react/preview.html +12 -0
  18. package/application/preview/svelte/Preview.svelte +104 -0
  19. package/application/preview/svelte/main.ts +13 -0
  20. package/application/preview/svelte/preview.html +12 -0
  21. package/application/preview/svelte/story-props.ts +172 -0
  22. package/application/preview/view/main.ts +124 -0
  23. package/application/preview/view/view.html +12 -0
  24. package/application/preview/vue/main.ts +145 -0
  25. package/application/preview/vue/preview.html +12 -0
  26. package/application/shells/charcell/components/Application/index.css +37 -0
  27. package/application/shells/charcell/components/Application/index.tsx +65 -0
  28. package/application/shells/charcell/components/Bar/index.css +68 -0
  29. package/application/shells/charcell/components/Bar/index.tsx +60 -0
  30. package/application/shells/charcell/components/CodePane/index.css +60 -0
  31. package/application/shells/charcell/components/CodePane/index.tsx +113 -0
  32. package/application/shells/charcell/components/ComponentStage/index.css +26 -0
  33. package/application/shells/charcell/components/ComponentStage/index.tsx +110 -0
  34. package/application/shells/charcell/components/Control/index.css +20 -0
  35. package/application/shells/charcell/components/Control/index.tsx +118 -0
  36. package/application/shells/charcell/components/CopyLink/index.tsx +35 -0
  37. package/application/shells/charcell/components/Description/index.css +6 -0
  38. package/application/shells/charcell/components/Description/index.tsx +24 -0
  39. package/application/shells/charcell/components/EditorAddDialog/index.css +9 -0
  40. package/application/shells/charcell/components/EditorAddDialog/index.tsx +158 -0
  41. package/application/shells/charcell/components/EditorPanel/index.css +48 -0
  42. package/application/shells/charcell/components/EditorPanel/index.tsx +496 -0
  43. package/application/shells/charcell/components/EventsRow/index.css +42 -0
  44. package/application/shells/charcell/components/EventsRow/index.tsx +68 -0
  45. package/application/shells/charcell/components/HeadingRow/index.css +11 -0
  46. package/application/shells/charcell/components/HeadingRow/index.tsx +16 -0
  47. package/application/shells/charcell/components/Hint/index.css +16 -0
  48. package/application/shells/charcell/components/Hint/index.tsx +81 -0
  49. package/application/shells/charcell/components/HtmlWarnings/index.tsx +45 -0
  50. package/application/shells/charcell/components/KeyHints/index.css +27 -0
  51. package/application/shells/charcell/components/KeyHints/index.tsx +106 -0
  52. package/application/shells/charcell/components/KitCodeBlock/index.css +55 -0
  53. package/application/shells/charcell/components/KitCodeBlock/index.tsx +18 -0
  54. package/application/shells/charcell/components/Markdown/index.tsx +16 -0
  55. package/application/shells/charcell/components/Navigation/index.css +13 -0
  56. package/application/shells/charcell/components/Navigation/index.tsx +98 -0
  57. package/application/shells/charcell/components/NavigationSplit/index.css +16 -0
  58. package/application/shells/charcell/components/NavigationSplit/index.tsx +82 -0
  59. package/application/shells/charcell/components/OptionControls/index.tsx +93 -0
  60. package/application/shells/charcell/components/PaneStatus/index.css +7 -0
  61. package/application/shells/charcell/components/PaneStatus/index.tsx +47 -0
  62. package/application/shells/charcell/components/PaneWidth/index.tsx +45 -0
  63. package/application/shells/charcell/components/PreviewGrid/index.css +39 -0
  64. package/application/shells/charcell/components/PreviewGrid/index.tsx +344 -0
  65. package/application/shells/charcell/components/PreviewPane/index.css +127 -0
  66. package/application/shells/charcell/components/PreviewPane/index.tsx +182 -0
  67. package/application/shells/charcell/components/PromptBar/index.tsx +133 -0
  68. package/application/shells/charcell/components/ScenarioRow/index.css +36 -0
  69. package/application/shells/charcell/components/ScenarioRow/index.tsx +71 -0
  70. package/application/shells/charcell/components/Select/index.tsx +48 -0
  71. package/application/shells/charcell/components/SlotOutlet/index.css +7 -0
  72. package/application/shells/charcell/components/SlotOutlet/index.tsx +25 -0
  73. package/application/shells/charcell/components/StageBar/index.css +7 -0
  74. package/application/shells/charcell/components/StageBar/index.tsx +109 -0
  75. package/application/shells/charcell/components/StageSplit/index.css +11 -0
  76. package/application/shells/charcell/components/StageSplit/index.tsx +99 -0
  77. package/application/shells/charcell/components/StatusBar/index.tsx +30 -0
  78. package/application/shells/charcell/components/ViewControls/index.css +15 -0
  79. package/application/shells/charcell/components/ViewControls/index.tsx +58 -0
  80. package/application/shells/charcell/components/ViewHeading/index.css +13 -0
  81. package/application/shells/charcell/components/ViewHeading/index.tsx +8 -0
  82. package/application/shells/charcell/components/ViewStage/index.css +9 -0
  83. package/application/shells/charcell/components/ViewStage/index.tsx +25 -0
  84. package/application/shells/charcell/components/WrapToggle/index.tsx +27 -0
  85. package/application/shells/charcell/configuration.ts +45 -0
  86. package/application/shells/charcell/controller-context.ts +27 -0
  87. package/application/shells/charcell/fit-labels.ts +42 -0
  88. package/application/shells/charcell/html-warnings.ts +18 -0
  89. package/application/shells/charcell/index.tsx +82 -0
  90. package/application/shells/charcell/page-loading.html +57 -0
  91. package/application/shells/charcell/placement.ts +24 -0
  92. package/application/shells/charcell/shell-options.ts +23 -0
  93. package/application/shells/charcell/shell.css +37 -0
  94. package/application/shells/charcell/shell.tsx +25 -0
  95. package/application/shells/charcell/split-reset.ts +47 -0
  96. package/application/shells/charcell/stacked-rows.ts +117 -0
  97. package/application/shells/charcell/stage-toggles.ts +93 -0
  98. package/application/types/manifest.d.ts +6 -0
  99. package/application/types/preview-configuration.d.ts +10 -0
  100. package/application/types/preview-shell.d.ts +7 -0
  101. package/application/types/react-registry.d.ts +15 -0
  102. package/application/types/vue-registry.d.ts +6 -0
  103. package/dist/build.d.ts +24 -0
  104. package/dist/build.js +34 -0
  105. package/dist/check.d.ts +48 -0
  106. package/dist/check.js +781 -0
  107. package/dist/cli.d.ts +2 -0
  108. package/dist/cli.js +88 -0
  109. package/dist/context.d.ts +179 -0
  110. package/dist/context.js +783 -0
  111. package/dist/controller/catalog.d.ts +339 -0
  112. package/dist/controller/catalog.js +367 -0
  113. package/dist/controller/create-preview-controller.d.ts +532 -0
  114. package/dist/controller/create-preview-controller.js +1707 -0
  115. package/dist/controller/editor-tree.d.ts +161 -0
  116. package/dist/controller/editor-tree.js +373 -0
  117. package/dist/controller/html-source.d.ts +62 -0
  118. package/dist/controller/html-source.js +61 -0
  119. package/dist/controller/index.d.ts +9 -0
  120. package/dist/controller/index.js +12 -0
  121. package/dist/controller/persistence.d.ts +191 -0
  122. package/dist/controller/persistence.js +315 -0
  123. package/dist/controller/regeneration.d.ts +30 -0
  124. package/dist/controller/regeneration.js +27 -0
  125. package/dist/controller/source.d.ts +80 -0
  126. package/dist/controller/source.js +236 -0
  127. package/dist/controller/store.d.ts +27 -0
  128. package/dist/controller/store.js +40 -0
  129. package/dist/declarations/application/shells/charcell/components/Application/index.d.ts +2 -0
  130. package/dist/declarations/application/shells/charcell/components/Bar/index.d.ts +11 -0
  131. package/dist/declarations/application/shells/charcell/components/CodePane/index.d.ts +5 -0
  132. package/dist/declarations/application/shells/charcell/components/ComponentStage/index.d.ts +2 -0
  133. package/dist/declarations/application/shells/charcell/components/Control/index.d.ts +9 -0
  134. package/dist/declarations/application/shells/charcell/components/CopyLink/index.d.ts +1 -0
  135. package/dist/declarations/application/shells/charcell/components/Description/index.d.ts +5 -0
  136. package/dist/declarations/application/shells/charcell/components/EditorAddDialog/index.d.ts +6 -0
  137. package/dist/declarations/application/shells/charcell/components/EditorPanel/index.d.ts +2 -0
  138. package/dist/declarations/application/shells/charcell/components/EventsRow/index.d.ts +4 -0
  139. package/dist/declarations/application/shells/charcell/components/HeadingRow/index.d.ts +6 -0
  140. package/dist/declarations/application/shells/charcell/components/Hint/index.d.ts +11 -0
  141. package/dist/declarations/application/shells/charcell/components/HtmlWarnings/index.d.ts +5 -0
  142. package/dist/declarations/application/shells/charcell/components/KeyHints/index.d.ts +2 -0
  143. package/dist/declarations/application/shells/charcell/components/KitCodeBlock/index.d.ts +5 -0
  144. package/dist/declarations/application/shells/charcell/components/Markdown/index.d.ts +3 -0
  145. package/dist/declarations/application/shells/charcell/components/Navigation/index.d.ts +8 -0
  146. package/dist/declarations/application/shells/charcell/components/NavigationSplit/index.d.ts +7 -0
  147. package/dist/declarations/application/shells/charcell/components/OptionControls/index.d.ts +7 -0
  148. package/dist/declarations/application/shells/charcell/components/PaneStatus/index.d.ts +2 -0
  149. package/dist/declarations/application/shells/charcell/components/PaneWidth/index.d.ts +4 -0
  150. package/dist/declarations/application/shells/charcell/components/PreviewGrid/index.d.ts +7 -0
  151. package/dist/declarations/application/shells/charcell/components/PreviewPane/index.d.ts +9 -0
  152. package/dist/declarations/application/shells/charcell/components/PromptBar/index.d.ts +1 -0
  153. package/dist/declarations/application/shells/charcell/components/ScenarioRow/index.d.ts +2 -0
  154. package/dist/declarations/application/shells/charcell/components/Select/index.d.ts +16 -0
  155. package/dist/declarations/application/shells/charcell/components/SlotOutlet/index.d.ts +5 -0
  156. package/dist/declarations/application/shells/charcell/components/StageBar/index.d.ts +2 -0
  157. package/dist/declarations/application/shells/charcell/components/StageSplit/index.d.ts +17 -0
  158. package/dist/declarations/application/shells/charcell/components/StatusBar/index.d.ts +1 -0
  159. package/dist/declarations/application/shells/charcell/components/ViewControls/index.d.ts +9 -0
  160. package/dist/declarations/application/shells/charcell/components/ViewHeading/index.d.ts +4 -0
  161. package/dist/declarations/application/shells/charcell/components/ViewStage/index.d.ts +4 -0
  162. package/dist/declarations/application/shells/charcell/components/WrapToggle/index.d.ts +1 -0
  163. package/dist/declarations/application/shells/charcell/configuration.d.ts +31 -0
  164. package/dist/declarations/application/shells/charcell/controller-context.d.ts +9 -0
  165. package/dist/declarations/application/shells/charcell/fit-labels.d.ts +18 -0
  166. package/dist/declarations/application/shells/charcell/html-warnings.d.ts +5 -0
  167. package/dist/declarations/application/shells/charcell/index.d.ts +39 -0
  168. package/dist/declarations/application/shells/charcell/placement.d.ts +7 -0
  169. package/dist/declarations/application/shells/charcell/shell-options.d.ts +15 -0
  170. package/dist/declarations/application/shells/charcell/shell.d.ts +9 -0
  171. package/dist/declarations/application/shells/charcell/split-reset.d.ts +30 -0
  172. package/dist/declarations/application/shells/charcell/stacked-rows.d.ts +58 -0
  173. package/dist/declarations/application/shells/charcell/stage-toggles.d.ts +28 -0
  174. package/dist/declarations/src/controller/catalog.d.ts +339 -0
  175. package/dist/declarations/src/controller/create-preview-controller.d.ts +532 -0
  176. package/dist/declarations/src/controller/editor-tree.d.ts +161 -0
  177. package/dist/declarations/src/controller/html-source.d.ts +62 -0
  178. package/dist/declarations/src/controller/index.d.ts +9 -0
  179. package/dist/declarations/src/controller/persistence.d.ts +191 -0
  180. package/dist/declarations/src/controller/regeneration.d.ts +30 -0
  181. package/dist/declarations/src/controller/source.d.ts +80 -0
  182. package/dist/declarations/src/controller/store.d.ts +27 -0
  183. package/dist/declarations/src/emit/common.d.ts +103 -0
  184. package/dist/declarations/src/emit/manifest.d.ts +138 -0
  185. package/dist/declarations/src/emit/source.d.ts +87 -0
  186. package/dist/declarations/src/lib/jsx-roots.d.ts +22 -0
  187. package/dist/declarations/src/lib/module-of-source.d.ts +18 -0
  188. package/dist/declarations/src/lib/naming.d.ts +29 -0
  189. package/dist/declarations/src/lib/react-spelling.d.ts +68 -0
  190. package/dist/declarations/src/lib/scenario-story.d.ts +41 -0
  191. package/dist/declarations/src/lib/slot-fill.d.ts +15 -0
  192. package/dist/declarations/src/lib/types.d.ts +741 -0
  193. package/dist/declarations/src/options.d.ts +224 -0
  194. package/dist/declarations/src/protocol.d.ts +541 -0
  195. package/dist/declarations/src/react-view.d.ts +22 -0
  196. package/dist/declarations/src/react.d.ts +17 -0
  197. package/dist/declarations/src/shell.d.ts +99 -0
  198. package/dist/declarations/src/views.d.ts +152 -0
  199. package/dist/development-server.d.ts +121 -0
  200. package/dist/development-server.js +331 -0
  201. package/dist/emit/common.d.ts +103 -0
  202. package/dist/emit/common.js +170 -0
  203. package/dist/emit/html-source.d.ts +46 -0
  204. package/dist/emit/html-source.js +322 -0
  205. package/dist/emit/manifest.d.ts +138 -0
  206. package/dist/emit/manifest.js +74 -0
  207. package/dist/emit/react.d.ts +16 -0
  208. package/dist/emit/react.js +210 -0
  209. package/dist/emit/scratch.d.ts +40 -0
  210. package/dist/emit/scratch.js +116 -0
  211. package/dist/emit/source.d.ts +87 -0
  212. package/dist/emit/source.js +789 -0
  213. package/dist/emit/svelte.d.ts +9 -0
  214. package/dist/emit/svelte.js +177 -0
  215. package/dist/emit/vue.d.ts +2 -0
  216. package/dist/emit/vue.js +152 -0
  217. package/dist/enumerate.d.ts +2 -0
  218. package/dist/enumerate.js +162 -0
  219. package/dist/generate.d.ts +43 -0
  220. package/dist/generate.js +302 -0
  221. package/dist/html-stylesheet.d.ts +43 -0
  222. package/dist/html-stylesheet.js +145 -0
  223. package/dist/index.d.ts +18 -0
  224. package/dist/index.js +21 -0
  225. package/dist/landing.d.ts +16 -0
  226. package/dist/landing.js +45 -0
  227. package/dist/lib/exported-types.d.ts +42 -0
  228. package/dist/lib/exported-types.js +86 -0
  229. package/dist/lib/html-attributes.d.ts +2 -0
  230. package/dist/lib/html-attributes.js +111 -0
  231. package/dist/lib/html-warnings.d.ts +19 -0
  232. package/dist/lib/html-warnings.js +79 -0
  233. package/dist/lib/jsx-roots.d.ts +22 -0
  234. package/dist/lib/jsx-roots.js +243 -0
  235. package/dist/lib/module-of-source.d.ts +18 -0
  236. package/dist/lib/module-of-source.js +24 -0
  237. package/dist/lib/naming.d.ts +29 -0
  238. package/dist/lib/naming.js +56 -0
  239. package/dist/lib/parse-type.d.ts +32 -0
  240. package/dist/lib/parse-type.js +103 -0
  241. package/dist/lib/react-spelling.d.ts +68 -0
  242. package/dist/lib/react-spelling.js +197 -0
  243. package/dist/lib/scenario-story.d.ts +41 -0
  244. package/dist/lib/scenario-story.js +84 -0
  245. package/dist/lib/slot-fill.d.ts +15 -0
  246. package/dist/lib/slot-fill.js +92 -0
  247. package/dist/lib/template-files.d.ts +14 -0
  248. package/dist/lib/template-files.js +25 -0
  249. package/dist/lib/types.d.ts +741 -0
  250. package/dist/lib/types.js +13 -0
  251. package/dist/lib/void-elements.d.ts +7 -0
  252. package/dist/lib/void-elements.js +13 -0
  253. package/dist/load.d.ts +37 -0
  254. package/dist/load.js +82 -0
  255. package/dist/model.d.ts +44 -0
  256. package/dist/model.js +464 -0
  257. package/dist/options.d.ts +224 -0
  258. package/dist/options.js +223 -0
  259. package/dist/prop-types.d.ts +26 -0
  260. package/dist/prop-types.js +102 -0
  261. package/dist/props-from.d.ts +84 -0
  262. package/dist/props-from.js +510 -0
  263. package/dist/protocol.d.ts +541 -0
  264. package/dist/protocol.js +819 -0
  265. package/dist/react-view.d.ts +22 -0
  266. package/dist/react-view.js +36 -0
  267. package/dist/react.d.ts +17 -0
  268. package/dist/react.js +23 -0
  269. package/dist/sass-importers.d.ts +26 -0
  270. package/dist/sass-importers.js +32 -0
  271. package/dist/shell-acceptance.d.ts +21 -0
  272. package/dist/shell-acceptance.js +37 -0
  273. package/dist/shell-page.d.ts +36 -0
  274. package/dist/shell-page.js +90 -0
  275. package/dist/shell-peers.d.ts +38 -0
  276. package/dist/shell-peers.js +61 -0
  277. package/dist/shell.d.ts +99 -0
  278. package/dist/shell.js +52 -0
  279. package/dist/shells/charcell/page.d.ts +8 -0
  280. package/dist/shells/charcell/page.js +69 -0
  281. package/dist/story-source.d.ts +113 -0
  282. package/dist/story-source.js +271 -0
  283. package/dist/story.d.ts +5 -0
  284. package/dist/story.js +28 -0
  285. package/dist/testing.d.ts +218 -0
  286. package/dist/testing.js +284 -0
  287. package/dist/views.d.ts +152 -0
  288. package/dist/views.js +7 -0
  289. package/dist/vite-configuration.d.ts +114 -0
  290. package/dist/vite-configuration.js +578 -0
  291. package/package.json +123 -3
  292. package/src/build.ts +51 -0
  293. package/src/check.ts +1062 -0
  294. package/src/cli.ts +123 -0
  295. package/src/context.ts +1168 -0
  296. package/src/controller/catalog.ts +719 -0
  297. package/src/controller/create-preview-controller.ts +2683 -0
  298. package/src/controller/editor-tree.ts +530 -0
  299. package/src/controller/html-source.ts +114 -0
  300. package/src/controller/index.ts +105 -0
  301. package/src/controller/persistence.ts +403 -0
  302. package/src/controller/regeneration.ts +55 -0
  303. package/src/controller/source.ts +333 -0
  304. package/src/controller/store.ts +55 -0
  305. package/src/development-server.ts +442 -0
  306. package/src/emit/common.ts +201 -0
  307. package/src/emit/html-source.ts +402 -0
  308. package/src/emit/manifest.ts +241 -0
  309. package/src/emit/react.ts +263 -0
  310. package/src/emit/scratch.ts +175 -0
  311. package/src/emit/source.ts +1094 -0
  312. package/src/emit/svelte.ts +230 -0
  313. package/src/emit/vue.ts +194 -0
  314. package/src/enumerate.ts +220 -0
  315. package/src/generate.ts +443 -0
  316. package/src/html-stylesheet.ts +173 -0
  317. package/src/index.ts +60 -0
  318. package/src/landing.ts +58 -0
  319. package/src/lib/exported-types.ts +114 -0
  320. package/src/lib/html-attributes.ts +113 -0
  321. package/src/lib/html-warnings.ts +81 -0
  322. package/src/lib/jsx-roots.ts +238 -0
  323. package/src/lib/module-of-source.ts +26 -0
  324. package/src/lib/naming.ts +65 -0
  325. package/src/lib/parse-type.ts +143 -0
  326. package/src/lib/react-spelling.ts +235 -0
  327. package/src/lib/scenario-story.ts +97 -0
  328. package/src/lib/slot-fill.ts +102 -0
  329. package/src/lib/template-files.ts +31 -0
  330. package/src/lib/types.ts +778 -0
  331. package/src/lib/void-elements.ts +15 -0
  332. package/src/load.ts +144 -0
  333. package/src/model.ts +568 -0
  334. package/src/options.ts +435 -0
  335. package/src/prop-types.ts +133 -0
  336. package/src/props-from.ts +678 -0
  337. package/src/protocol.ts +1135 -0
  338. package/src/react-view.ts +45 -0
  339. package/src/react.ts +34 -0
  340. package/src/sass-importers.ts +45 -0
  341. package/src/shell-acceptance.ts +45 -0
  342. package/src/shell-page.ts +133 -0
  343. package/src/shell-peers.ts +70 -0
  344. package/src/shell.ts +194 -0
  345. package/src/shells/charcell/page.ts +82 -0
  346. package/src/story-source.ts +391 -0
  347. package/src/story.ts +42 -0
  348. package/src/testing.ts +497 -0
  349. package/src/views.ts +183 -0
  350. package/src/vite-configuration.ts +675 -0
@@ -0,0 +1,442 @@
1
+ // The `dev` command's server: generate, serve, and keep the preview in step
2
+ // with the kit while it runs. The kit's templates, story files and manual
3
+ // component modules load in Node for generation, not through Vite, so Vite
4
+ // does not watch them; this module adds them to Vite's watcher and
5
+ // regenerates when one changes. The generated registries and manifest are in
6
+ // Vite's module graph, so writing a changed one reloads the pages that import
7
+ // it. An edit to the kit configuration starts a new server from the new
8
+ // configuration, since it can change the port, the sources, the shell, the
9
+ // aliases and the data the pages are served.
10
+
11
+ import { realpathSync } from 'node:fs';
12
+ import { createRequire } from 'node:module';
13
+ import { dirname, resolve, sep } from 'node:path';
14
+ import type { ConfigurationLookupOptions } from 'scaffold-ui-kit';
15
+ import { createServer, type Plugin, type ViteDevServer } from 'vite';
16
+ import { loadPreviewContext, type PreviewContext } from './context.js';
17
+ import { generatePreview, type GeneratePreviewResult } from './generate.js';
18
+ import {
19
+ buildViteConfiguration,
20
+ type DevelopmentServerOverrides,
21
+ } from './vite-configuration.js';
22
+ import { isTemplateFile } from './lib/template-files.js';
23
+
24
+ /** How long a burst of saves settles before the preview regenerates. */
25
+ export const REGENERATION_DELAY_MILLISECONDS = 100;
26
+
27
+ /** The module cache the kit's modules load into (Node's, shared by every importer). */
28
+ const moduleCache = createRequire(import.meta.url).cache;
29
+
30
+ /** The kit root as written and as resolved, since a loaded module is cached
31
+ * under the path it resolved to. */
32
+ function kitRoots(context: PreviewContext): string[] {
33
+ const roots = new Set([context.kitRoot]);
34
+ try {
35
+ roots.add(realpathSync(context.kitRoot));
36
+ } catch {
37
+ // A kit root that does not resolve is only known as written.
38
+ }
39
+ return [...roots].map((root) => root + sep);
40
+ }
41
+
42
+ /** Whether `path` is one of the kit's own modules: under its root, and
43
+ * neither an installed package nor generated output. */
44
+ function isKitModule(
45
+ path: string,
46
+ context: PreviewContext,
47
+ roots: readonly string[]
48
+ ): boolean {
49
+ return (
50
+ roots.some((root) => path.startsWith(root)) &&
51
+ !path.includes(`${sep}node_modules${sep}`) &&
52
+ !path.startsWith(context.generatedRoot + sep)
53
+ );
54
+ }
55
+
56
+ /**
57
+ * The kit's own modules generation has loaded: the templates and story files,
58
+ * and every module of the kit they import.
59
+ *
60
+ * @param context - The kit's preview context.
61
+ * @returns Their absolute paths.
62
+ */
63
+ export function loadedKitModules(context: PreviewContext): string[] {
64
+ const roots = kitRoots(context);
65
+ return Object.keys(moduleCache).filter((path) =>
66
+ isKitModule(path, context, roots)
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Forgets the kit's loaded modules, so the next generation imports each one
72
+ * afresh from disk. Installed packages stay loaded.
73
+ *
74
+ * @param context - The kit's preview context.
75
+ * @returns The absolute paths forgotten.
76
+ */
77
+ export function forgetKitModules(context: PreviewContext): string[] {
78
+ const forgotten = loadedKitModules(context);
79
+ for (const path of forgotten) delete moduleCache[path];
80
+ return forgotten;
81
+ }
82
+
83
+ /** What the last successful generation read from a kit, beyond the paths
84
+ * its preview context names. */
85
+ export interface GenerationInputs {
86
+ /** The kit modules it loaded (`loadedKitModules`). */
87
+ loadedModules?: readonly string[];
88
+ /** The theme stylesheets and assets it copied (`generatePreview`'s
89
+ * `themeSources`). */
90
+ themeSources?: readonly string[];
91
+ }
92
+
93
+ /** What the development server watches in a kit. */
94
+ export interface KitWatchPaths {
95
+ /** The files and directories whose changes regenerate the preview. */
96
+ regeneration: string[];
97
+ /** The kit configuration file, whose changes restart the server. */
98
+ kitConfiguration: string;
99
+ /** The kit's modules a `.ts` configuration imports. A change to one
100
+ * restarts the server too. */
101
+ configurationModules: string[];
102
+ }
103
+
104
+ /**
105
+ * The paths the development server watches in a kit: each source's templates
106
+ * and stories directories (so an added or deleted component is noticed),
107
+ * its manual components module, the shared
108
+ * modules templates import, every other kit module generation loaded, and
109
+ * the themes' stylesheets and assets. The custom views module, the shell and
110
+ * the setup modules are left to Vite, which serves them and reloads them
111
+ * itself; generation reads none of them.
112
+ *
113
+ * @param context - The kit's preview context.
114
+ * @param inputs - What the last successful generation read.
115
+ * @returns The paths, absolute.
116
+ */
117
+ export function kitWatchPaths(
118
+ context: PreviewContext,
119
+ inputs: GenerationInputs = {}
120
+ ): KitWatchPaths {
121
+ const regeneration = new Set<string>();
122
+ for (const source of context.sources) {
123
+ for (const path of [
124
+ source.templatesDirectoryAbsolute,
125
+ source.storiesDirectoryAbsolute,
126
+ source.manualModuleAbsolute,
127
+ ]) {
128
+ if (path) regeneration.add(path);
129
+ }
130
+ }
131
+ for (const path of context.sharedModulesAbsolute) regeneration.add(path);
132
+ for (const path of inputs.loadedModules ?? []) regeneration.add(path);
133
+ for (const path of inputs.themeSources ?? []) regeneration.add(path);
134
+ return {
135
+ regeneration: [...regeneration],
136
+ kitConfiguration: context.configurationPath,
137
+ configurationModules: context.configurationModules,
138
+ };
139
+ }
140
+
141
+ /** What a change to a file means for the development server. */
142
+ export type KitChange = 'regenerate' | 'restart';
143
+
144
+ /**
145
+ * What a change to `path` asks of the development server: a restart for the
146
+ * kit configuration file and the kit modules it loads; a regeneration for a template (a TypeScript module
147
+ * directly in a templates directory), a story file, a theme's stylesheet or a
148
+ * file in its assets, or a module named in `kitWatchPaths`; nothing for any
149
+ * other file.
150
+ *
151
+ * @param path - The absolute path that changed, was added or was deleted.
152
+ * @param context - The kit's preview context.
153
+ * @param inputs - What the last successful generation read.
154
+ * @returns The change, or `undefined` when the file is none of the kit's inputs.
155
+ */
156
+ export function classifyKitChange(
157
+ path: string,
158
+ context: PreviewContext,
159
+ inputs: GenerationInputs = {}
160
+ ): KitChange | undefined {
161
+ const paths = kitWatchPaths(context, inputs);
162
+ if (
163
+ path === paths.kitConfiguration ||
164
+ paths.configurationModules.includes(path)
165
+ )
166
+ return 'restart';
167
+ const directory = dirname(path);
168
+ for (const source of context.sources) {
169
+ if (
170
+ directory === source.templatesDirectoryAbsolute &&
171
+ isTemplateFile(path)
172
+ ) {
173
+ return 'regenerate';
174
+ }
175
+ if (
176
+ directory === source.storiesDirectoryAbsolute &&
177
+ path.endsWith('.story.ts')
178
+ ) {
179
+ return 'regenerate';
180
+ }
181
+ }
182
+ if (
183
+ (inputs.themeSources ?? []).some(
184
+ (source) => path === source || path.startsWith(source + sep)
185
+ )
186
+ ) {
187
+ return 'regenerate';
188
+ }
189
+ const files = new Set(
190
+ paths.regeneration.filter(
191
+ (each) =>
192
+ !context.sources.some(
193
+ (source) =>
194
+ each === source.templatesDirectoryAbsolute ||
195
+ each === source.storiesDirectoryAbsolute
196
+ )
197
+ )
198
+ );
199
+ return files.has(path) ? 'regenerate' : undefined;
200
+ }
201
+
202
+ /** What the kit-watching plugin is told to do beyond regenerating. */
203
+ export interface KitWatchOptions {
204
+ /** Called when the kit configuration file changes; settles once the
205
+ * server is replaced, or kept because the new configuration did not load. */
206
+ onKitConfigurationChange: () => Promise<void>;
207
+ /** What the generation before the server started read; later ones
208
+ * replace it. */
209
+ inputs?: GenerationInputs;
210
+ /** How long a burst of saves settles first. */
211
+ delayMilliseconds?: number;
212
+ }
213
+
214
+ /**
215
+ * A Vite plugin that watches a kit's preview inputs while the development
216
+ * server runs and regenerates the preview when one changes. Saves arriving
217
+ * within the delay of each other regenerate once. A regeneration imports the
218
+ * kit's modules afresh, keeps Vite's cache and the scenario editor's story,
219
+ * and writes only the files whose content changed; Vite then reloads the
220
+ * pages importing them. A kit with an html pane also reloads its pages when
221
+ * a regeneration writes nothing, since the pane prints the templates' markup
222
+ * itself and a change to it writes no generated file. A kit module that fails to load is
223
+ * logged and the last good output stays until a later save loads.
224
+ *
225
+ * @param context - The kit's preview context.
226
+ * @param options - What a change to the kit configuration file does, and the delay.
227
+ * @returns The plugin.
228
+ */
229
+ export function kitWatchPlugin(
230
+ context: PreviewContext,
231
+ options: KitWatchOptions
232
+ ): Plugin {
233
+ const delay = options.delayMilliseconds ?? REGENERATION_DELAY_MILLISECONDS;
234
+ return {
235
+ name: 'preview-kit-watch',
236
+ configureServer(server) {
237
+ const logger = server.config.logger;
238
+ let inputs: GenerationInputs = {
239
+ loadedModules: loadedKitModules(context),
240
+ ...options.inputs,
241
+ };
242
+ let watched = new Set<string>();
243
+ // Watch what the latest generation read, and stop watching what it no
244
+ // longer reads, unless Vite serves that file itself.
245
+ const watch = (): void => {
246
+ const paths = kitWatchPaths(context, inputs);
247
+ const next = new Set([
248
+ ...paths.regeneration,
249
+ paths.kitConfiguration,
250
+ ...paths.configurationModules,
251
+ ]);
252
+ const dropped = [...watched].filter(
253
+ (path) =>
254
+ !next.has(path) && !server.moduleGraph?.getModulesByFile(path)?.size
255
+ );
256
+ if (dropped.length > 0) server.watcher.unwatch(dropped);
257
+ server.watcher.add([...next].filter((path) => !watched.has(path)));
258
+ watched = next;
259
+ };
260
+ watch();
261
+
262
+ let closed = false;
263
+ let timer: ReturnType<typeof setTimeout> | undefined;
264
+ let running: Promise<void> | undefined;
265
+ let pendingRegeneration = false;
266
+ let pendingRestart = false;
267
+
268
+ const regenerate = async (): Promise<void> => {
269
+ forgetKitModules(context);
270
+ try {
271
+ const { written, removed, themeSources } = await generatePreview(
272
+ context,
273
+ { clean: false }
274
+ );
275
+ inputs = { loadedModules: loadedKitModules(context), themeSources };
276
+ watch();
277
+ const changed = written.length + removed.length;
278
+ logger.info(
279
+ changed === 0
280
+ ? '[preview] regenerated: no generated files changed'
281
+ : `[preview] regenerated: ${changed} ${changed === 1 ? 'file' : 'files'} changed`,
282
+ { timestamp: true }
283
+ );
284
+ // The theme stylesheets are served as files, outside the module
285
+ // graph: a changed one reloads the pages here. The html pane's
286
+ // markup is printed on request from the templates just loaded, and
287
+ // a change to a template's markup writes no generated file, so
288
+ // that kit reloads here when nothing was written for Vite to act on.
289
+ const publicRoot = resolve(context.generatedRoot, 'public') + sep;
290
+ if (
291
+ (context.buildsHtml && changed === 0) ||
292
+ written.some((path) => path.startsWith(publicRoot))
293
+ ) {
294
+ server.ws.send({ type: 'full-reload', path: '*' });
295
+ }
296
+ } catch (error) {
297
+ const detail =
298
+ error instanceof Error ? (error.stack ?? error.message) : error;
299
+ logger.error(
300
+ `[preview] regeneration failed; the preview keeps its last output until a save loads:\n${String(detail)}`,
301
+ { timestamp: true }
302
+ );
303
+ }
304
+ };
305
+
306
+ const work = async (): Promise<void> => {
307
+ if (pendingRestart) {
308
+ pendingRestart = false;
309
+ await options.onKitConfigurationChange();
310
+ }
311
+ if (pendingRegeneration && !closed) {
312
+ pendingRegeneration = false;
313
+ await regenerate();
314
+ }
315
+ };
316
+
317
+ const settle = (): void => {
318
+ timer = undefined;
319
+ if (closed) return;
320
+ if (running) return;
321
+ running = work().finally(() => {
322
+ running = undefined;
323
+ if ((pendingRestart || pendingRegeneration) && !closed && !timer) {
324
+ settle();
325
+ }
326
+ });
327
+ };
328
+
329
+ server.watcher.on('all', (event, path) => {
330
+ if (closed) return;
331
+ if (event !== 'add' && event !== 'change' && event !== 'unlink') return;
332
+ const change = classifyKitChange(path, context, inputs);
333
+ if (!change) return;
334
+ if (change === 'restart') pendingRestart = true;
335
+ else pendingRegeneration = true;
336
+ if (timer) clearTimeout(timer);
337
+ timer = setTimeout(settle, delay);
338
+ });
339
+ server.httpServer?.once('close', () => {
340
+ closed = true;
341
+ if (timer) clearTimeout(timer);
342
+ });
343
+ },
344
+ };
345
+ }
346
+
347
+ /** A running development server. */
348
+ export interface DevelopmentServer {
349
+ /** The Vite server currently serving; replaced when the kit configuration changes. */
350
+ readonly server: ViteDevServer;
351
+ /** Stops serving. */
352
+ close(): Promise<void>;
353
+ }
354
+
355
+ /**
356
+ * Generates a kit's preview and serves it, regenerating as the kit's
357
+ * preview inputs change and starting a new server, from the new
358
+ * configuration, when the kit configuration changes. A configuration that
359
+ * fails to load is logged and the running server kept.
360
+ *
361
+ * @param kitRoot - The kit root (where its package.json lives).
362
+ * @param overrides - The port and host, over the kit's configured ones.
363
+ * @param options - The kit configuration file, when it is not the one the
364
+ * kit root gives.
365
+ * @returns The running server.
366
+ * @example
367
+ * ```ts
368
+ * const development = await startDevelopmentServer(kitRoot, { port: 6100 });
369
+ * ```
370
+ */
371
+ export async function startDevelopmentServer(
372
+ kitRoot: string,
373
+ overrides: DevelopmentServerOverrides = {},
374
+ options: ConfigurationLookupOptions = {}
375
+ ): Promise<DevelopmentServer> {
376
+ let server: ViteDevServer;
377
+ let restarting: Promise<void> | undefined;
378
+
379
+ const listen = async (
380
+ context: PreviewContext,
381
+ generated: GeneratePreviewResult
382
+ ): Promise<ViteDevServer> => {
383
+ const configuration = buildViteConfiguration(context, overrides);
384
+ configuration.plugins = [
385
+ ...(configuration.plugins ?? []),
386
+ kitWatchPlugin(context, {
387
+ onKitConfigurationChange: () => (restarting = restart()),
388
+ inputs: {
389
+ loadedModules: loadedKitModules(context),
390
+ themeSources: generated.themeSources,
391
+ },
392
+ }),
393
+ ];
394
+ const started = await createServer(configuration);
395
+ await started.listen();
396
+ started.printUrls();
397
+ return started;
398
+ };
399
+
400
+ const restart = async (): Promise<void> => {
401
+ const logger = server.config.logger;
402
+ let context: PreviewContext;
403
+ let generated: GeneratePreviewResult;
404
+ try {
405
+ context = loadPreviewContext(kitRoot, options);
406
+ forgetKitModules(context);
407
+ generated = await generatePreview(context, { clean: false });
408
+ } catch (error) {
409
+ logger.error(
410
+ `[preview] the kit configuration changed but did not load; the server keeps its previous configuration until a save loads:\n${error instanceof Error ? error.message : String(error)}`,
411
+ { timestamp: true }
412
+ );
413
+ return;
414
+ }
415
+ logger.info('[preview] the kit configuration changed: restarting', {
416
+ timestamp: true,
417
+ });
418
+ await server.close();
419
+ try {
420
+ server = await listen(context, generated);
421
+ } catch (error) {
422
+ logger.error(
423
+ `[preview] the server did not restart:\n${error instanceof Error ? error.message : String(error)}`,
424
+ { timestamp: true }
425
+ );
426
+ process.exitCode = 1;
427
+ }
428
+ };
429
+
430
+ const context = loadPreviewContext(kitRoot, options);
431
+ for (const warning of context.warnings) console.warn(`[preview] ${warning}`);
432
+ server = await listen(context, await generatePreview(context));
433
+ return {
434
+ get server() {
435
+ return server;
436
+ },
437
+ async close() {
438
+ await restarting;
439
+ await server.close();
440
+ },
441
+ };
442
+ }
@@ -0,0 +1,201 @@
1
+ // Shared helpers used by every per-target emitter. The propTypes/props are
2
+ // identical across targets (same model); only imports, render syntax, and
3
+ // slot-fill translation differ - those live in the per-target emitters.
4
+
5
+ import type {
6
+ PropTypeSpecification,
7
+ JsonValue,
8
+ SlotFill,
9
+ ResolvedStory,
10
+ } from '../lib/types.js';
11
+
12
+ export function serialize(value: JsonValue): string {
13
+ if (value === undefined) return 'undefined';
14
+ if (value === null) return 'null';
15
+ if (Array.isArray(value)) return `[${value.map(serialize).join(', ')}]`;
16
+ if (typeof value === 'string') return JSON.stringify(value);
17
+ if (typeof value === 'object') {
18
+ const body = Object.entries(value)
19
+ .map(
20
+ ([key, entryValue]) =>
21
+ `${JSON.stringify(key)}: ${serialize(entryValue as JsonValue)}`
22
+ )
23
+ .join(', ');
24
+ return `{ ${body} }`;
25
+ }
26
+ return String(value);
27
+ }
28
+
29
+ /**
30
+ * Text as a Vue or Svelte template reads it back: `&` and `<` escaped, as
31
+ * either would start a character reference or open a tag.
32
+ *
33
+ * @param value - The text as the story gives it.
34
+ * @returns The text as markup.
35
+ */
36
+ export function markupText(value: string): string {
37
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;');
38
+ }
39
+
40
+ /**
41
+ * Whether a text has whitespace a template does not keep as written:
42
+ * whitespace at an edge, a run of it or a line break, which a template
43
+ * trims, collapses, or loses where the printed element breaks over lines.
44
+ *
45
+ * @param value - The text as the story gives it.
46
+ * @returns Whether the text has to be printed as a string expression.
47
+ */
48
+ export function hasFragileWhitespace(value: string): boolean {
49
+ return /^\s|\s$|\s\s|[\t\n\r\f]/.test(value);
50
+ }
51
+
52
+ /**
53
+ * Text as a Vue template reads it back: markup text with each `{{` held in
54
+ * an interpolation of its own, so it does not open one.
55
+ *
56
+ * @param value - The text as the story gives it.
57
+ * @returns The text as Vue template content.
58
+ * @example
59
+ * vueText('Type {{ name }} here');
60
+ * // 'Type {{ "{{" }} name }} here'
61
+ */
62
+ export function vueText(value: string): string {
63
+ return markupText(value).replace(/\{\{/g, '{{ "{{" }}');
64
+ }
65
+
66
+ /**
67
+ * Text as a printed Vue template keeps it: {@link vueText}, or one string
68
+ * interpolation for a text with whitespace at an edge, a run of it or a
69
+ * line break, which Vue condenses and a printed element's line breaks
70
+ * replace.
71
+ *
72
+ * @param value - The text as the story gives it.
73
+ * @returns The text as Vue template content.
74
+ * @example
75
+ * vueSourceText(' 1 ripburger');
76
+ * // '{{ " 1 ripburger" }}'
77
+ */
78
+ export function vueSourceText(value: string): string {
79
+ if (hasFragileWhitespace(value)) {
80
+ // A `}}` inside the string would close the interpolation.
81
+ return `{{ ${JSON.stringify(value).replace(/\}\}/g, '}\\u007d')} }}`;
82
+ }
83
+ return vueText(value);
84
+ }
85
+
86
+ /**
87
+ * The Vue template content before a junction, given the content joined onto
88
+ * it: a trailing `{` before content starting with one is held in an
89
+ * interpolation of its own, as the two would otherwise open one.
90
+ *
91
+ * @param before - The content before the junction.
92
+ * @param next - The content joined onto it.
93
+ * @returns The content before the junction, safe to join.
94
+ * @example
95
+ * vueJunction('Price: {', '{ amount }}');
96
+ * // "Price: {{ '{' }}"
97
+ */
98
+ export function vueJunction(before: string, next: string): string {
99
+ return before.endsWith('{') && next.startsWith('{')
100
+ ? `${before.slice(0, -1)}{{ '{' }}`
101
+ : before;
102
+ }
103
+
104
+ /**
105
+ * Vue template pieces joined into one, each junction separated by
106
+ * {@link vueJunction}.
107
+ *
108
+ * @param pieces - The template content, in order.
109
+ * @returns The joined content.
110
+ */
111
+ export function joinVue(pieces: string[]): string {
112
+ return pieces.reduce(
113
+ (joined, piece) => (joined ? vueJunction(joined, piece) : joined) + piece,
114
+ ''
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Text as a Svelte template reads it back: markup text with each brace held
120
+ * in an expression of its own, so none opens one. Svelte trims the whitespace
121
+ * at the edges of an element's or a component's content and collapses a run
122
+ * of it, so a text with whitespace at an edge, a run of it or a line break
123
+ * is one string expression instead, which Svelte renders as it is.
124
+ *
125
+ * @param value - The text as the story gives it.
126
+ * @returns The text as Svelte markup.
127
+ * @example
128
+ * svelteText('Price: {amount}');
129
+ * // "Price: {'{'}amount{'}'}"
130
+ * svelteText(' 1 ripburger');
131
+ * // '{" 1 ripburger"}'
132
+ */
133
+ export function svelteText(value: string): string {
134
+ if (hasFragileWhitespace(value)) {
135
+ return `{${JSON.stringify(value)}}`;
136
+ }
137
+ return markupText(value).replace(/[{}]/g, (brace) => `{'${brace}'}`);
138
+ }
139
+
140
+ /**
141
+ * A value as a Vue template's bound attribute holds it, single-quoted:
142
+ * the value's expression with `&` and `'` as character references, which
143
+ * the template reads back as the characters, so neither ends the attribute
144
+ * nor starts a reference of its own.
145
+ *
146
+ * @param value - The prop's value.
147
+ * @returns The attribute's text, quotes included.
148
+ * @example
149
+ * vueBoundAttribute("Don't");
150
+ * // `'"Don&#39;t"'`
151
+ */
152
+ export function vueBoundAttribute(value: JsonValue): string {
153
+ return `'${serialize(value).replace(/&/g, '&amp;').replace(/'/g, '&#39;')}'`;
154
+ }
155
+
156
+ export function needsColor(model: ResolvedStory): boolean {
157
+ return model.propTypes.some(
158
+ (propType) => propType.include && propType.control === 'color-select'
159
+ );
160
+ }
161
+
162
+ function propTypeLine(specification: PropTypeSpecification): string {
163
+ if (!specification.include)
164
+ return ` ${specification.name}: { table: { disable: true } },`;
165
+ switch (specification.control) {
166
+ case 'color-select':
167
+ return ` ${specification.name}: { control: 'select', options: colorOptions },`;
168
+ case 'select':
169
+ return ` ${specification.name}: { control: 'select', options: ${serialize(specification.options ?? [])} },`;
170
+ case 'number':
171
+ return specification.number
172
+ ? ` ${specification.name}: { control: { type: 'number', min: ${specification.number.min}, max: ${specification.number.max}, step: ${specification.number.step} } },`
173
+ : ` ${specification.name}: { control: 'number' },`;
174
+ case 'boolean':
175
+ return ` ${specification.name}: { control: 'boolean' },`;
176
+ case 'text':
177
+ return ` ${specification.name}: { control: 'text' },`;
178
+ }
179
+ }
180
+
181
+ export function propTypesBody(model: ResolvedStory): string {
182
+ return model.propTypes.map(propTypeLine).join('\n');
183
+ }
184
+
185
+ /** The effective default-slot fill: an explicit story file fill, or the `children`
186
+ * text prop for components that have a default slot and no story file fill. */
187
+ export type DefaultSlot =
188
+ | SlotFill
189
+ | { kind: 'propText'; prop: string }
190
+ | undefined;
191
+
192
+ export function effectiveDefaultSlot(model: ResolvedStory): DefaultSlot {
193
+ if (model.slots?.default) return model.slots.default;
194
+ if (model.defaultProps.children !== undefined)
195
+ return { kind: 'propText', prop: 'children' };
196
+ return undefined;
197
+ }
198
+
199
+ export function namedSlots(model: ResolvedStory): Record<string, SlotFill> {
200
+ return model.slots?.named ?? {};
201
+ }