Proposal hmpl

1.0.0 / March 9, 2025

HMPL Template Language Specification

Overview

The HMPL is a declarative template language designed for server-side UI rendering with client-side interactivity. The language combines:

  • JSON5-based syntax for request configuration
  • HTML templating capabilities
  • Integrated security via DOMPurify
  • Event-driven updates

1 Introduction

HMPL enables efficient UI rendering through customizable server requests initiated via the Fetch API, with responses processed into ready-made HTML. Reduce the size of your javascript files and display the same UI as if it was written in a modern framework and apply Server-Side Rendering, Static Site Generation, Incremental Static Generation (SSR, SSG, ISG) without robot indexing on any sites without Next.js, Remix, Nuxt.js.

1.1 Features

  • Customizable: Provides the ability to initiate custom server requests for dynamically receiving user interface content, enabling fine-grained control over request structure and behavior.
  • Memory Preserving: Significantly reduces client-side file sizes by offloading rendering logic to the server, minimizing front-end resource requirements.
  • Standards-Based (Fetch API): Utilizes the modern Fetch API for performing network requests, offering a standardized, promise-based alternative to legacy XMLHttpRequest.
  • Server-Oriented: Designed to prioritize server-driven rendering workflows, enabling UI generation through declarative markup with minimal JavaScript involvement.
  • High-Volume DOM Generation: Supports the efficient creation of large quantities of DOM nodes from a single template, enabling scalable component rendering both on the server and the client.
  • Simplicity: Allows ready-to-render UI content to be retrieved from the server using concise, familiar object notation requiring minimal code.
  • XSS Protection: Integrates HTML sanitization using DOMPurify, ensuring the safe handling of incoming server-generated markup to mitigate cross-site scripting (XSS) vulnerabilities.
  • Flexible Integration: Capable of operating within a wide range of project types, supporting both script-based usage and dedicated markup files with the .hmpl file extension.
  • JSON5 Compatibility: Provides flexible and expressive object syntax via JSON5, ensuring developer-friendly notation and leveraging a widely adopted, reliable parser.
  • Compact Bundle Size: Delivers substantial functionality in a minimal bundle footprint, typically limited to a few kilobytes.

1.2 Example

import hmpl from "hmpl-js";
  
const templateFn = hmpl.compile(
  `<div>
      <button data-action="increment" id="btn">Click!</button>
      <div>Clicks: {{
        "src": "/api/clicks",
        "after": "click:#btn"
      }}</div>
  </div>`
);

const clicker = templateFn(({ request: { event } }) => ({
  body: JSON.stringify({ action: event.target.getAttribute("data-action") })
})).response;

document.querySelector("#app").append(clicker);

2 hmpl

The global hmpl object serves as the primary interface for template compilation and request serialization. This object is automatically available in the global scope without requiring explicit import.

Table 1: Methods
Method Type Signature Description
compile HMPLCompile Transforms template strings into executable template functions
stringify (info: HMPLRequestInfo) => string Serializes request configuration objects to JSON5 strings

2.1 compile

The compile method processes HMPL template strings into executable template functions (HMPLTemplateFunction).

Table 2: Parameters
Parameter Type Description Required
template string HMPL template string containing HTML markup and request configurations Yes
options HMPLCompileOptions Compilation configuration parameters No
Example (Informative)
const templateFn = hmpl.compile(
  `{ 
     {
       "src":"/api/test" 
     } 
   }`,
  {
    memo: true,
    autoBody: {
      formData: true
    },
    allowedContentTypes: ["text/html"],
    disallowedTags: ["script"],
    sanitize: false
  }
);

2.1.1 Template Syntax

HMPL templates consist of standard HTML markup interspersed with request configuration objects delimited by single curly braces ({$config}).

Example (Informative)
<div>
  <button data-action="increment" id="btn">Click!</button>
  <div>Clicks: {{ "src": "/api/clicks", "after": "click:#btn" }}</div>
</div>

Key characteristics:

  • Request configurations must be valid JSON5
  • Nested within {} delimiters
  • May appear anywhere within HTML markup

