StaticResourcesOverviewPage.vala 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. using Spry;
  2. using Inversion;
  3. /**
  4. * StaticResourcesOverviewPage - Introduction to Spry Static Resources
  5. *
  6. * This page provides a comprehensive overview of static resource handling in Spry,
  7. * including the different types of resources and how to use them.
  8. */
  9. public class StaticResourcesOverviewPage : PageComponent {
  10. public const string ROUTE = "/static-resources/overview";
  11. private ComponentFactory factory = inject<ComponentFactory>();
  12. public override string markup { get {
  13. return """
  14. <div sid="page" class="doc-content">
  15. <h1>Static Resources Overview</h1>
  16. <section class="doc-section">
  17. <h2>What are Static Resources?</h2>
  18. <p>
  19. Spry provides a built-in system for serving static assets like CSS, JavaScript,
  20. images, and fonts. The static resource system handles content negotiation,
  21. compression, and caching automatically.
  22. </p>
  23. <p>
  24. Unlike traditional web frameworks that serve static files directly from disk,
  25. Spry's static resources are pre-compressed and can be embedded directly into
  26. your binary for single-binary deployments.
  27. </p>
  28. </section>
  29. <section class="doc-section">
  30. <h2>Types of Static Resources</h2>
  31. <p>
  32. Spry provides three implementations of the
  33. <code>StaticResource</code> interface:
  34. </p>
  35. <div class="resource-types">
  36. <div class="resource-type-card">
  37. <h3>FileStaticResource</h3>
  38. <p>
  39. Reads pre-compiled <code>.ssr</code> (Spry Static Resource) files from
  40. disk at runtime. These files contain the resource data along with
  41. pre-compressed versions (gzip, zstd, brotli) and metadata.
  42. </p>
  43. <ul>
  44. <li>Loaded from <code>.ssr</code> files generated by spry-mkssr</li>
  45. <li>Contains all compression variants in a single file</li>
  46. <li>Includes SHA-512 hash for ETags</li>
  47. </ul>
  48. </div>
  49. <div class="resource-type-card">
  50. <h3>MemoryStaticResource</h3>
  51. <p>
  52. Holds static resource data entirely in memory. Useful for
  53. dynamically generated resources or when you want to load
  54. resources from a custom source.
  55. </p>
  56. <ul>
  57. <li>All data held in RAM</li>
  58. <li>Supports all compression formats</li>
  59. <li>Created programmatically at runtime</li>
  60. </ul>
  61. </div>
  62. <div class="resource-type-card">
  63. <h3>ConstantStaticResource</h3>
  64. <p>
  65. Embeds static resources directly into your binary at compile time.
  66. The resource data becomes part of your executable, enabling true
  67. single-binary deployments.
  68. </p>
  69. <ul>
  70. <li>Compiled into your binary</li>
  71. <li>Generated by spry-mkssr with <code>--vala</code> flag</li>
  72. <li>Zero runtime file I/O</li>
  73. </ul>
  74. </div>
  75. </div>
  76. </section>
  77. <section class="doc-section">
  78. <h2>The StaticResource Interface</h2>
  79. <p>
  80. All static resources implement the <code>StaticResource</code> interface,
  81. which defines the contract for serving static content:
  82. </p>
  83. <spry-component name="CodeBlockComponent" sid="interface-code"/>
  84. </section>
  85. <section class="doc-section">
  86. <h2>The StaticResourceProvider Endpoint</h2>
  87. <p>
  88. Spry includes a built-in endpoint for serving static resources. The
  89. <code>StaticResourceProvider</code> handles all the complexity of content
  90. negotiation and caching.
  91. </p>
  92. <h3>Route Pattern</h3>
  93. <p>
  94. Static resources are served from the route:
  95. </p>
  96. <div class="code-inline">
  97. <code>/_spry/res/{resource}</code>
  98. </div>
  99. <p>
  100. Where <code>{resource}</code> is the name of the resource (e.g.,
  101. <code>docs.css</code>, <code>htmx.js</code>).
  102. </p>
  103. <h3>Content Negotiation</h3>
  104. <p>
  105. The provider automatically selects the best compression format based on
  106. the client's <code>Accept-Encoding</code> header. Supported encodings:
  107. </p>
  108. <ul>
  109. <li><strong>gzip</strong> - Universal browser support</li>
  110. <li><strong>zstd</strong> - Modern, high-performance compression</li>
  111. <li><strong>br</strong> (Brotli) - Best compression ratios for text</li>
  112. <li><strong>identity</strong> - No compression (always available)</li>
  113. </ul>
  114. <h3>ETag-Based Caching</h3>
  115. <p>
  116. Each resource generates an ETag based on its content hash and encoding.
  117. The provider handles <code>If-None-Match</code> requests, returning
  118. <code>304 Not Modified</code> when the client's cached version is current.
  119. </p>
  120. <spry-component name="CodeBlockComponent" sid="etag-code"/>
  121. </section>
  122. <section class="doc-section">
  123. <h2>Using Resources in Templates</h2>
  124. <p>
  125. Spry provides the <code>spry-res</code> attribute for easily referencing
  126. static resources in your HTML templates. This attribute automatically
  127. generates the correct URL for your resource.
  128. </p>
  129. <spry-component name="CodeBlockComponent" sid="template-usage"/>
  130. <p>
  131. The <code>spry-res</code> attribute works with any element that has
  132. <code>href</code> or <code>src</code> attributes:
  133. </p>
  134. <ul>
  135. <li><code><link></code> for stylesheets</li>
  136. <li><code><script></code> for JavaScript</li>
  137. <li><code><img></code> for images</li>
  138. <li><code><source></code> for media elements</li>
  139. </ul>
  140. </section>
  141. <section class="doc-section">
  142. <h2>How It Works</h2>
  143. <div class="lifecycle-diagram">
  144. <div class="lifecycle-step">
  145. <div class="step-number">1</div>
  146. <div class="step-content">
  147. <h4>Build Time</h4>
  148. <p>Use spry-mkssr to generate .ssr files or Vala source files</p>
  149. </div>
  150. </div>
  151. <div class="lifecycle-arrow">↓</div>
  152. <div class="lifecycle-step">
  153. <div class="step-number">2</div>
  154. <div class="step-content">
  155. <h4>Registration</h4>
  156. <p>Register resources via dependency injection</p>
  157. </div>
  158. </div>
  159. <div class="lifecycle-arrow">↓</div>
  160. <div class="lifecycle-step">
  161. <div class="step-number">3</div>
  162. <div class="step-content">
  163. <h4>Request</h4>
  164. <p>Client requests /_spry/res/resource-name</p>
  165. </div>
  166. </div>
  167. <div class="lifecycle-arrow">↓</div>
  168. <div class="lifecycle-step">
  169. <div class="step-number">4</div>
  170. <div class="step-content">
  171. <h4>Negotiation</h4>
  172. <p>Server selects best encoding based on Accept-Encoding</p>
  173. </div>
  174. </div>
  175. <div class="lifecycle-arrow">↓</div>
  176. <div class="lifecycle-step">
  177. <div class="step-number">5</div>
  178. <div class="step-content">
  179. <h4>Response</h4>
  180. <p>Compressed content sent with ETag for caching</p>
  181. </div>
  182. </div>
  183. </div>
  184. </section>
  185. <section class="doc-section">
  186. <h2>Next Steps</h2>
  187. <div class="nav-cards">
  188. <a href="/static-resources/spry-mkssr" class="nav-card">
  189. <h3>spry-mkssr Tool →</h3>
  190. <p>Learn how to generate static resources</p>
  191. </a>
  192. </div>
  193. </section>
  194. </div>
  195. """;
  196. }}
  197. public override async void prepare() throws Error {
  198. var interface_code = get_component_child<CodeBlockComponent>("interface-code");
  199. interface_code.language = "Vala";
  200. interface_code.code = "public interface StaticResource : Object {\n" +
  201. " // The unique name of this resource (e.g., \"docs.css\")\n" +
  202. " public abstract string name { get; }\n\n" +
  203. " // Returns the best encoding supported by the client\n" +
  204. " public abstract string get_best_encoding(Set<string> supported);\n\n" +
  205. " // Returns the ETag for a specific encoding\n" +
  206. " public abstract string get_etag_for(string encoding);\n\n" +
  207. " // Converts the resource to an HTTP result\n" +
  208. " public abstract HttpResult to_result(string encoding, \n" +
  209. " StatusCode status = StatusCode.OK) \n" +
  210. " throws Error;\n" +
  211. "}";
  212. var etag_code = get_component_child<CodeBlockComponent>("etag-code");
  213. etag_code.language = "Vala";
  214. etag_code.code = "// ETag format: \"{encoding}-{hash}\"\n" +
  215. "// Example: \"br-a1b2c3d4e5f6...\"\n\n" +
  216. "// The provider checks If-None-Match automatically:\n" +
  217. "if (request.headers.get_or_empty(\"If-None-Match\").contains(etag)) {\n" +
  218. " return new HttpEmptyResult(StatusCode.NOT_MODIFIED);\n" +
  219. "}\n\n" +
  220. "// Fresh content is returned with the ETag header\n" +
  221. "response.set_header(\"ETag\", etag);";
  222. var template_usage = get_component_child<CodeBlockComponent>("template-usage");
  223. template_usage.language = "HTML";
  224. template_usage.code = "<!-- Stylesheets -->\n" +
  225. "<link rel=\"stylesheet\" spry-res=\"docs.css\">\n\n" +
  226. "<!-- JavaScript -->\n" +
  227. "<script spry-res=\"htmx.js\"></script>\n" +
  228. "<script spry-res=\"htmx-sse.js\"></script>\n\n" +
  229. "<!-- Images -->\n" +
  230. "<img spry-res=\"logo.png\" alt=\"Logo\">\n\n" +
  231. "<!-- Results in URLs like: -->\n" +
  232. "<!-- /_spry/res/docs.css -->\n" +
  233. "<!-- /_spry/res/htmx.js -->";
  234. }
  235. }