using Spry; using Inversion; /** * ComponentsMkcomponentPage - Documentation for the spry-mkcomponent tool * * This page explains how to use spry-mkcomponent to inject HTML markup * into Vala component files. */ public class ComponentsMkcomponentPage : PageComponent { public const string ROUTE = "/components/spry-mkcomponent"; private ComponentFactory factory = inject(); public override string markup { get { return """

spry-mkcomponent Tool

What is spry-mkcomponent?

spry-mkcomponent is a command-line tool that processes Vala component files by injecting HTML markup into the markup property getter. This allows you to maintain your HTML templates in separate files while keeping your Vala code clean and focused on logic.

The tool enables a cleaner separation of concerns:

  • Component logic - Vala code handling actions, state, and behavior
  • HTML templates - Separate .html files with your markup

Command-Line Usage

Options

Option Description
-o, --output FILE Output file path (default: input filename in build directory)
-h, --html FILE HTML template file (default: .html)
-v, --version Show version information
--help Show help message

How It Works

The tool takes two input files and produces a single output file with the HTML template embedded in the markup property:

💡 Key Concept: The tool looks for an empty markup property getter and injects the HTML content. If the getter already has content, the file is passed through unchanged.

Example Transformation

Here's a complete example showing how the tool transforms your component files:

Input Vala File (UserContentComponent.vala)

Input HTML File (UserContentComponent.vala.html)

Output Vala File (UserContentComponent.vala)

Notice how the empty markup getter now returns the HTML content as a verbatim string literal. The sid attributes in your HTML are preserved, allowing you to access elements via this["sid"] in your Vala code.

Integrating with Meson

The recommended way to use spry-mkcomponent is with meson's build system. You can use either generator() for processing multiple files or custom_target() for single files with custom options.

Using generator() for Multiple Files

The generator() function is ideal when you have multiple component files that all follow the same pattern (HTML file named .vala.html).

Using custom_target() for Single Files

Use custom_target() when you need more control over file paths or want to specify a custom HTML template location.

Complete meson.build Example

💡 Tip: The @INPUT@ and @OUTPUT@ placeholders are automatically replaced by meson with the actual file paths. The @BASENAME@ placeholder is replaced with the input filename without extension.

HTML Template Guidelines

When creating HTML templates for use with spry-mkcomponent, follow these guidelines:

  • Use sid attributes - Add sid="name" to elements you need to access from Vala code via this["name"]
  • Keep templates alongside Vala files - Name them ComponentName.vala.html for automatic discovery
  • Use HTMX attributes - Add hx-* attributes for interactivity without writing JavaScript
  • Avoid complex logic - Keep templates focused on structure; use Vala for conditional rendering

Workflow Integration

A typical development workflow with spry-mkcomponent looks like this:

  1. Create your Vala component with an empty markup getter
  2. Create an HTML template file with the same base name
  3. Build your project - meson runs spry-mkcomponent automatically
  4. Iterate - Edit either file; changes are picked up on rebuild

During development, you can run the tool manually to preview the generated output:

When to Use spry-mkcomponent

✅ Good Use Cases

  • Components with substantial HTML markup
  • Teams where designers work on HTML separately from developers
  • Reusing HTML templates across multiple components
  • Keeping Vala files focused on logic

❌ When to Skip It

  • Simple components with minimal markup
  • Quick prototypes where separation isn't beneficial
  • Components with dynamically generated markup

💡 Remember: You can always inline markup directly in your Vala files. spry-mkcomponent is opt-in - use it when the separation provides value for your project.

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-mkcomponent [OPTIONS] \n\n" + "# Examples:\n" + "spry-mkcomponent Components/UserComponent.vala\n" + "spry-mkcomponent -o build/UserComponent.vala Components/UserComponent.vala\n" + "spry-mkcomponent --html Templates/User.html Components/UserComponent.vala"; var workflow_diagram = get_component_child("workflow-diagram"); workflow_diagram.language = "Text"; workflow_diagram.code = "┌─────────────────────────────┐ ┌─────────────────────────────┐\n" + "│ Component.vala │ │ Component.vala.html │\n" + "│ (empty markup getter) │ │ (HTML template) │\n" + "└─────────────┬───────────────┘ └─────────────┬───────────────┘\n" + " │ │\n" + " └───────────────┬───────────────────┘\n" + " │\n" + " ▼\n" + " ┌─────────────────┐\n" + " │ spry-mkcomponent │\n" + " └────────┬────────┘\n" + " │\n" + " ▼\n" + " ┌─────────────────────────────┐\n" + " │ Component.vala │\n" + " │ (with generated getter) │\n" + " └─────────────────────────────┘"; var input_vala = get_component_child("input-vala"); input_vala.language = "Vala"; input_vala.code = "class UserContentComponent : Component {\n" + " public override string markup { get; }\n\n" + " public async override void handle_action(string action) throws Error {\n" + " this[\"message\"].text_content = \"Hello, World!\";\n" + " }\n" + "}"; var input_html = get_component_child("input-html"); input_html.language = "HTML"; input_html.code = "

You said:

"; var output_vala = get_component_child("output-vala"); output_vala.language = "Vala"; output_vala.code = "class UserContentComponent : Component {\n" + " public override string markup { get {\n" + " return \"\"\"

You said:

\"\"\";\n" + " }}\n\n" + " public async override void handle_action(string action) throws Error {\n" + " this[\"message\"].text_content = \"Hello, World!\";\n" + " }\n" + "}"; var meson_generator = get_component_child("meson-generator"); meson_generator.language = "Meson"; meson_generator.code = "# Find the tool\n" + "spry_mkcomponent = find_program('spry-mkcomponent')\n\n" + "# Define the generator once\n" + "spry_mkcomponent_gen = generator(spry_mkcomponent,\n" + " output: '@BASENAME@.vala',\n" + " arguments: ['@INPUT@', '@OUTPUT@']\n" + ")\n\n" + "# Process multiple component files\n" + "generated_components = spry_mkcomponent_gen.process(files(\n" + " 'Components/UserContentComponent.vala',\n" + " 'Components/LoginFormComponent.vala',\n" + " 'Components/NavSidebarComponent.vala',\n" + "))\n\n" + "# Use in executable\n" + "executable('myapp',\n" + " sources: [other_sources, generated_components],\n" + " dependencies: [spry_dep]\n" + ")"; var meson_custom_target = get_component_child("meson-custom-target"); meson_custom_target.language = "Meson"; meson_custom_target.code = "# Simple case - HTML file is ComponentName.vala.html\n" + "user_content_component = custom_target('user-content-component',\n" + " input: 'Components/UserContentComponent.vala',\n" + " output: 'UserContentComponent.vala',\n" + " command: [spry_mkcomponent, '@INPUT@', '@OUTPUT@']\n" + ")\n\n" + "# With explicit HTML file path\n" + "login_form_component = custom_target('login-form-component',\n" + " input: 'Components/LoginFormComponent.vala',\n" + " output: 'LoginFormComponent.vala',\n" + " command: [spry_mkcomponent, '@INPUT@', '@OUTPUT@',\n" + " '--html', 'Templates/LoginForm.html']\n" + ")"; var meson_complete = get_component_child("meson-complete"); meson_complete.language = "Meson"; meson_complete.code = "# Find the spry-mkcomponent tool\n" + "spry_mkcomponent = find_program('spry-mkcomponent')\n\n" + "# Define generator for components\n" + "component_gen = generator(spry_mkcomponent,\n" + " output: '@BASENAME@.vala',\n" + " arguments: ['@INPUT@', '@OUTPUT@']\n" + ")\n\n" + "# Process all component files with HTML templates\n" + "generated_components = component_gen.process(files(\n" + " 'Components/HeaderComponent.vala',\n" + " 'Components/FooterComponent.vala',\n" + " 'Components/SidebarComponent.vala',\n" + "))\n\n" + "# Other source files (no HTML templates)\n" + "other_sources = files(\n" + " 'Main.vala',\n" + " 'Components/SimpleComponent.vala', # Inline markup\n" + ")\n\n" + "# Build executable with generated components\n" + "executable('myapp',\n" + " sources: [other_sources, generated_components],\n" + " dependencies: [spry_dep],\n" + " vala_args: ['--vapidir', spry_vapi_dir]\n" + ")"; var html_guidelines = get_component_child("html-guidelines"); html_guidelines.language = "HTML"; html_guidelines.code = "\n" + "
\n" + "

\n" + "

\n" + " \n" + "
\n\n" + "\n" + "
\n" + " \n" + " \n" + " \n" + "
"; var manual_run = get_component_child("manual-run"); manual_run.language = "Bash"; manual_run.code = "# Run manually to see generated output\n" + "spry-mkcomponent Components/MyComponent.vala -o /tmp/output.vala\n\n" + "# View the result\n" + "cat /tmp/output.vala\n\n" + "# Check if the tool would modify a file (compare with diff)\n" + "spry-mkcomponent Components/MyComponent.vala -o /tmp/test.vala && \\\n" + " diff Components/MyComponent.vala /tmp/test.vala"; } }