2.1.2 Compilation Options

The options parameter establishes default behaviors for all requests generated by the compiled template function.

Table 3: Option Properties
Property Type Default Description
memo boolean false Enables response caching for identical requests
autoBody boolean | HMPLAutoBodyOptions false Configures automatic request body generation
allowedContentTypes HMPLContentTypes ["text/html"] Permitted response Content-Type headers
sanitize HMPLSanitize false Enables HTML sanitization of server responses
disallowedTags HMPLDisallowedTags [] HTML elements to remove from responses

Note: Request-specific configurations override these defaults when provided.

2.2 stringify

Serializes HMPLRequestInfo objects into JSON5 strings suitable for template embedding.

Example (Informative)
const request = hmpl.stringify({
  src: "/api/test"
});
const templateFn = hmpl.compile(`{${request}}`);

Implementation notes:

  • Uses JSON5 serialization for improved readability
  • Maintains all original object properties
  • Produces valid template syntax

3 Template Function

The HMPLTemplateFunction instantiates template executions, managing server requests and DOM updates. Each invocation creates an independent HMPLInstance.

Table 4: Parameters
Parameter Type Description
options HMPLIdentificationRequestInit[] | HMPLRequestInit | HMPLRequestInitFunction Configuration for request initialization and execution
Example (Informative)
const elementObj = templateFn();
// Returns: { response: div, status: 200 }

3.1 RequestInit

The HMPLRequestInit interface extends the standard RequestInit with HMPL-specific properties.

Table 5: Properties
Property Type Description
mode RequestMode The mode of the request (cors, no-cors, same-origin)
cache RequestCache The cache mode for the request (default, no-store, reload, etc.)
redirect RequestRedirect How to handle redirects (follow, error, manual)
referrerPolicy ReferrerPolicy Policy for the referrer header (no-referrer, same-origin, etc.)
integrity string Subresource integrity value for the request
referrer string The referrer URL for the request
get HMPLRequestGet Optional function to retrieve properties from the request
body BodyInit | null The body of the request (can be a string, FormData, etc.)
signal AbortSignal | null An AbortSignal to abort the request if needed
window any Reference to the window object (if applicable)
credentials RequestCredentials Credentials mode for the request (omit, same-origin, include)
headers HMPLHeadersInit Custom headers for the request
timeout number Optional timeout duration for the request in milliseconds
Example (Informative)
const elementObj = templateFn({
  mode: "cors",
  cache: "no-cache",
  credentials: "same-origin",
  headers: {
    "Content-Type": "text/html"
  },
  redirect: "follow",
  get: (prop, value) => {},
  referrerPolicy: "no-referrer",
  body: JSON.stringify(data),
  signal: new AbortController().signal,
  integrity: "...",
  window: null,
  refferer: "about:client"
});

3.1.1 get

The get property specifies a callback function that executes on property updates within the HMPLInstance.

Table 6: Callback Parameters
Parameter Type Description
prop string The name of the updated property
value any The new property value
requestObject HMPLRequest The associated request object (for multi-request templates)
Example 1 (Informative)
const elementObj = templateFn({
  get: (prop, value, requestObject) => {
    switch (prop) {
      case "response":
        if (!requestObject) console.log(requestObject);
        console.log("Response:");
        console.log(value);
        break;
      case "status":
        console.log("Status:");
        console.log(value);
        break;
    }
  }
});
Note
The get callback does not trigger for changes to individual array elements within the requests property. This design prevents excessive callbacks during batch updates.

For asynchronous operations, wrap the template function in a Promise:

Example 2 (Informative)
const val = await new Promise((res, rej) => {
  templateFn({
    get: (prop, value, requestObject) => {
      switch (prop) {
        case "response":
          if (!value) return;
          res(value);
          break;
      }
    }
  });
});

3.2 Identification RequestInit

The HMPLIdentificationRequestInit interface associates request configurations with specific template requests via unique identifiers.

