using Spry; using Inversion; /** * StaticResourcesMkssrPage - Documentation for the spry-mkssr tool * * This page explains how to use spry-mkssr to generate static resources * for Spry applications. */ public class StaticResourcesMkssrPage : PageComponent { public const string ROUTE = "/static-resources/spry-mkssr"; private ComponentFactory factory = inject(); public override string markup { get { return """

spry-mkssr Tool

What is spry-mkssr?

spry-mkssr is a command-line tool that generates Spry Static Resource files from any input file. It handles compression, hashing, and metadata generation so your web application can serve optimized static content efficiently.

The tool can generate two types of output:

  • SSR files (.ssr) - Pre-compiled resource files read at runtime
  • Vala source files - Embedded resources compiled into your binary

Command-Line Usage

Options

Option Description
-o, --output=FILE Output file name (default: input.ssr or ClassNameResource.vala)
-c, --content-type=TYPE Override content type (e.g., text/css, application/javascript)
-n, --name=NAME Override resource name (default: input filename)
--vala Generate Vala source file instead of SSR
--ns=NAMESPACE Namespace for generated Vala class (requires --vala)
-v, --version Show version information

Generating SSR Files

By default, spry-mkssr generates .ssr files that can be read at runtime by FileStaticResource:

The generated SSR file contains:

  • Magic header - "spry-sr\0" identifier
  • Resource name - Used in URLs
  • Content type - MIME type for HTTP headers
  • SHA-512 hash - For ETag generation
  • Encoding headers - Offset and size for each encoding
  • Compressed data - gzip, zstd, brotli, and identity

Generating Vala Source Files

Use the --vala flag to generate a Vala source file that embeds the resource directly into your binary:

This creates a class that extends ConstantStaticResource with the resource data embedded as const uint8[] arrays.

Namespace Configuration

Use --ns to place the generated class in a namespace:

Generated Vala Class Structure

The generated Vala class follows this structure:

Integrating with Meson

The recommended way to use spry-mkssr is with meson's custom_target to generate resources at build time:

Complete meson.build Example

💡 Tip: The @INPUT@ and @OUTPUT@ placeholders are automatically replaced by meson with the actual file paths.

Compression Details

spry-mkssr compresses resources with the highest compression levels:

Encoding Level Best For
gzip 9 (maximum) Universal compatibility
zstd 19 (maximum) Modern browsers, fast decompression
brotli 11 (maximum) Best compression for text assets
identity N/A No compression (always included)

Encodings are only included if they result in smaller file sizes than the original. The tool reports compression statistics during generation:

Content Type Detection

spry-mkssr automatically detects content types based on file extensions using GLib's content type guessing. You can override this with the -c flag:

Resource Naming

By default, the resource name is derived from the input filename. For Vala source generation, the class name is created by converting the resource name to PascalCase and appending "Resource":

Input File Resource Name Generated Class
docs.css docs.css DocsResource.vala → DocsResource
htmx.min.js htmx.min.js HtmxMinResource.vala → HtmxMinResource
-n htmx.js htmx.min.js htmx.js HtmxResource.vala → HtmxResource

Use the -n flag to set a specific resource name:

Next Steps

"""; }} public override async void prepare() throws Error { var usage_code = get_component_child("usage-code"); usage_code.language = "Bash"; usage_code.code = "spry-mkssr [OPTION?] INPUT_FILE\n\n" + "# Examples:\n" + "spry-mkssr styles.css # Creates styles.css.ssr\n" + "spry-mkssr -o bundle.ssr app.js # Creates bundle.ssr\n" + "spry-mkssr --vala logo.png # Creates LogoResource.vala\n" + "spry-mkssr --vala --ns=MyApp styles.css # Creates StylesResource.vala in MyApp namespace"; var ssr_example = get_component_child("ssr-example"); ssr_example.language = "Bash"; ssr_example.code = "# Generate an SSR file from a CSS file\n" + "spry-mkssr styles.css\n" + "# Output: styles.css.ssr\n\n" + "# Generate with custom output name\n" + "spry-mkssr -o resources/bundle.css.ssr styles.css\n" + "# Output: resources/bundle.css.ssr\n\n" + "# The resource name in URLs will be \"styles.css\"\n" + "# URL: /_spry/res/styles.css"; var vala_example = get_component_child("vala-example"); vala_example.language = "Bash"; vala_example.code = "# Generate a Vala source file for embedding\n" + "spry-mkssr --vala styles.css\n" + "# Output: StylesResource.vala\n\n" + "# The generated class name is derived from the filename\n" + "# styles.css → StylesResource"; var namespace_example = get_component_child("namespace-example"); namespace_example.language = "Bash"; namespace_example.code = "# Generate with namespace\n" + "spry-mkssr --vala --ns=MyApp.Static styles.css\n" + "# Output: StylesResource.vala\n\n" + "# Generated class:\n" + "# namespace MyApp.Static {\n" + "# public class StylesResource : ConstantStaticResource {\n" + "# ...\n" + "# }\n" + "# }"; var class_structure = get_component_child("class-structure"); class_structure.language = "Vala"; class_structure.code = "// Generated by spry-mkssr\n" + "public class DocsResource : ConstantStaticResource {\n\n" + " // Resource metadata\n" + " public override string name { get { return \"docs.css\"; } }\n" + " public override string file_hash { get { return \"a1b2c3d4...\"; } }\n" + " public override string content_type { get { return \"text/css\"; } }\n\n" + " // Select best encoding based on client support\n" + " public override string get_best_encoding(Set supported) {\n" + " if (supported.has(\"br\")) return \"br\";\n" + " if (supported.has(\"zstd\")) return \"zstd\";\n" + " if (supported.has(\"gzip\")) return \"gzip\";\n" + " return \"identity\";\n" + " }\n\n" + " // Return the data for a specific encoding\n" + " public override unowned uint8[] get_encoding(string encoding) {\n" + " switch (encoding) {\n" + " case \"br\": return BR_DATA;\n" + " case \"zstd\": return ZSTD_DATA;\n" + " case \"gzip\": return GZIP_DATA;\n" + " default: return IDENTITY_DATA;\n" + " }\n" + " }\n\n" + " // Embedded compressed data\n" + " private const uint8[] IDENTITY_DATA = { /* ... */ };\n" + " private const uint8[] GZIP_DATA = { /* ... */ };\n" + " private const uint8[] ZSTD_DATA = { /* ... */ };\n" + " private const uint8[] BR_DATA = { /* ... */ };\n" + "}"; var meson_example = get_component_child("meson-example"); meson_example.language = "Meson"; meson_example.code = "# Generate a Vala resource file from CSS\n" + "docs_css_resource = custom_target('docs-css-resource',\n" + " input: 'Static/docs.css',\n" + " output: 'DocsCssResource.vala',\n" + " command: [spry_mkssr, '--vala', '--ns=Demo.Static', \n" + " '-n', 'docs.css', '-c', 'text/css', \n" + " '-o', '@OUTPUT@', '@INPUT@']\n" + ")\n\n" + "# Then include in your executable sources:\n" + "executable('my-app',\n" + " app_sources,\n" + " docs_css_resource, # Add generated file\n" + " dependencies: [spry_dep]\n" + ")"; var meson_complete = get_component_child("meson-complete"); meson_complete.language = "Meson"; meson_complete.code = "# Find the spry-mkssr tool\n" + "spry_mkssr = find_program('spry-mkssr')\n\n" + "# Generate multiple resources\n" + "css_resource = custom_target('css-resource',\n" + " input: 'Static/styles.css',\n" + " output: 'StylesResource.vala',\n" + " command: [spry_mkssr, '--vala', '--ns=MyApp.Resources',\n" + " '-o', '@OUTPUT@', '@INPUT@']\n" + ")\n\n" + "js_resource = custom_target('js-resource',\n" + " input: 'Static/app.js',\n" + " output: 'AppResource.vala',\n" + " command: [spry_mkssr, '--vala', '--ns=MyApp.Resources',\n" + " '-c', 'application/javascript',\n" + " '-o', '@OUTPUT@', '@INPUT@']\n" + ")\n\n" + "# Build executable with embedded resources\n" + "executable('my-app',\n" + " 'Main.vala',\n" + " css_resource,\n" + " js_resource,\n" + " dependencies: [spry_dep]\n" + ")"; var compression_output = get_component_child("compression-output"); compression_output.language = "Bash"; compression_output.code = "$ spry-mkssr --vala docs.css\n" + "Compressing with gzip...\n" + " gzip: 16094 -> 3245 bytes (20.2%)\n" + "Compressing with zstd...\n" + " zstd: 16094 -> 2987 bytes (18.6%)\n" + "Compressing with brotli...\n" + " brotli: 16094 -> 2654 bytes (16.5%)\n" + "Generated: DocsResource.vala"; var content_type_example = get_component_child("content-type-example"); content_type_example.language = "Bash"; content_type_example.code = "# Automatic detection (recommended)\n" + "spry-mkssr styles.css # → text/css\n" + "spry-mkssr app.js # → application/javascript\n" + "spry-mkssr logo.png # → image/png\n\n" + "# Manual override\n" + "spry-mkssr -c text/plain data.json # Force text/plain\n" + "spry-mkssr -c application/wasm module.wasm # Custom MIME type"; var naming_example = get_component_child("naming-example"); naming_example.language = "Bash"; naming_example.code = "# Default naming\n" + "spry-mkssr --vala htmx.min.js\n" + "# Resource: \"htmx.min.js\", Class: HtmxMinResource\n\n" + "# Custom resource name\n" + "spry-mkssr --vala -n htmx.js htmx.min.js\n" + "# Resource: \"htmx.js\", Class: HtmxResource\n" + "# URL: /_spry/res/htmx.js\n\n" + "# This is useful for:\n" + "# - Hiding .min suffix from URLs\n" + "# - Using versioned files with clean names\n" + "# - Renaming resources for organization"; } }