StaticResourcesMkssrPage.vala 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. using Spry;
  2. using Inversion;
  3. /**
  4. * StaticResourcesMkssrPage - Documentation for the spry-mkssr tool
  5. *
  6. * This page explains how to use spry-mkssr to generate static resources
  7. * for Spry applications.
  8. */
  9. public class StaticResourcesMkssrPage : PageComponent {
  10. public const string ROUTE = "/static-resources/spry-mkssr";
  11. private ComponentFactory factory = inject<ComponentFactory>();
  12. public override string markup { get {
  13. return """
  14. <div sid="page" class="doc-content">
  15. <h1>spry-mkssr Tool</h1>
  16. <section class="doc-section">
  17. <h2>What is spry-mkssr?</h2>
  18. <p>
  19. <code>spry-mkssr</code> is a command-line tool that generates Spry Static Resource
  20. files from any input file. It handles compression, hashing, and metadata generation
  21. so your web application can serve optimized static content efficiently.
  22. </p>
  23. <p>
  24. The tool can generate two types of output:
  25. </p>
  26. <ul>
  27. <li><strong>SSR files</strong> (<code>.ssr</code>) - Pre-compiled resource files read at runtime</li>
  28. <li><strong>Vala source files</strong> - Embedded resources compiled into your binary</li>
  29. </ul>
  30. </section>
  31. <section class="doc-section">
  32. <h2>Command-Line Usage</h2>
  33. <spry-component name="CodeBlockComponent" sid="usage-code"/>
  34. <h3>Options</h3>
  35. <table class="doc-table">
  36. <thead>
  37. <tr>
  38. <th>Option</th>
  39. <th>Description</th>
  40. </tr>
  41. </thead>
  42. <tbody>
  43. <tr>
  44. <td><code>-o, --output=FILE</code></td>
  45. <td>Output file name (default: input.ssr or ClassNameResource.vala)</td>
  46. </tr>
  47. <tr>
  48. <td><code>-c, --content-type=TYPE</code></td>
  49. <td>Override content type (e.g., text/css, application/javascript)</td>
  50. </tr>
  51. <tr>
  52. <td><code>-n, --name=NAME</code></td>
  53. <td>Override resource name (default: input filename)</td>
  54. </tr>
  55. <tr>
  56. <td><code>--vala</code></td>
  57. <td>Generate Vala source file instead of SSR</td>
  58. </tr>
  59. <tr>
  60. <td><code>--ns=NAMESPACE</code></td>
  61. <td>Namespace for generated Vala class (requires --vala)</td>
  62. </tr>
  63. <tr>
  64. <td><code>-v, --version</code></td>
  65. <td>Show version information</td>
  66. </tr>
  67. </tbody>
  68. </table>
  69. </section>
  70. <section class="doc-section">
  71. <h2>Generating SSR Files</h2>
  72. <p>
  73. By default, spry-mkssr generates <code>.ssr</code> files that can be read
  74. at runtime by <code>FileStaticResource</code>:
  75. </p>
  76. <spry-component name="CodeBlockComponent" sid="ssr-example"/>
  77. <p>
  78. The generated SSR file contains:
  79. </p>
  80. <ul>
  81. <li><strong>Magic header</strong> - "spry-sr\0" identifier</li>
  82. <li><strong>Resource name</strong> - Used in URLs</li>
  83. <li><strong>Content type</strong> - MIME type for HTTP headers</li>
  84. <li><strong>SHA-512 hash</strong> - For ETag generation</li>
  85. <li><strong>Encoding headers</strong> - Offset and size for each encoding</li>
  86. <li><strong>Compressed data</strong> - gzip, zstd, brotli, and identity</li>
  87. </ul>
  88. </section>
  89. <section class="doc-section">
  90. <h2>Generating Vala Source Files</h2>
  91. <p>
  92. Use the <code>--vala</code> flag to generate a Vala source file that embeds
  93. the resource directly into your binary:
  94. </p>
  95. <spry-component name="CodeBlockComponent" sid="vala-example"/>
  96. <p>
  97. This creates a class that extends <code>ConstantStaticResource</code> with
  98. the resource data embedded as <code>const uint8[]</code> arrays.
  99. </p>
  100. <h3>Namespace Configuration</h3>
  101. <p>
  102. Use <code>--ns</code> to place the generated class in a namespace:
  103. </p>
  104. <spry-component name="CodeBlockComponent" sid="namespace-example"/>
  105. </section>
  106. <section class="doc-section">
  107. <h2>Generated Vala Class Structure</h2>
  108. <p>
  109. The generated Vala class follows this structure:
  110. </p>
  111. <spry-component name="CodeBlockComponent" sid="class-structure"/>
  112. </section>
  113. <section class="doc-section">
  114. <h2>Integrating with Meson</h2>
  115. <p>
  116. The recommended way to use spry-mkssr is with meson's
  117. <code>custom_target</code> to generate resources at build time:
  118. </p>
  119. <spry-component name="CodeBlockComponent" sid="meson-example"/>
  120. <h3>Complete meson.build Example</h3>
  121. <spry-component name="CodeBlockComponent" sid="meson-complete"/>
  122. <div class="info-box">
  123. <p>
  124. <strong>💡 Tip:</strong> The <code>@INPUT@</code> and <code>@OUTPUT@</code>
  125. placeholders are automatically replaced by meson with the actual file paths.
  126. </p>
  127. </div>
  128. </section>
  129. <section class="doc-section">
  130. <h2>Compression Details</h2>
  131. <p>
  132. spry-mkssr compresses resources with the highest compression levels:
  133. </p>
  134. <table class="doc-table">
  135. <thead>
  136. <tr>
  137. <th>Encoding</th>
  138. <th>Level</th>
  139. <th>Best For</th>
  140. </tr>
  141. </thead>
  142. <tbody>
  143. <tr>
  144. <td>gzip</td>
  145. <td>9 (maximum)</td>
  146. <td>Universal compatibility</td>
  147. </tr>
  148. <tr>
  149. <td>zstd</td>
  150. <td>19 (maximum)</td>
  151. <td>Modern browsers, fast decompression</td>
  152. </tr>
  153. <tr>
  154. <td>brotli</td>
  155. <td>11 (maximum)</td>
  156. <td>Best compression for text assets</td>
  157. </tr>
  158. <tr>
  159. <td>identity</td>
  160. <td>N/A</td>
  161. <td>No compression (always included)</td>
  162. </tr>
  163. </tbody>
  164. </table>
  165. <p>
  166. Encodings are only included if they result in smaller file sizes than
  167. the original. The tool reports compression statistics during generation:
  168. </p>
  169. <spry-component name="CodeBlockComponent" sid="compression-output"/>
  170. </section>
  171. <section class="doc-section">
  172. <h2>Content Type Detection</h2>
  173. <p>
  174. spry-mkssr automatically detects content types based on file extensions
  175. using GLib's content type guessing. You can override this with the
  176. <code>-c</code> flag:
  177. </p>
  178. <spry-component name="CodeBlockComponent" sid="content-type-example"/>
  179. </section>
  180. <section class="doc-section">
  181. <h2>Resource Naming</h2>
  182. <p>
  183. By default, the resource name is derived from the input filename. For
  184. Vala source generation, the class name is created by converting the
  185. resource name to PascalCase and appending "Resource":
  186. </p>
  187. <table class="doc-table">
  188. <thead>
  189. <tr>
  190. <th>Input File</th>
  191. <th>Resource Name</th>
  192. <th>Generated Class</th>
  193. </tr>
  194. </thead>
  195. <tbody>
  196. <tr>
  197. <td>docs.css</td>
  198. <td>docs.css</td>
  199. <td>DocsResource.vala → DocsResource</td>
  200. </tr>
  201. <tr>
  202. <td>htmx.min.js</td>
  203. <td>htmx.min.js</td>
  204. <td>HtmxMinResource.vala → HtmxMinResource</td>
  205. </tr>
  206. <tr>
  207. <td>-n htmx.js htmx.min.js</td>
  208. <td>htmx.js</td>
  209. <td>HtmxResource.vala → HtmxResource</td>
  210. </tr>
  211. </tbody>
  212. </table>
  213. <p>
  214. Use the <code>-n</code> flag to set a specific resource name:
  215. </p>
  216. <spry-component name="CodeBlockComponent" sid="naming-example"/>
  217. </section>
  218. <section class="doc-section">
  219. <h2>Next Steps</h2>
  220. <div class="nav-cards">
  221. <a href="/static-resources/overview" class="nav-card">
  222. <h3>← Static Resources Overview</h3>
  223. <p>Back to static resources introduction</p>
  224. </a>
  225. </div>
  226. </section>
  227. </div>
  228. """;
  229. }}
  230. public override async void prepare() throws Error {
  231. var usage_code = get_component_child<CodeBlockComponent>("usage-code");
  232. usage_code.language = "Bash";
  233. usage_code.code = "spry-mkssr [OPTION?] INPUT_FILE\n\n" +
  234. "# Examples:\n" +
  235. "spry-mkssr styles.css # Creates styles.css.ssr\n" +
  236. "spry-mkssr -o bundle.ssr app.js # Creates bundle.ssr\n" +
  237. "spry-mkssr --vala logo.png # Creates LogoResource.vala\n" +
  238. "spry-mkssr --vala --ns=MyApp styles.css # Creates StylesResource.vala in MyApp namespace";
  239. var ssr_example = get_component_child<CodeBlockComponent>("ssr-example");
  240. ssr_example.language = "Bash";
  241. ssr_example.code = "# Generate an SSR file from a CSS file\n" +
  242. "spry-mkssr styles.css\n" +
  243. "# Output: styles.css.ssr\n\n" +
  244. "# Generate with custom output name\n" +
  245. "spry-mkssr -o resources/bundle.css.ssr styles.css\n" +
  246. "# Output: resources/bundle.css.ssr\n\n" +
  247. "# The resource name in URLs will be \"styles.css\"\n" +
  248. "# URL: /_spry/res/styles.css";
  249. var vala_example = get_component_child<CodeBlockComponent>("vala-example");
  250. vala_example.language = "Bash";
  251. vala_example.code = "# Generate a Vala source file for embedding\n" +
  252. "spry-mkssr --vala styles.css\n" +
  253. "# Output: StylesResource.vala\n\n" +
  254. "# The generated class name is derived from the filename\n" +
  255. "# styles.css → StylesResource";
  256. var namespace_example = get_component_child<CodeBlockComponent>("namespace-example");
  257. namespace_example.language = "Bash";
  258. namespace_example.code = "# Generate with namespace\n" +
  259. "spry-mkssr --vala --ns=MyApp.Static styles.css\n" +
  260. "# Output: StylesResource.vala\n\n" +
  261. "# Generated class:\n" +
  262. "# namespace MyApp.Static {\n" +
  263. "# public class StylesResource : ConstantStaticResource {\n" +
  264. "# ...\n" +
  265. "# }\n" +
  266. "# }";
  267. var class_structure = get_component_child<CodeBlockComponent>("class-structure");
  268. class_structure.language = "Vala";
  269. class_structure.code = "// Generated by spry-mkssr\n" +
  270. "public class DocsResource : ConstantStaticResource {\n\n" +
  271. " // Resource metadata\n" +
  272. " public override string name { get { return \"docs.css\"; } }\n" +
  273. " public override string file_hash { get { return \"a1b2c3d4...\"; } }\n" +
  274. " public override string content_type { get { return \"text/css\"; } }\n\n" +
  275. " // Select best encoding based on client support\n" +
  276. " public override string get_best_encoding(Set<string> supported) {\n" +
  277. " if (supported.has(\"br\")) return \"br\";\n" +
  278. " if (supported.has(\"zstd\")) return \"zstd\";\n" +
  279. " if (supported.has(\"gzip\")) return \"gzip\";\n" +
  280. " return \"identity\";\n" +
  281. " }\n\n" +
  282. " // Return the data for a specific encoding\n" +
  283. " public override unowned uint8[] get_encoding(string encoding) {\n" +
  284. " switch (encoding) {\n" +
  285. " case \"br\": return BR_DATA;\n" +
  286. " case \"zstd\": return ZSTD_DATA;\n" +
  287. " case \"gzip\": return GZIP_DATA;\n" +
  288. " default: return IDENTITY_DATA;\n" +
  289. " }\n" +
  290. " }\n\n" +
  291. " // Embedded compressed data\n" +
  292. " private const uint8[] IDENTITY_DATA = { /* ... */ };\n" +
  293. " private const uint8[] GZIP_DATA = { /* ... */ };\n" +
  294. " private const uint8[] ZSTD_DATA = { /* ... */ };\n" +
  295. " private const uint8[] BR_DATA = { /* ... */ };\n" +
  296. "}";
  297. var meson_example = get_component_child<CodeBlockComponent>("meson-example");
  298. meson_example.language = "Meson";
  299. meson_example.code = "# Generate a Vala resource file from CSS\n" +
  300. "docs_css_resource = custom_target('docs-css-resource',\n" +
  301. " input: 'Static/docs.css',\n" +
  302. " output: 'DocsCssResource.vala',\n" +
  303. " command: [spry_mkssr, '--vala', '--ns=Demo.Static', \n" +
  304. " '-n', 'docs.css', '-c', 'text/css', \n" +
  305. " '-o', '@OUTPUT@', '@INPUT@']\n" +
  306. ")\n\n" +
  307. "# Then include in your executable sources:\n" +
  308. "executable('my-app',\n" +
  309. " app_sources,\n" +
  310. " docs_css_resource, # Add generated file\n" +
  311. " dependencies: [spry_dep]\n" +
  312. ")";
  313. var meson_complete = get_component_child<CodeBlockComponent>("meson-complete");
  314. meson_complete.language = "Meson";
  315. meson_complete.code = "# Find the spry-mkssr tool\n" +
  316. "spry_mkssr = find_program('spry-mkssr')\n\n" +
  317. "# Generate multiple resources\n" +
  318. "css_resource = custom_target('css-resource',\n" +
  319. " input: 'Static/styles.css',\n" +
  320. " output: 'StylesResource.vala',\n" +
  321. " command: [spry_mkssr, '--vala', '--ns=MyApp.Resources',\n" +
  322. " '-o', '@OUTPUT@', '@INPUT@']\n" +
  323. ")\n\n" +
  324. "js_resource = custom_target('js-resource',\n" +
  325. " input: 'Static/app.js',\n" +
  326. " output: 'AppResource.vala',\n" +
  327. " command: [spry_mkssr, '--vala', '--ns=MyApp.Resources',\n" +
  328. " '-c', 'application/javascript',\n" +
  329. " '-o', '@OUTPUT@', '@INPUT@']\n" +
  330. ")\n\n" +
  331. "# Build executable with embedded resources\n" +
  332. "executable('my-app',\n" +
  333. " 'Main.vala',\n" +
  334. " css_resource,\n" +
  335. " js_resource,\n" +
  336. " dependencies: [spry_dep]\n" +
  337. ")";
  338. var compression_output = get_component_child<CodeBlockComponent>("compression-output");
  339. compression_output.language = "Bash";
  340. compression_output.code = "$ spry-mkssr --vala docs.css\n" +
  341. "Compressing with gzip...\n" +
  342. " gzip: 16094 -> 3245 bytes (20.2%)\n" +
  343. "Compressing with zstd...\n" +
  344. " zstd: 16094 -> 2987 bytes (18.6%)\n" +
  345. "Compressing with brotli...\n" +
  346. " brotli: 16094 -> 2654 bytes (16.5%)\n" +
  347. "Generated: DocsResource.vala";
  348. var content_type_example = get_component_child<CodeBlockComponent>("content-type-example");
  349. content_type_example.language = "Bash";
  350. content_type_example.code = "# Automatic detection (recommended)\n" +
  351. "spry-mkssr styles.css # → text/css\n" +
  352. "spry-mkssr app.js # → application/javascript\n" +
  353. "spry-mkssr logo.png # → image/png\n\n" +
  354. "# Manual override\n" +
  355. "spry-mkssr -c text/plain data.json # Force text/plain\n" +
  356. "spry-mkssr -c application/wasm module.wasm # Custom MIME type";
  357. var naming_example = get_component_child<CodeBlockComponent>("naming-example");
  358. naming_example.language = "Bash";
  359. naming_example.code = "# Default naming\n" +
  360. "spry-mkssr --vala htmx.min.js\n" +
  361. "# Resource: \"htmx.min.js\", Class: HtmxMinResource\n\n" +
  362. "# Custom resource name\n" +
  363. "spry-mkssr --vala -n htmx.js htmx.min.js\n" +
  364. "# Resource: \"htmx.js\", Class: HtmxResource\n" +
  365. "# URL: /_spry/res/htmx.js\n\n" +
  366. "# This is useful for:\n" +
  367. "# - Hiding .min suffix from URLs\n" +
  368. "# - Using versioned files with clean names\n" +
  369. "# - Renaming resources for organization";
  370. }
  371. }