Table 7: Properties
Property Type Description
id string | number Unique identifier matching template request's initId
value HMPLRequestInit | HMPLRequestInitFunction The initialization parameters for the referenced request
Example (Informative)
const elementObj = templateFn([
{
  id: "1",
  value: {
    mode: "cors",
    cache: "no-cache",
    credentials: "same-origin",
    headers: {
      "Content-Type": "text/html"
    },
    redirect: "follow",
    get: (prop, value) => {},
    referrerPolicy: "no-referrer",
    body: JSON.stringify(data)
  }
}]);
Note

When passing an array of initialization objects, requests without matching initId values execute with default configurations. Each template request must explicitly declare its initId to receive custom initialization.

3.2.1 Instance Structure

The structure of the returned HMPLInstance depends on the template's request configuration:

3.2.1.1 Single Request Template

Example (Informative)
const templateFn = hmpl.compile(
  `{ 
     {
       "src":"/api/test" 
     } 
   }`
);
const elementObj = templateFn();
Table 8
Property Type Description
status number HTTP status code of the response
response Element Rendered DOM element

3.2.1.2 Multi-Request Template

Example (Informative)
const templateFn = hmpl.compile(
  `<div>
   {
     {
       "src":"/api/test"
     }
   }
   {
     {
       "src":"/api/test"
     }
   }
</div>`
);
const elementObj = templateFn();
Table 9
Property Type Description
response Element Container DOM element
requests HMPLRequest[] Array of individual request results

Each item in the requests array contains status and response properties for its respective request.

3.3 RequestInit Function

The HMPLRequestInitFunction enables dynamic request configuration based on execution context.

Table 10: Parameters
Parameter Type Description
context HMPLInstanceContext Execution context containing request information
Example (Informative)
const elementObj = templateFn(({
  request: { event }
})=>{
  return {
      mode: "cors",
      cache: "no-cache",
      credentials: "same-origin",
      headers: {
        "Content-Type": "text/html",
      },
      redirect: "follow",
      get: (prop, value) => {},
      referrerPolicy: "no-referrer",
      body: new FormatData(event.target, event.submitter),
    }
});

4 Execution Context

HMPL provides an execution context that exposes request-specific information during template instantiation.

Table 11: Context Structure
Property Type Description
request HMPLRequestContext Contains information about the triggering request
Example 1 (Informative)
<div>
  <button id="getHTML">Get HTML!</button>
  {{ "src":"/api/getHTML", method:"POST", after:"click:#getHTML" }}
</div>
Example 2 (Informative)
const initFn = (ctx) => {
  const event = ctx.request.event;
  const text = event.target.textContent;
  return {
    body: text
  };
};
const elementObj = templateFn(initFn);

5 Request Configuration

Request objects define how and when the template makes server requests. These JSON5 objects appear within template markup and support the following properties:

Table 12: Properties
Property Type Default Description
src string Required The source URL of the request
method string "GET" The HTTP method used for the request
initId string | number undefined Identifier for request initialization
after string undefined Event trigger specification
repeat boolean true Whether the request repeats on subsequent triggers
memo boolean false Enables response caching
allowedContentTypes HMPLContentTypes ["text/html"] Permitted response Content-Types
indicators HMPLIndicator[] [] Status-specific UI indicators
sanitize HMPLSanitize false Enables HTML sanitization
disallowedTags HMPLDisallowedTags [] HTML elements to remove from responses
autoBody boolean | HMPLAutoBodyOptions false Configures automatic body generation
Example (Informative)
{
  {
    src: "/api/test",
    method: "get",
    after: "click:.target",
    repeat: true,
    indicators: [
      {
        trigger: "pending",
        content: "<p>Loading...</p>"
      },
      {
        trigger: "rejected",
        content: "<p>Error</p><button>reload</button>"
      }
    ],
    autoBody: {
      formData: true
    },
    sanitize: false,
    disallowedTags: ["script", "style", "iframe"],
    memo: true,
    initId: "id1"
  }
}

5.1 src

The src property specifies the target URL for the request. This property is required for all request configurations.

Table 13: URL Resolution
Example Resolution
"src":"http://github.com/api" Absolute URL used as-is
"src":"/api/test" Resolved relative to current origin

