| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- using Spry;
- using Inversion;
- /**
- * StaticResourcesOverviewPage - Introduction to Spry Static Resources
- *
- * This page provides a comprehensive overview of static resource handling in Spry,
- * including the different types of resources and how to use them.
- */
- public class StaticResourcesOverviewPage : PageComponent {
-
- public const string ROUTE = "/static-resources/overview";
-
- private ComponentFactory factory = inject<ComponentFactory>();
-
- public override string markup { get {
- return """
- <div sid="page" class="doc-content">
- <h1>Static Resources Overview</h1>
-
- <section class="doc-section">
- <h2>What are Static Resources?</h2>
- <p>
- Spry provides a built-in system for serving static assets like CSS, JavaScript,
- images, and fonts. The static resource system handles content negotiation,
- compression, and caching automatically.
- </p>
- <p>
- Unlike traditional web frameworks that serve static files directly from disk,
- Spry's static resources are pre-compressed and can be embedded directly into
- your binary for single-binary deployments.
- </p>
- </section>
-
- <section class="doc-section">
- <h2>Types of Static Resources</h2>
- <p>
- Spry provides three implementations of the
- <code>StaticResource</code> interface:
- </p>
-
- <div class="resource-types">
- <div class="resource-type-card">
- <h3>FileStaticResource</h3>
- <p>
- Reads pre-compiled <code>.ssr</code> (Spry Static Resource) files from
- disk at runtime. These files contain the resource data along with
- pre-compressed versions (gzip, zstd, brotli) and metadata.
- </p>
- <ul>
- <li>Loaded from <code>.ssr</code> files generated by spry-mkssr</li>
- <li>Contains all compression variants in a single file</li>
- <li>Includes SHA-512 hash for ETags</li>
- </ul>
- </div>
-
- <div class="resource-type-card">
- <h3>MemoryStaticResource</h3>
- <p>
- Holds static resource data entirely in memory. Useful for
- dynamically generated resources or when you want to load
- resources from a custom source.
- </p>
- <ul>
- <li>All data held in RAM</li>
- <li>Supports all compression formats</li>
- <li>Created programmatically at runtime</li>
- </ul>
- </div>
-
- <div class="resource-type-card">
- <h3>ConstantStaticResource</h3>
- <p>
- Embeds static resources directly into your binary at compile time.
- The resource data becomes part of your executable, enabling true
- single-binary deployments.
- </p>
- <ul>
- <li>Compiled into your binary</li>
- <li>Generated by spry-mkssr with <code>--vala</code> flag</li>
- <li>Zero runtime file I/O</li>
- </ul>
- </div>
- </div>
- </section>
-
- <section class="doc-section">
- <h2>The StaticResource Interface</h2>
- <p>
- All static resources implement the <code>StaticResource</code> interface,
- which defines the contract for serving static content:
- </p>
-
- <spry-component name="CodeBlockComponent" sid="interface-code"/>
- </section>
-
- <section class="doc-section">
- <h2>The StaticResourceProvider Endpoint</h2>
- <p>
- Spry includes a built-in endpoint for serving static resources. The
- <code>StaticResourceProvider</code> handles all the complexity of content
- negotiation and caching.
- </p>
-
- <h3>Route Pattern</h3>
- <p>
- Static resources are served from the route:
- </p>
- <div class="code-inline">
- <code>/_spry/res/{resource}</code>
- </div>
- <p>
- Where <code>{resource}</code> is the name of the resource (e.g.,
- <code>docs.css</code>, <code>htmx.js</code>).
- </p>
-
- <h3>Content Negotiation</h3>
- <p>
- The provider automatically selects the best compression format based on
- the client's <code>Accept-Encoding</code> header. Supported encodings:
- </p>
- <ul>
- <li><strong>gzip</strong> - Universal browser support</li>
- <li><strong>zstd</strong> - Modern, high-performance compression</li>
- <li><strong>br</strong> (Brotli) - Best compression ratios for text</li>
- <li><strong>identity</strong> - No compression (always available)</li>
- </ul>
-
- <h3>ETag-Based Caching</h3>
- <p>
- Each resource generates an ETag based on its content hash and encoding.
- The provider handles <code>If-None-Match</code> requests, returning
- <code>304 Not Modified</code> when the client's cached version is current.
- </p>
-
- <spry-component name="CodeBlockComponent" sid="etag-code"/>
- </section>
-
- <section class="doc-section">
- <h2>Using Resources in Templates</h2>
- <p>
- Spry provides the <code>spry-res</code> attribute for easily referencing
- static resources in your HTML templates. This attribute automatically
- generates the correct URL for your resource.
- </p>
-
- <spry-component name="CodeBlockComponent" sid="template-usage"/>
-
- <p>
- The <code>spry-res</code> attribute works with any element that has
- <code>href</code> or <code>src</code> attributes:
- </p>
- <ul>
- <li><code><link></code> for stylesheets</li>
- <li><code><script></code> for JavaScript</li>
- <li><code><img></code> for images</li>
- <li><code><source></code> for media elements</li>
- </ul>
- </section>
-
- <section class="doc-section">
- <h2>How It Works</h2>
- <div class="lifecycle-diagram">
- <div class="lifecycle-step">
- <div class="step-number">1</div>
- <div class="step-content">
- <h4>Build Time</h4>
- <p>Use spry-mkssr to generate .ssr files or Vala source files</p>
- </div>
- </div>
- <div class="lifecycle-arrow">↓</div>
- <div class="lifecycle-step">
- <div class="step-number">2</div>
- <div class="step-content">
- <h4>Registration</h4>
- <p>Register resources via dependency injection</p>
- </div>
- </div>
- <div class="lifecycle-arrow">↓</div>
- <div class="lifecycle-step">
- <div class="step-number">3</div>
- <div class="step-content">
- <h4>Request</h4>
- <p>Client requests /_spry/res/resource-name</p>
- </div>
- </div>
- <div class="lifecycle-arrow">↓</div>
- <div class="lifecycle-step">
- <div class="step-number">4</div>
- <div class="step-content">
- <h4>Negotiation</h4>
- <p>Server selects best encoding based on Accept-Encoding</p>
- </div>
- </div>
- <div class="lifecycle-arrow">↓</div>
- <div class="lifecycle-step">
- <div class="step-number">5</div>
- <div class="step-content">
- <h4>Response</h4>
- <p>Compressed content sent with ETag for caching</p>
- </div>
- </div>
- </div>
- </section>
-
- <section class="doc-section">
- <h2>Next Steps</h2>
- <div class="nav-cards">
- <a href="/static-resources/spry-mkssr" class="nav-card">
- <h3>spry-mkssr Tool →</h3>
- <p>Learn how to generate static resources</p>
- </a>
- </div>
- </section>
- </div>
- """;
- }}
-
- public override async void prepare() throws Error {
- var interface_code = get_component_child<CodeBlockComponent>("interface-code");
- interface_code.language = "Vala";
- interface_code.code = "public interface StaticResource : Object {\n" +
- " // The unique name of this resource (e.g., \"docs.css\")\n" +
- " public abstract string name { get; }\n\n" +
- " // Returns the best encoding supported by the client\n" +
- " public abstract string get_best_encoding(Set<string> supported);\n\n" +
- " // Returns the ETag for a specific encoding\n" +
- " public abstract string get_etag_for(string encoding);\n\n" +
- " // Converts the resource to an HTTP result\n" +
- " public abstract HttpResult to_result(string encoding, \n" +
- " StatusCode status = StatusCode.OK) \n" +
- " throws Error;\n" +
- "}";
-
- var etag_code = get_component_child<CodeBlockComponent>("etag-code");
- etag_code.language = "Vala";
- etag_code.code = "// ETag format: \"{encoding}-{hash}\"\n" +
- "// Example: \"br-a1b2c3d4e5f6...\"\n\n" +
- "// The provider checks If-None-Match automatically:\n" +
- "if (request.headers.get_or_empty(\"If-None-Match\").contains(etag)) {\n" +
- " return new HttpEmptyResult(StatusCode.NOT_MODIFIED);\n" +
- "}\n\n" +
- "// Fresh content is returned with the ETag header\n" +
- "response.set_header(\"ETag\", etag);";
-
- var template_usage = get_component_child<CodeBlockComponent>("template-usage");
- template_usage.language = "HTML";
- template_usage.code = "<!-- Stylesheets -->\n" +
- "<link rel=\"stylesheet\" spry-res=\"docs.css\">\n\n" +
- "<!-- JavaScript -->\n" +
- "<script spry-res=\"htmx.js\"></script>\n" +
- "<script spry-res=\"htmx-sse.js\"></script>\n\n" +
- "<!-- Images -->\n" +
- "<img spry-res=\"logo.png\" alt=\"Logo\">\n\n" +
- "<!-- Results in URLs like: -->\n" +
- "<!-- /_spry/res/docs.css -->\n" +
- "<!-- /_spry/res/htmx.js -->";
- }
- }
|