5.2 method

The method property defines the HTTP verb for the request. Supported methods include:

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE

5.3 after

The after property specifies event-based request triggering using the format:

${eventType}:${selector}
Table 14: Supported Events
Event Type Description
click Mouse click events
submit Form submission events
change Input change events
Note
Selectors are scoped to the template's DOM fragment or elements, not the global document.

5.4 repeat

The repeat property controls whether a request re-executes on subsequent trigger events:

  • true: Request executes on every trigger (default)
  • false: Request executes once, then removes listeners

5.5 indicators

The indicators property is intended to define the HTML content that should be rendered in response to a particular request status. This property enables authors to provide user interface feedback corresponding to specific states of a request initiated by the HMPL module.

The value of the indicators property is either a single HMPLIndicator object or an array of such objects. Each object specifies a trigger condition and associated content, representing the HTML markup to be displayed when the corresponding status is active.

The value of the content property is a string containing static HTML markup. This markup is not interpreted or extended by the HMPL module, and therefore does not support HMPL-specific syntax or dynamic bindings.

The trigger property specifies the status condition under which the associated content should be displayed. Its value may be one of the following:

Table 15
Property Type Description
trigger HMPLIndicatorTrigger Condition under which the corresponding content is displayed.
content string HTML markup to be rendered in response to the trigger.

The permissible values for trigger are:

  • HTTP status codes within the range 400 to 599, indicating client and server errors as defined by the HTTP specification.
  • The string literal "pending", representing the Promise state where a request is in progress and awaiting resolution.
  • The string literal "rejected", representing a Promise rejection or a failed fetch operation.
  • The string literal "error", a generic value that serves as a catch-all trigger, activated under either of the following circumstances:
    • A HTTP response with a status code between 400 and 599.
    • A "rejected" Promise state.
Note
The "error" trigger value overlaps with "rejected" and any HTTP status codes in the range 400 to 599. This allows authors to define a default fallback indicator without the need to enumerate all possible error conditions individually.

5.6 autoBody

The autoBody property specifies automatic generation of the body property within a HMPLRequestInit object. Its purpose is to facilitate the automatic population of request bodies from form-related user input when a request is initiated via HMPL.

The property accepts either a Boolean value or an HMPLAutoBodyOptions object, determining how the request body should be constructed.

Example 1 (Informative)
<div>
  <form onsubmit="function prevent(e){e.preventDefault();};return prevent(event);" id="form">
    <div class="form-example">
      <label for="name">Enter your email:</label>
      <input type="text" name="email" id="email" required />
    </div>
    <div class="form-example">
      <input type="submit" value="Subscribe!" />
    </div>
  </form>
  {
    {
      "src":"/api/subscribe",
      "after":"submit:#form",
      "autoBody":true
    }
  }
</div>

If the value of autoBody is true, the module automatically generates a FormData instance using the associated SubmitEvent target element and its submitter.

Table 16
Value Behavior
true Enables automatic FormData generation using default behavior.
false Disables automatic body generation. This is the default behavior.
HMPLAutoBodyOptions Enables configuration of specific generation parameters and behaviors.

When automatic generation is active, the body property in the resulting HMPLRequestInit will be assigned a FormData instance constructed as follows:

Example 2 (Informative)
const body = new FormData(event.target, event.submitter);

It is important to note that automatic generation will only overwrite the existing body property if the HMPLRequestInit object is not derived from a RequestInit function. In such cases, the manually specified body value takes precedence.

Note
As of the current specification, automatic body generation exclusively supports targets of type SubmitEvent originating from <form> elements. Future revisions of the HMPL module may extend this functionality to additional HTML element types, such as input, progress, and other interactive components where automatic body construction is applicable.

5.7 memo

The memo property enables response caching for identical requests. When enabled:

  • Identical requests return cached responses
  • Only active when repeat is true
  • Improves performance for repeated requests

The memoization process can be conceptually compared to disabling the no-cache directive in the RequestCache API, as it prevents repeated fetching of the same resource within the same runtime environment.

Figure 1: Memoization Process
memoization
Note
For additional details regarding the behavior and implementation of memoization in HMPL, refer to the article Memoization in HMPL.

5.8 initId

The initId property establishes an explicit association between a request and a predefined initialization object. It references the id property of a corresponding entry in the HMPLRequestInit dictionary collection, determining the initialization parameters for the request.

The value assigned to initId may be either a string or a number, providing flexibility in identification schemes.

Example 1 (Informative)
<div>
  {
    {
      "src": "/api/test",
      "initId": "1"
    }
  }
  {
    {
      "src": "/api/test",
      "initId": 2
    }
  }
</div>
Example 2 (Informative)
const requestInits = [
  { id: "1", value: ... },
  { id: 2, value: ... },
];
const instance = templateFn(requestInits);

Multiple requests can reference the same initId, allowing them to share a common set of initialization options. This behavior is conceptually analogous to the use of foreign keys in relational databases, enabling reuse and normalization of request configurations.

5.9 allowedContentTypes

The allowedContentTypes property defines the list of permissible Content-Type values within the headers of HTTP responses returned from server requests. This property enables content validation and type safety when processing server responses.

The property value may be either:

  • The string literal "*", indicating that all content types compatible with the text method are allowed.
  • An array of string values, each representing an explicitly permitted Content-Type.

If the array is empty, behavior is equivalent to using "*" — all types supporting the text method are permitted.

Example 1 (Informative)
{
  {
    "allowedContentTypes": [
      "application/json; charset=utf-8",
      "text/plain"
    ]
  }
}

The default value of this property is ["text/html"].

Note
For security considerations, it is strongly recommended not to modify this property unless necessary. Accepting unknown or untrusted Content-Type values may lead to uncontrolled application behavior or vulnerabilities. For instance, receiving a response with Content-Type: application/octet-stream could result in the response body being delivered as raw binary data. In such a case, a simple message such as Hello, World! may arrive encoded as:
Example 2 (Informative)

    48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21
        

For additional guidance on server-side configuration and content type handling, refer to the Server Configuration documentation.

5.10 disallowedTags

The disallowedTags property specifies HTML elements to remove from responses. Supported tags include:

  • "script"
  • "style"
  • "iframe"

5.11 sanitize

The sanitize property enables DOMPurify-based HTML sanitization. When enabled:

  • Removes potentially malicious content
  • Preserves safe HTML markup
  • Executes before disallowed tag removal

6 Type Definitions

6.1 HMPLRequestInit

Extends standard RequestInit with HMPL-specific properties.

Example (Informative)
interface HMPLRequestOptions {
  mode?: RequestMode;
  cache?: RequestCache;
  redirect?: RequestRedirect;
  referrerPolicy?: ReferrerPolicy;
  integrity?: string;
  referrer?: string;
  get?: HMPLRequestGet;
  body?: BodyInit | null;
  signal?: AbortSignal | null;
  window?: any;
  credentials?: RequestCredentials;
  headers?: HMPLHeadersInit;
  timeout?: number;
}

6.2 HMPLInstance

Template execution result container.

Example (Informative)
interface HMPLInstance {
  response: undefined | Element | null;
  status?: HMPLRequestStatus;
  requests?: HMPLRequest[];
}

6.3 HMPLInstanceContext

Provides execution context to request initialization functions.

Example (Informative)
interface HMPLInstanceContext {
  request: HMPLRequestContext;
}

6.4 HMPLRequest

Individual request result container.

Example (Informative)
interface HMPLRequest {
  response: undefined | Element | null | ChildNode[];
  status: number;
  id?: string;
}

6.5 HMPLRequestContext

Contains request-specific execution details.

Example (Informative)
interface HMPLRequestContext {
  event?: Event;
}

6.6 HMPLRequestGet

Property change callback signature.

Example (Informative)
type HMPLRequestGet = (prop: string, value: any, request?: HMPLRequest) => void;

6.7 HMPLRequestInfo

Template request configuration interface.

Example (Informative)
interface HMPLRequestInfo {
  src: string;
  method?: string;
  initId?: string | number;
  after?: string;
  repeat?: boolean;
  memo?: boolean;
  allowedContentTypes?: HMPLContentTypes;
  indicators?: HMPLIndicator[];
  sanitize?: HMPLSanitize;
  disallowedTags?: HMPLDisallowedTags;
  autoBody?: boolean | HMPLAutoBodyOptions;
}

6.8 HMPLCompile

Template compilation function signature.

Example (Informative)
type HMPLCompile = (
  template: string,
  options?: HMPLCompileOptions
) => HMPLTemplateFunction;

6.9 HMPLCompileOptions

Template compilation configuration.

Example (Informative)
interface HMPLCompileOptions {
  memo?: boolean;
  autoBody?: boolean | HMPLAutoBodyOptions;
  allowedContentTypes?: HMPLContentTypes;
  sanitize?: HMPLSanitize;
  disallowedTags?: HMPLDisallowedTags;
}

6.10 HMPLTemplateFunction

Executable template function interface.

Example (Informative)
type HMPLTemplateFunction = (
  options?:
    | HMPLIdentificationRequestInit[]
    | HMPLRequestInit
    | HMPLRequestInitFunction
) => HMPLInstance;

6.11 HMPLAutoBodyOptions

Automatic body generation configuration.

Example (Informative)
interface HMPLAutoBodyOptions {
  formData?: boolean;
}

6.12 HMPLInitalStatus

Union of Promise states and non-success HTTP status codes.

Example (Informative)
type HMPLInitalStatus =
  | "pending"
  | "rejected"
  | 100
  | 101
  | 102
  | 103
  | 300
  | 301
  | 302
  | 303
  | 304
  | 305
  | 306
  | 307
  | 308
  | 400
  | 401
  | 402
  | 403
  | 404
  | 405
  | 406
  | 407
  | 408
  | 409
  | 410
  | 411
  | 412
  | 413
  | 414
  | 415
  | 416
  | 417
  | 418
  | 421
  | 422
  | 423
  | 424
  | 425
  | 426
  | 428
  | 429
  | 431
  | 451
  | 500
  | 501
  | 502
  | 503
  | 504
  | 505
  | 506
  | 507
  | 508
  | 510
  | 511;

6.13 HMPLIndicatorTrigger

Valid indicator display conditions.

Example (Informative)
type HMPLIndicatorTrigger = HMPLInitalStatus | "error";

6.14 HMPLRequestStatus

Union of all possible HTTP status codes and Promise states.

Example (Informative)
type HMPLRequestStatus =
  | HMPLInitalStatus
  | 200
  | 201
  | 202
  | 203
  | 204
  | 205
  | 206
  | 207
  | 208
  | 226;

6.15 HMPLContentTypes

Content-Type restriction specification.

Example (Informative)
type HMPLContentTypes = string[] | "*";

6.16 HMPLDisallowedTag

HTML elements eligible for removal.

Example (Informative)
type HMPLDisallowedTag = "script" | "style" | "iframe";

6.17 HMPLDisallowedTags

Array of disallowed elements.

Example (Informative)
type HMPLDisallowedTags = HMPLDisallowedTag[];

6.18 HMPLSanitize

HTML sanitization toggle.

Example (Informative)
type HMPLSanitize = boolean;

6.19 HMPLIndicator

Status-specific UI definition.

Example (Informative)
interface HMPLIndicator {
  trigger: HMPLIndicatorTrigger;
  content: string;
}

6.20 HMPLHeadersInit

Header dictionary interface.

Example (Informative)
interface HMPLHeadersInit {
  [key: string]: string;
}

6.21 HMPLIdentificationRequestInit

Request initialization reference container.

Example (Informative)
interface HMPLIdentificationRequestInit {
  value: HMPLRequestInit | HMPLRequestInitFunction;
  id: string | number;
}

6.22 HMPLRequestInitFunction

Dynamic request initialization signature.

Example (Informative)
type HMPLRequestInitFunction = (
  context: HMPLInstanceContext
) => HMPLRequestInit;