<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Luca Minuti</title>
    <description>The latest articles on DEV Community by Luca Minuti (@lminuti).</description>
    <link>https://dev.to/lminuti</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1244316%2Fbe4b896e-0ce9-4929-8922-e333c8aa5aec.jpg</url>
      <title>DEV Community: Luca Minuti</title>
      <link>https://dev.to/lminuti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lminuti"/>
    <language>en</language>
    <item>
      <title>Cosa sono gli embeddings?</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Mon, 23 Feb 2026 07:29:08 +0000</pubDate>
      <link>https://dev.to/lminuti/cosa-sono-gli-embeddings-38f0</link>
      <guid>https://dev.to/lminuti/cosa-sono-gli-embeddings-38f0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Questo articolo è parte del corso &lt;a href="https://www.wintech-italia.it/corsi/delphi/ai-intro/" rel="noopener noreferrer"&gt;AI con Delphi&lt;/a&gt; tenuto a febbraio da Wintech Italia. Per conoscere le prossime date, consultate il sito oppure contattate direttamente &lt;a href="https://www.wintech-italia.it/wintech/contatti/" rel="noopener noreferrer"&gt;Wintech Italia&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nell'ambito dell'intelligenza artificiale si sente spesso parlare del concetto di &lt;strong&gt;embedding&lt;/strong&gt;, sia per il funzionamento dei modelli di linguaggio generativi (&lt;strong&gt;LLM&lt;/strong&gt;) sia per alcune loro applicazioni, per esempio i &lt;strong&gt;RAG&lt;/strong&gt; (Retrieval Augmented Generation). Ma cosa sono gli embeddings? In termini molto semplici potremmo dire che un embedding è un vettore (una sequenza di numeri) che &lt;strong&gt;cattura il senso&lt;/strong&gt; di una parola o di una frase. Questo, nel campo dei modelli di linguaggio, è fondamentale, infatti è necessario trovare un modo per rappresentare le parole tramite dei numeri per poter usare le reti neurali alla base di questi sistemi. Il primo approccio che potrebbe venire in mente è creare una associazione che segua l'ordine alfabetico:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Parola&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Abaco&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Abate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Abbagliare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Abbaiare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Abate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;.....&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15000&lt;/td&gt;
&lt;td&gt;Zolfo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Il problema è che le reti neurali eseguono dei calcoli e in un esempio come il precedente anche una banale somma non avrebbe senso: aumentare di poco un valore non porterebbe a una parola dal significato più vicino rispetto ad una somma con un valore più grande. Per esempio se "Abaco" è 1 e "Abate" è 2, il fatto che siano numericamente vicini non indica nulla del loro significato.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cosa servirebbe
&lt;/h2&gt;

&lt;p&gt;A questo punto quello che ci serve è un modo per classificare le parole in base a dei criteri. Per fare un esempio potremmo usare come criteri &lt;em&gt;peso&lt;/em&gt; e &lt;em&gt;altezza&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcv2on4ut5dv0w3d2aekp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcv2on4ut5dv0w3d2aekp.jpg" alt="Grafico con peso e altezza" width="800" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dal grafico è evidente che le distanze tra le parole hanno un significato concreto. Il problema è che con vettori che rappresentano peso e altezza possiamo esprimere dei concetti molto limitati. Quindi quali criteri usare?&lt;/p&gt;

&lt;h2&gt;
  
  
  Come vengono calcolati
&lt;/h2&gt;

&lt;p&gt;Una delle tecniche usate per calcolare questi vettori che catturano il senso delle parole è &lt;a href="https://en.wikipedia.org/wiki/Word2vec" rel="noopener noreferrer"&gt;Word2Vec&lt;/a&gt;. Word2Vec si basa sul concetto che &lt;strong&gt;parole simili si usano in frasi simili&lt;/strong&gt;. In pratica con questo approccio si addestra una rete neurale a trovare la parola mancante in frasi da cui è stata tolta una parola (in gergo &lt;strong&gt;Filling the blank&lt;/strong&gt;).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Frase&lt;/th&gt;
&lt;th&gt;Parola mancante&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L'acqua del mare è _______&lt;/td&gt;
&lt;td&gt;salata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Il _______ abbaia al postino&lt;/td&gt;
&lt;td&gt;cane&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In inverno cade la _______ dal cielo&lt;/td&gt;
&lt;td&gt;neve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ho bevuto un bicchiere di _______ a cena&lt;/td&gt;
&lt;td&gt;vino&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ho bevuto un bicchiere di _______ a cena&lt;/td&gt;
&lt;td&gt;birra&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Come si vede dall'esempio nell'ultima frase ci sono più parole che possono andare bene e quello che ci aspettiamo è che i calcoli (e di conseguenza i pesi) della rete per quelle due parole dovranno essere simili.&lt;/p&gt;

&lt;p&gt;Usando Word2Vec si crea una rete che nello strato di input e di output ha un &lt;strong&gt;numero di neuroni pari alle parole del vocabolario&lt;/strong&gt;, il primo neurone sarà associato alla prima parola, il secondo alla seconda, e così via. Durante l'allenamento si passano le frasi ponendo a 1 i neuroni associati alle parole presenti nella frase e ci aspettiamo che nello strato di output venga posto a 1 il neurone associato alla parola da cercare.&lt;/p&gt;

&lt;p&gt;Una volta che la rete è stata addestrata non ci interessa tanto la rete in sé, ma i pesi associati a ciascun neurone dello strato di input (quindi alle parole del vocabolario). Questi pesi sono gli embeddings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qgdu32a2d4b40go3yeu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qgdu32a2d4b40go3yeu.jpg" alt="Rete neurale" width="715" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ma mentre nell'esempio precedente i valori rappresentavano peso e altezza, i valori che abbiamo trovato cosa rappresentano? In realtà nessuno lo sa in maniera chiara. Quello che conta però è che se proviamo a metterli in un grafico vediamo che effettivamente parole simili hanno dei vettori che si trovano vicini.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusione
&lt;/h2&gt;

&lt;p&gt;Gli embeddings sono quindi una rappresentazione numerica del significato delle parole, ottenuta addestrando una rete neurale su grandi quantità di testo. Anche se non sappiamo esattamente cosa rappresenti ciascun valore, il risultato è che parole con significati simili si trovano vicine nello spazio vettoriale, permettendo ai modelli di linguaggio di "comprendere" le relazioni tra i concetti e di lavorare efficacemente con il linguaggio naturale.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>delphi</category>
      <category>ai</category>
    </item>
    <item>
      <title>MCPConnect (EN)</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Mon, 12 Jan 2026 08:32:23 +0000</pubDate>
      <link>https://dev.to/lminuti/mcpconnect-en-2e37</link>
      <guid>https://dev.to/lminuti/mcpconnect-en-2e37</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is an automated translation from the original &lt;a href="https://dev.to/lminuti/mcp-connect-566p"&gt;Italian article&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Delphi MCP Connect&lt;/strong&gt; (&lt;a href="https://github.com/delphi-blocks/MCPConnect" rel="noopener noreferrer"&gt;MCPConnect&lt;/a&gt;) is a lightweight yet robust framework designed to dramatically simplify the creation of Model Context Protocol (MCP) Servers using &lt;a href="https://www.embarcadero.com/products/delphi/" rel="noopener noreferrer"&gt;Embarcadero Delphi&lt;/a&gt;. By leveraging &lt;em&gt;attributes&lt;/em&gt;, the framework allows you to reuse existing logic and standard Delphi classes, transforming them into protocol-aware server components. MCPConnect handles the serialization, routing, and context management required for the server-side implementation of the &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to MCP
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Model Context Protocol&lt;/strong&gt; is a framework developed by &lt;a href="https://www.anthropic.com/" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt; in 2024. Its purpose is to standardize how &lt;em&gt;large language models&lt;/em&gt; (LLMs) communicate and interact with external sources. This is necessary because LLM capabilities are often limited by their ability to produce text (tokens) without being able to act on the external world or generate responses based on anything beyond their training data.&lt;/p&gt;

&lt;p&gt;Actually, back in 2023, &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; had already attempted to expand the capabilities of their LLMs with a similar approach through a technology called "function-calling". The problem was that this technology, copied by most other vendors, was tied to individual product APIs. This meant anyone wanting to use it was forced to write multiple integrations.&lt;/p&gt;

&lt;p&gt;Some typical MCP use cases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local database access&lt;/strong&gt; - Directly query PostgreSQL, MySQL, or SQLite databases for data analysis and complex queries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Filesystem integration&lt;/strong&gt; - Gain the ability to read, search, and analyze files in your local system for development support or project analysis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Browser automation&lt;/strong&gt; - Control a browser for web scraping, automated testing, or interaction with web applications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Third-party APIs&lt;/strong&gt; - Integrate external services like Slack, GitHub, Linear, or Jira to manage tasks and communications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom enterprise tools&lt;/strong&gt; - Expose internal organizational APIs and tools to allow the LLM to interact with proprietary systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complex data manipulation&lt;/strong&gt; - Process Excel, CSV, or other structured formats with custom logic not available in standard tools&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;To achieve these capabilities, the MCP architecture is divided into three blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP Host&lt;/strong&gt;: The AI application that will use MCP, for example Claude Desktop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Client&lt;/strong&gt;: The component that connects to the MCP server and sends the new context (data retrieved from the server) to the host&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Server&lt;/strong&gt;: The program that provides the context. It essentially responds to calls by providing the information requested by the host through the client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1va6yt0dlp2bn0mezk9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1va6yt0dlp2bn0mezk9n.png" alt="Architecture" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Server
&lt;/h2&gt;

&lt;p&gt;The server is the heart of the &lt;em&gt;MCP&lt;/em&gt; protocol, the part that actually performs the necessary tasks. It can execute multiple functions: access filesystem documents, execute database queries, send messages via email or messaging systems, sync with a calendar, etc.&lt;/p&gt;

&lt;p&gt;To perform these tasks, the protocol defines three types of building blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt;: functions that the LLM can decide to call. Tools can interact with a database, call external APIs, modify files, or execute any type of logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources&lt;/strong&gt;: read-only datasets that can be requested by the LLM to insert information into the context. They can be documents, files, or any other type of structured data. (&lt;em&gt;currently not supported by MCPConnect&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts&lt;/strong&gt;: templates that can be invoked by the user (usually by writing the template name preceded by a slash) to generate a specific prompt for a particular operation. (&lt;em&gt;currently not supported by MCPConnect&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assuming you have a &lt;code&gt;TDocumentService&lt;/code&gt; class that implements some tools, you can configure the &lt;strong&gt;MCPConnect&lt;/strong&gt; server like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;uses&lt;/span&gt;
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JRPC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// This register the standard MCP API
&lt;/span&gt;  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="n"&gt;Demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DocumentService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Unit with your MCP classes
&lt;/span&gt;
&lt;span class="c1"&gt;// Create the JSON-RPC Server
&lt;/span&gt;&lt;span class="n"&gt;FJRPCServer&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TJRPCServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;FJRPCServer&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMCPConfig&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetServerName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'delphi-mcp-server'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetServerVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'2.0.0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterToolClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TDocumentService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Register your tool class here
&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Transport
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;MCP&lt;/em&gt; protocol uses &lt;a href="https://www.jsonrpc.org/specification" rel="noopener noreferrer"&gt;JSON-RPC&lt;/a&gt; to encode messages passing between client and server, while for the actual transport it supports two types of channels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stdio&lt;/strong&gt;: standard input and output similar to old CGI: essentially the application will be console-based with requests arriving entirely on standard input and responses sent to standard output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streamable HTTP&lt;/strong&gt;: HTTP communication with the ability to keep the channel open to allow the server to directly send notifications to the client without polling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;MCP Connect&lt;/strong&gt; supports both operating modes, and the choice depends on the project type and also the development phase. For example, debugging is generally much simpler in an &lt;em&gt;HTTP&lt;/em&gt; application compared to one using &lt;em&gt;stdio&lt;/em&gt;. The same application could also work in both modes depending on configuration or a startup parameter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;stdio&lt;/th&gt;
&lt;th&gt;Streamable HTTP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;Local + remote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;⭐ Low&lt;/td&gt;
&lt;td&gt;⭐⭐⭐ High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;Implicit&lt;/td&gt;
&lt;td&gt;Must configure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Advanced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;Complex&lt;/td&gt;
&lt;td&gt;Simple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use&lt;/td&gt;
&lt;td&gt;Dev / local tools&lt;/td&gt;
&lt;td&gt;Production / cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Continuing with the previous example, with &lt;strong&gt;MCPConnect&lt;/strong&gt; you can set &lt;em&gt;HTTP&lt;/em&gt; as the transport mechanism by specifying the technology (&lt;em&gt;Indy&lt;/em&gt; or &lt;em&gt;WebBroker&lt;/em&gt;) to use. For example, if you want to use &lt;em&gt;WebBroker&lt;/em&gt;, the configuration is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;uses&lt;/span&gt;
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JRPC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// This register the standard MCP API
&lt;/span&gt;  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebBroker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="n"&gt;Demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DocumentService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Unit with your MCP classes
&lt;/span&gt;
&lt;span class="c1"&gt;// Create the JSON-RPC Server
&lt;/span&gt;&lt;span class="n"&gt;FJRPCServer&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TJRPCServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;FJRPCServer&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMCPConfig&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;....&lt;/span&gt;

&lt;span class="c1"&gt;// Create and configure the Dispatcher
&lt;/span&gt;&lt;span class="n"&gt;FJRPCDispatcher&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TJRPCDispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Self should be the TWebModule
&lt;/span&gt;&lt;span class="n"&gt;FJRPCDispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathInfo&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;'/mcp'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Set the endpoint path
&lt;/span&gt;&lt;span class="n"&gt;FJRPCDispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FJRPCServer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Connect to the server
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the previous code, &lt;code&gt;FJRPCDispatcher&lt;/code&gt; will handle all incoming calls to the &lt;code&gt;/mcp&lt;/code&gt; path through the previously configured &lt;em&gt;JRPC&lt;/em&gt; server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools with MCP Connect
&lt;/h2&gt;

&lt;p&gt;The most interesting aspect of MCPConnect is that it allows you to write MCP servers without worrying about the protocol architecture details. For example, suppose you want to provide information to the LLM based on a category. In Delphi, you could write a class like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;  &lt;span class="n"&gt;TDocumentService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;ListDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ACategory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the information will end up in an LLM's context, even though MCPConnect also handles more complex return values (e.g., &lt;code&gt;TArray&amp;lt;TDocument&amp;gt;&lt;/code&gt;, &lt;code&gt;TStringList&lt;/code&gt;, &lt;code&gt;TImage&lt;/code&gt;, etc.), returning a string is often more than sufficient.&lt;/p&gt;

&lt;p&gt;Returning to our example, it doesn't matter how the &lt;code&gt;ListDocument&lt;/code&gt; method returns the data - MCPConnect will describe it to the LLM so it can decide how and when to call it.&lt;/p&gt;

&lt;p&gt;To do this, however, you need to "explain" to the LLM what the class is for and in particular what the method in question does. With &lt;em&gt;MCPConnect&lt;/em&gt;, this is done primarily through attributes. This way, the class declaration will be enriched as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;  &lt;span class="n"&gt;TDocumentService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt;
    &lt;span class="c1"&gt;// This method is published as an MCP tool
&lt;/span&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'doclist'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'List all the available documents'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;ListDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Document Category'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ACategory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// This method is NOT exposed because it lacks the [McpTool] attribute
&lt;/span&gt;    &lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="n"&gt;InternalStuff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;McpTool&lt;/code&gt; and &lt;code&gt;McpParam&lt;/code&gt; attributes take two parameters: the method name and the description. The description is essential - it's precisely based on this that the LLM understands what the element is for, whether to use it, and how to use it. Therefore, it's crucial to be as clear as possible in formulating the text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool Results
&lt;/h2&gt;

&lt;p&gt;In the example we've seen, the tool returns a string, which can often be sufficient, but obviously depends on the tool's purpose. We can divide tools into two broad groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Operational&lt;/strong&gt;: whose main purpose is to perform a concrete action. They can write files, modify database state, send emails, etc. In this case, the output could even be simply an indication of the operation's outcome, for example a boolean, or a string indicating the problem detected in case of error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Informational&lt;/strong&gt;: in this case, the tool searches for data and injects it into the context. Depending on the nature of the data, it might be sufficient to return it as a string or in a structured format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;MCP Connect&lt;/strong&gt; handles this transparently, so if the function returns a complex object, it will automatically convert it to the appropriate format.&lt;/p&gt;

&lt;p&gt;However, there's a particular case worth mentioning, which is the &lt;code&gt;TContentList&lt;/code&gt; class. This class maps fairly low-level to the output expected by MCP. In this case, the response can be composed of various sections, each of different type (&lt;em&gt;text&lt;/em&gt;, &lt;em&gt;image&lt;/em&gt;, &lt;em&gt;audio&lt;/em&gt;, &lt;em&gt;link&lt;/em&gt;, &lt;em&gt;blob&lt;/em&gt;). &lt;em&gt;MCPConnect&lt;/em&gt; allows you to simplify the use of this class through &lt;code&gt;TToolResultBuilder&lt;/code&gt; as you can see in the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;TDelphiDayTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuyTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AQuantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;TContentList&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;LTicketStream&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FCart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuyTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AQuantity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;FGC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LTicketStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Add the stream to the garbage collector
&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;LResultBuilder&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TToolResultBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;LResultBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Purchase completed successfully.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;LResultBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'image/png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LTicketStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;LResultBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Session Management
&lt;/h2&gt;

&lt;p&gt;An essential feature of MCP is session management, which is implicit with &lt;em&gt;stdio&lt;/em&gt; transport and explicit when using &lt;em&gt;Streamable HTTP&lt;/em&gt;. &lt;strong&gt;MCP Connect&lt;/strong&gt; handles all this transparently, and in any tool you'll be able to retrieve data from the current session. Depending on the configuration, the session can be an instance of the &lt;code&gt;TSessionData&lt;/code&gt; object (which actually saves information in JSON) or any object derived from &lt;code&gt;TSessionBase&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The following code shows how to configure an application to use a custom session (&lt;code&gt;TShoppingSession&lt;/code&gt;) to save the necessary information during the order creation process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;uses&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;FJRPCServer&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ISessionConfig&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TSessionIdLocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// default
&lt;/span&gt;      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetHeaderName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Mcp-Session-Id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// default
&lt;/span&gt;      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// 30 minutes timeout (default)
&lt;/span&gt;      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSessionClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TShoppingSession&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Use custom typed session
&lt;/span&gt;      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MCP Connect&lt;/strong&gt; is a young but already quite extensive library. In this article, I've quickly covered some of the main topics without going into too much detail. More comprehensive documentation can be found on the &lt;a href="https://github.com/delphi-blocks/MCPConnect" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; page. Additionally, the demos included in the sources present numerous cases from which you can learn some techniques for creating simple MCP servers.&lt;/p&gt;

&lt;p&gt;Other features of &lt;strong&gt;MCP Connect&lt;/strong&gt; that I haven't discussed but plan to write about soon include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context injection&lt;/li&gt;
&lt;li&gt;Fluent-configuration&lt;/li&gt;
&lt;li&gt;Garbage collection&lt;/li&gt;
&lt;li&gt;Tools namespacing&lt;/li&gt;
&lt;li&gt;JSON-RPC only server (no MCP)&lt;/li&gt;
&lt;li&gt;VCL/Firemonkey support&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>delphi</category>
      <category>mcp</category>
    </item>
    <item>
      <title>MCP Connect</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Mon, 12 Jan 2026 08:17:08 +0000</pubDate>
      <link>https://dev.to/lminuti/mcp-connect-566p</link>
      <guid>https://dev.to/lminuti/mcp-connect-566p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://dev.to/lminuti/mcpconnect-en-2e37"&gt;Read this article in English&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Delphi MCP Connect&lt;/strong&gt; (&lt;a href="https://github.com/delphi-blocks/MCPConnect" rel="noopener noreferrer"&gt;MCPConnect&lt;/a&gt;) è un framework leggero e robusto progettato per semplificare drasticamente la creazione di Server Model Context Protocol (MCP) utilizzando &lt;a href="https://www.embarcadero.com/products/delphi/" rel="noopener noreferrer"&gt;Embarcadero Delphi&lt;/a&gt;. Sfruttando gli &lt;em&gt;attributi&lt;/em&gt;, il framework consente di riutilizzare la logica esistente e le classi Delphi standard, trasformandole in componenti server consapevoli del protocollo. MCPConnect gestisce la serializzazione, il routing e la gestione del contesto richiesti per l'implementazione lato server del protocollo &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduzione a MCP
&lt;/h2&gt;

&lt;p&gt;Il &lt;strong&gt;Model Context Protocol&lt;/strong&gt; è un framework sviluppato da &lt;a href="https://www.anthropic.com/" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt; nel 2024. Il suo scopo è standardizzare il modo in cui i &lt;em&gt;modelli linguistici di grandi dimensioni&lt;/em&gt; (LLM) comunicano e interagiscono con le fonti esterne. Questo perché in molti casi le possibilità degli LLM sono vincolate dal fatto di poter produrre del testo (token) senza però poter agire in qualche modo sul mondo esterno o produrre risposte basate su qualcosa che non provenga dai loro dati di addestramento.&lt;/p&gt;

&lt;p&gt;In realtà nel 2023 &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; aveva già provato ad espandere le possibilità dei propri LLM con un approccio simile, tramite una tecnologia denominata "function-calling". Il problema è che questa tecnologia, copiata dalla maggior parte degli altri produttori era legata alle API dei singoli prodotti. Per questo chi avesse voluto usarla era obbligato a scrivere diverse integrazioni.&lt;/p&gt;

&lt;p&gt;Alcuni casi d'uso tipici di MCP possono essere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accesso a database locali&lt;/strong&gt; - Permettere di interrogare direttamente database PostgreSQL, MySQL o SQLite per analisi dati e query complesse&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integrazione con filesystem&lt;/strong&gt; - Ottenere la capacità di leggere, cercare e analizzare file nel tuo sistema locale per supporto allo sviluppo o analisi di progetti&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automazione browser&lt;/strong&gt; - Controllare un browser per web scraping, testing automatizzato o interazione con applicazioni web&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API di terze parti&lt;/strong&gt; - Integrare servizi esterni come Slack, GitHub, Linear o Jira per gestire task e comunicazioni&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tool personalizzati aziendali&lt;/strong&gt; - Esporre API e strumenti interni all'organizzazione per permettere all'LLM di interagire con sistemi proprietari&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manipolazione dati complessi&lt;/strong&gt; - Processare file Excel, CSV o altri formati strutturati con logica personalizzata non disponibile nei tool standard&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  L'architettura
&lt;/h2&gt;

&lt;p&gt;Per ottenere queste possibilità l'architettura di MCP è divisa in tre blocchi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP Host&lt;/strong&gt;: L'applicazione AI che userà MCP, per esempio Claude Desktop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Client&lt;/strong&gt;: Il componente che si connette con il server MCP e invia all'host il nuovo contesto (dati ricavati dal server)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Server&lt;/strong&gt;: Il programma che fornisce il contesto. In pratica risponde alle chiamate fornendo le informazioni richieste dall'host tramite il client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1va6yt0dlp2bn0mezk9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1va6yt0dlp2bn0mezk9n.png" alt="Architettura" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Il Server
&lt;/h2&gt;

&lt;p&gt;Il server è il cuore del protocollo &lt;em&gt;MCP&lt;/em&gt;, la parte che concretamente esegue i compiti necessari. Le funzioni che può eseguire sono molteplici: accedere ai documenti del file system, eseguire delle query su un database, inviare messaggi tramite mail o sistemi di messaggistica, sincronizzarsi con un calendario, ecc.&lt;/p&gt;

&lt;p&gt;Per eseguire questi compiti il protocollo prevede tre tipi di blocchi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt;: funzioni che l'LLM può decidere di chiamare. I tool possono interagire con un database, chiamare API esterne, modificare file o eseguire un qualsiasi tipo di logica.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risorse&lt;/strong&gt;: sono dei dataset read-only che possono essere richiesti dal LLM per inserire informazioni nel contesto. Possono essere documenti, file o qualsiasi altro tipo di dato strutturato. (&lt;em&gt;attualmente non supportato da MCPConnect&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts&lt;/strong&gt;: sono dei template che possono essere richiamati dall'utente (di solito scrivendo il nome del template preceduto da uno slash) per generare un prompt specifico per una particolare operazione. (&lt;em&gt;attualmente non supportato da MCPConnect&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supponendo di avere una classe &lt;code&gt;TDocumentService&lt;/code&gt; che implementa alcuni tool è possibile configurare il server di &lt;strong&gt;MCPConnect&lt;/strong&gt; in questo modo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;uses&lt;/span&gt;
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JRPC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// This register the standard MCP API
&lt;/span&gt;  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="n"&gt;Demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DocumentService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Unit with your MCP classes
&lt;/span&gt;
&lt;span class="c1"&gt;// Create the JSON-RPC Server
&lt;/span&gt;&lt;span class="n"&gt;FJRPCServer&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TJRPCServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;FJRPCServer&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMCPConfig&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetServerName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'delphi-mcp-server'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetServerVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'2.0.0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterToolClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TDocumentService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Register your tool class here
&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Il trasporto
&lt;/h2&gt;

&lt;p&gt;Il protocollo &lt;em&gt;MCP&lt;/em&gt; usa &lt;a href="https://www.jsonrpc.org/specification" rel="noopener noreferrer"&gt;JSON-RPC&lt;/a&gt; per codificare i messaggi che passano tra il client e il server mentre per quanto riguarda il trasporto vero e proprio supporta due tipo di canali:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stdio&lt;/strong&gt;: lo standard input e output in maniera analoga ai vecchi CGI: in pratica l'applicazione sarà di tipo consolle con le richieste che arrivano interamente sullo standard input e le risposte inviate sulla standard output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streamable HTTP&lt;/strong&gt;: una comunicazione HTTP con la possibilità di mantenere aperto il canale per permettere al server di inviare direttamente notifiche al client senza bisogno di fare polling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;MCP Connect&lt;/strong&gt; supporta entrambe le modalità di funzionamento e la scelta dipende dal tipo di progetto e anche dalla fase di sviluppo. Per esempio in genere il debug è molto più semplice in una applicazione &lt;em&gt;HTTP&lt;/em&gt; rispetto ad una che fa uso di &lt;em&gt;stdio&lt;/em&gt;. La stessa applicazione potrebbe anche funzionare in entrambe le modalità a seconda della configurazione o un parametro passato all'avvio.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspetto&lt;/th&gt;
&lt;th&gt;stdio&lt;/th&gt;
&lt;th&gt;Streamable HTTP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ambito&lt;/td&gt;
&lt;td&gt;Locale&lt;/td&gt;
&lt;td&gt;Locale + remoto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complessità&lt;/td&gt;
&lt;td&gt;⭐ Bassa&lt;/td&gt;
&lt;td&gt;⭐⭐⭐ Alta&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalabilità&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Sì&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sicurezza&lt;/td&gt;
&lt;td&gt;Implicita&lt;/td&gt;
&lt;td&gt;Da configurare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;Limitato&lt;/td&gt;
&lt;td&gt;Avanzato&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;Complesso&lt;/td&gt;
&lt;td&gt;Semplice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uso tipico&lt;/td&gt;
&lt;td&gt;Dev / tool locali&lt;/td&gt;
&lt;td&gt;Produzione / cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Riprendendo l'esempio precedente con &lt;strong&gt;MCPConnect&lt;/strong&gt; è possibile impostare &lt;em&gt;HTTP&lt;/em&gt; come meccanismo di trasporto specificando la tecnologia (&lt;em&gt;Indy&lt;/em&gt; o &lt;em&gt;WebBroker&lt;/em&gt;) da usare. Per esempio volendo usare &lt;em&gt;WebBroker&lt;/em&gt; la configurazione è la seguente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;uses&lt;/span&gt;
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JRPC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// This register the standard MCP API
&lt;/span&gt;  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebBroker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="n"&gt;Demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DocumentService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Unit with your MCP classes
&lt;/span&gt;
&lt;span class="c1"&gt;// Create the JSON-RPC Server
&lt;/span&gt;&lt;span class="n"&gt;FJRPCServer&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TJRPCServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;FJRPCServer&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMCPConfig&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;....&lt;/span&gt;

&lt;span class="c1"&gt;// Create and configure the Dispatcher
&lt;/span&gt;&lt;span class="n"&gt;FJRPCDispatcher&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TJRPCDispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Self should be the TWebModule
&lt;/span&gt;&lt;span class="n"&gt;FJRPCDispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathInfo&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;'/mcp'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Set the endpoint path
&lt;/span&gt;&lt;span class="n"&gt;FJRPCDispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FJRPCServer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Connect to the server
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con il codice precedente &lt;code&gt;FJRPCDispatcher&lt;/code&gt; si farà carico di rispondere a tutte le chiamate in arrivo al path &lt;code&gt;/mcp&lt;/code&gt; tramite il server &lt;em&gt;JRPC&lt;/em&gt; configurato in precedenza.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools con MCP Connect
&lt;/h2&gt;

&lt;p&gt;L'aspetto più interessante di MCPConnect è il fatto che permette di scrivere dei server MCP senza doversi preoccupare dei dettagli dell'architettura del protocollo. Supponiamo per esempio di voler fornire all'LLM delle informazioni in base a una categoria. In Delphi potremmo scrivere una classe di questo genere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;  &lt;span class="n"&gt;TDocumentService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;ListDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ACategory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le informazioni finiranno nel contesto di un LLM quindi, anche se MCPConnect gestisce anche dei valori di ritorno più complessi (es. &lt;code&gt;TArray&amp;lt;TDocument&amp;gt;&lt;/code&gt;, &lt;code&gt;TStringList&lt;/code&gt;, &lt;code&gt;TImage&lt;/code&gt;, ecc.), molto spesso restituire una stringa è più che sufficiente.&lt;/p&gt;

&lt;p&gt;Ritornando al nostro esempio non è importante come il metodo &lt;code&gt;ListDocument&lt;/code&gt; restituisca i dati MCPConnect si occuperà di descriverlo all'LLM perché possa decidere come e quando chiamarlo.&lt;/p&gt;

&lt;p&gt;Per poterlo fare però è necessario "spiegare" all'LLM a cosa serve la classe e in particolare il metodo in questione. Questo con &lt;em&gt;MCPConnect&lt;/em&gt; avviene principalmente attraverso degli attributi. In questo modo la dichiarazione della classe verrà arricchita come segue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;  &lt;span class="n"&gt;TDocumentService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt;
    &lt;span class="c1"&gt;// This method is published as an MCP tool
&lt;/span&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'doclist'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'List all the available documents'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;ListDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Document Category'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ACategory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// This method is NOT exposed because it lacks the [McpTool] attribute
&lt;/span&gt;    &lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="n"&gt;InternalStuff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gli attributi &lt;code&gt;McpTool&lt;/code&gt; e &lt;code&gt;McpParam&lt;/code&gt; prevedono due parametri: il nome del metodo e la descrizione. La descrizione è essenziale, infatti è proprio in base a questa che LLM capisce a cosa serve l'elemento in questione, se deve usarlo e come usarlo. Quindi è fondamentale essere il più possibile chiari nella formulazione del testo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Il risultato dei tool
&lt;/h2&gt;

&lt;p&gt;Nell'esempio che abbiamo visto il tool restituisce una stringa, molto spesso questo può essere sufficiente, ma ovviamente dipende dallo scopo del tool. Possiamo dividere i tool in due macro gruppi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Operativi&lt;/strong&gt;: il cui scopo principale è compiere un'azione concreta. Possono scrivere dei file, modificare lo stato di un database, inviare una mail, ecc. In questo caso l'output potrebbe anche essere semplicemente un indicazione dell'esito dell'operazione, ad esempio un booleano, oppure una stringa che indichi il problema rilevato in caso di errore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Informativi&lt;/strong&gt;: in questo caso il tool si occupa di cercare dei dati e di iniettarli nel contesto. A seconda della natura del dato potrebbe essere sufficiente restituirlo sotto forma di stringa o in formato strutturato.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;MCP Connect&lt;/strong&gt; si occupa di questo in maniera trasparente quindi in caso la funzione restituisca un oggetto complesso automaticamente lo convertirà nel formato appropriato.&lt;/p&gt;

&lt;p&gt;C'è però un caso particolare che vale la pena accennare ed è la classe &lt;code&gt;TContentList&lt;/code&gt;. Questa classe mappa abbastanza a basso livello l'output previsto da MCP. In questo caso la risposta può essere composta da varie sezioni, ognuna di tipo diverso (&lt;em&gt;text&lt;/em&gt;, &lt;em&gt;image&lt;/em&gt;, &lt;em&gt;audio&lt;/em&gt;, &lt;em&gt;link&lt;/em&gt;, &lt;em&gt;blob&lt;/em&gt;). &lt;em&gt;MCPConnect&lt;/em&gt; permette di semplificare l'utilizzo di questa classe tramite &lt;code&gt;TToolResultBuilder&lt;/code&gt; come è possibile vedere nell'esempio seguente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;TDelphiDayTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuyTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AQuantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;TContentList&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;LTicketStream&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FCart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuyTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AQuantity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;FGC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LTicketStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Add the stream to the garbage collector
&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;LResultBuilder&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TToolResultBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;LResultBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Purchase completed successfully.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;LResultBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'image/png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LTicketStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;LResultBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gestione della sessione
&lt;/h2&gt;

&lt;p&gt;Una funzione essenziale di MCP è la gestione delle sessioni, che sono implicite con il trasporto &lt;em&gt;stdio&lt;/em&gt; e esplicite se si usa &lt;em&gt;Streamable HTTP&lt;/em&gt;. &lt;strong&gt;MCP Connect&lt;/strong&gt; gestisce tutto questo in maniera trasparente e in qualsiasi tool sarà possibile andare a recuperare i dati dalla sessione corrente. A seconda della configurazione la sessione potrà essere una istanza dell'oggetto &lt;code&gt;TSessionData&lt;/code&gt; (che concretamente salva le informazioni in un JSON) o da un qualsiasi oggetto che derivi da &lt;code&gt;TSessionBase&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Il seguente codice mostra come configurare un'applicazione per usare una sessione custom (&lt;code&gt;TShoppingSession&lt;/code&gt;) per salvare le informazioni necessarie durante il processo di creazione di un ordine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;uses&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;MCPConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;FJRPCServer&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ISessionConfig&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TSessionIdLocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// default
&lt;/span&gt;      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetHeaderName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Mcp-Session-Id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// default
&lt;/span&gt;      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// 30 minutes timeout (default)
&lt;/span&gt;      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSessionClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TShoppingSession&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Use custom typed session
&lt;/span&gt;      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusione
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MCP Connect&lt;/strong&gt; è una libreria giovane ma già piuttosto vasta. In questo articolo ho trattato in maniera rapida alcuni degli argomenti principali senza entrare troppo nel dettaglio. Una documentazione più esaustiva può essere trovata nella pagina &lt;a href="https://github.com/delphi-blocks/MCPConnect" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Inoltre i demo presenti nei sorgenti presentano numerosi casi da cui è possibile imparare alcune tecniche per creare semplici server MCP.&lt;/p&gt;

&lt;p&gt;Altre caratteristiche di &lt;strong&gt;MCP Connect&lt;/strong&gt; di cui non ho parlato ma penso di scrivere qualcosa quanto prima sono:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context injection&lt;/li&gt;
&lt;li&gt;Fluent-configuration&lt;/li&gt;
&lt;li&gt;Garbage collection&lt;/li&gt;
&lt;li&gt;Tools namespacing&lt;/li&gt;
&lt;li&gt;JSON-RPC only server (no MCP)&lt;/li&gt;
&lt;li&gt;VCL/Firemokey support&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>opensource</category>
      <category>delphi</category>
      <category>mcp</category>
    </item>
    <item>
      <title>WiRL e WebBroker</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Wed, 17 Dec 2025 08:33:15 +0000</pubDate>
      <link>https://dev.to/lminuti/wirl-e-webbroker-60p</link>
      <guid>https://dev.to/lminuti/wirl-e-webbroker-60p</guid>
      <description>&lt;h2&gt;
  
  
  Introduzione a WebBroker
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WebBroker&lt;/strong&gt; è probabilmente la prima tecnologia sviluppata da Embarcadero (all'epoca ancora Borland) che ha cercato di integrare la programmazione &lt;em&gt;Web&lt;/em&gt; con &lt;a href="https://www.embarcadero.com/products/delphi" rel="noopener noreferrer"&gt;Delphi&lt;/a&gt;. Il sistema si basa sul componente &lt;code&gt;TWebModule&lt;/code&gt; (un particolare tipo di &lt;em&gt;DataModule&lt;/em&gt;) che è in grado di smistare le singole chiamate. Per fare questa operazione può agire in due modi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;WebAction&lt;/em&gt;: facendo doppio click sul &lt;em&gt;WebModule&lt;/em&gt; viene aperta una finestra popup dove è possibile creare una o più &lt;em&gt;WebAction&lt;/em&gt;, per ognuna di esse è possibile definire dei criteri per fare &lt;em&gt;match&lt;/em&gt; con la richiesta HTTP (metodo, url, ecc) e gestire un evento dove definire la risposta.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Dispatcher&lt;/em&gt;: in alternativa è possibile scrivere una classe che implementi l'interfaccia &lt;code&gt;IWebDispatch&lt;/code&gt; e registrarla sul &lt;em&gt;WebModule&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WiRL e WebBroker
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WiRL&lt;/strong&gt; usa quest'ultimo approccio. In effetti con l'ultima versione è stato introdotto un nuovo componente &lt;code&gt;TWiRLDispatcher&lt;/code&gt; che permette di inoltrare tutte le chiamate fatte a WebBroker verso WiRL.&lt;/p&gt;

&lt;p&gt;Il &lt;em&gt;dispatcher&lt;/em&gt; poi verificherà se esiste un &lt;em&gt;engine&lt;/em&gt; di WiRL configurato per gestire quella specifica richiesta. In caso positivo demanderà all'engine il compito di gestire la risposta altrimenti notificherà WebBroker di non essere in grado di produrla. In quest'ultimo caso WebBroker proverà con altri dispatcher o WebAction configurati. &lt;/p&gt;

&lt;p&gt;Dal punto di vista del codice quello che è necessario fare è creare una normalissima applicazione WebBroker (magari usando il wizard dedicato: File → New → Others → Web → Web Server Application) ed istanziare TWiRLServer e TWiRLDispatcher.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;interface&lt;/span&gt;

&lt;span class="k"&gt;uses&lt;/span&gt;
  &lt;span class="c1"&gt;// .............
&lt;/span&gt;  &lt;span class="n"&gt;WiRL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;WiRL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebBroker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt;
  &lt;span class="n"&gt;TMainWebModule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TWebModule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt;
    &lt;span class="n"&gt;FWiRLServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TWiRLServer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;FDispatcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TWiRLDispatcher&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt;
    &lt;span class="k"&gt;constructor&lt;/span&gt; &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AOwner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;destructor&lt;/span&gt; &lt;span class="n"&gt;Destroy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt;
  &lt;span class="n"&gt;WebModuleClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TComponentClass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TMainWebModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;implementation&lt;/span&gt;

&lt;span class="cm"&gt;{$R *.dfm}&lt;/span&gt;

&lt;span class="k"&gt;constructor&lt;/span&gt; &lt;span class="n"&gt;TMainWebModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AOwner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;inherited&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;FWiRLServer&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TWiRLServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;FWiRLServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddEngine&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TWiRLRESTEngine&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;'/rest'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetEngineName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'RESTEngine'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/app'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;FWiRLServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Active&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;True&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// È fondamentale che l'owner dei dispatcher sia Self, che deriva da TWebModule
&lt;/span&gt;  &lt;span class="c1"&gt;// In questo modo il dispatcher viene automaticamente registrato sul WebModule
&lt;/span&gt;  &lt;span class="n"&gt;FDispatcher&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TWiRLDispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;FDispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FWiRLServer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Vantaggi e svantaggi di WebBroker con WiRL
&lt;/h2&gt;

&lt;p&gt;A questo punto ci si potrà chiedere quali sono i vantaggi nell'usare una soluzione basata su WebBroker piuttosto che quella classica basata sui componenti &lt;em&gt;Indy&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I &lt;strong&gt;vantaggi&lt;/strong&gt; principali sono i seguenti:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Possibilità di integrare servizi REST in applicazioni WebBroker preesistenti.&lt;/li&gt;
&lt;li&gt;La possibilità di generare applicazioni integrate con le architetture supportate da WebBroker: Moduli Apache, ISAPI, CGI, FastCGI (introdotta da Delphi 13), Standalone.&lt;/li&gt;
&lt;li&gt;Usare le funzionalità specifiche di WebBroker: logging nativo e sessioni.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gli &lt;strong&gt;svantaggi&lt;/strong&gt; sono principalmente relativi ad un certo aumento di complessità che l'uso di WebBroker comporta e ad alcune funzionalità non disponibili, come ad esempio &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt; (Server-Sent Events) e &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding#chunked" rel="noopener noreferrer"&gt;chunked transfer encoding&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Personalmente consiglio di utilizzare l'architettura tradizionale basata su &lt;strong&gt;Indy&lt;/strong&gt; per i nuovi progetti WiRL, a meno che non si debba integrare servizi ReST in un'applicazione WebBroker preesistente o si abbia l'esigenza specifica di utilizzare una delle architetture supportate da WebBroker (come moduli Apache, ISAPI, CGI o FastCGI). L'approccio con Indy risulta infatti più semplice da configurare e gestire, ed offre il pieno supporto di tutte le funzionalità di WiRL.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>webdev</category>
      <category>delphi</category>
      <category>wirl</category>
    </item>
    <item>
      <title>Nested ExtJS Form Data</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Wed, 21 May 2025 15:19:10 +0000</pubDate>
      <link>https://dev.to/lminuti/nested-extjs-form-data-jbe</link>
      <guid>https://dev.to/lminuti/nested-extjs-form-data-jbe</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When developing web applications, handling form data effectively is often an important aspect of the development process. This article explores how to transform &lt;strong&gt;flat form data&lt;/strong&gt; into &lt;strong&gt;structured JSON objects&lt;/strong&gt; with &lt;strong&gt;nested properties&lt;/strong&gt; and &lt;strong&gt;arrays&lt;/strong&gt;. While &lt;strong&gt;ExtJS&lt;/strong&gt; has excellent built-in form capabilities, converting form values into complex JSON structures requires additional processing that isn't provided out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forms
&lt;/h2&gt;

&lt;p&gt;ExtJS has very powerful support for &lt;a href="https://docs.sencha.com/extjs/7.9.0/guides/components/forms.html" rel="noopener noreferrer"&gt;HTML Forms&lt;/a&gt;. It offers many components under the &lt;code&gt;Ext.form.*&lt;/code&gt; namespace that can help build complex layouts and handle common needs. However, sometimes we want not only a specific layout but also a custom JSON structure from the form.&lt;/p&gt;

&lt;p&gt;Let's take a look at the following image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo4ctk7p7j6zy8l86hspa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo4ctk7p7j6zy8l86hspa.png" alt="Simple form" width="408" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This can be achieved with this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;formpanel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Luca&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fieldcontainer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Credit card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VISA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},{&lt;/span&gt;
            &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1234567890&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, there are a couple of fields: &lt;em&gt;name&lt;/em&gt; and &lt;em&gt;credit card&lt;/em&gt;. The credit card field is composed of two "subfields": &lt;em&gt;brand&lt;/em&gt; and &lt;em&gt;number&lt;/em&gt;. If we try to get the values from the form (for example, using the &lt;code&gt;getValues&lt;/code&gt; method), we get a &lt;em&gt;flat object&lt;/em&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Luca"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VISA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's consider an even more complex form, for example, one where we have more than one credit card:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8h9feu5z74sapqxj8uf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8h9feu5z74sapqxj8uf.png" alt="Complex form" width="407" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With some minor changes to the previous code, we would get an object like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Luca"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"brand1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VISA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"number1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"brand2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VISA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"number2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0987654321"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this object structure is not ideal. A better object should have a shape like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Luca"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VISA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VISA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0987654321"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How can we transform the first object to the second one? Let's look at the following form, paying particular attention to the field names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;formpanel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Luca&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fieldcontainer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Credit card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card.0.brand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VISA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},{&lt;/span&gt;
            &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card.0.number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1234567890&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fieldcontainer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Credit card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card.1.brand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VISA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},{&lt;/span&gt;
            &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textfield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card.1.number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0987654321&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The object we get from this form looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Luca"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"card.0.brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VISA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"card.0.number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"card.1.brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VISA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"card.1.number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0987654321"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still not ideal, but by following this "dot notation" convention, we can easily transform this object into the structured version we want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Converting Dot Notation to Nested Objects
&lt;/h2&gt;

&lt;p&gt;The key to solving this problem is to create a utility function that can parse field names with dot notation and transform them into a nested object structure. The function below, that I call &lt;code&gt;nestify&lt;/code&gt; (suggestion are welcome), recursively processes the form values and builds the desired nested JSON structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * This procedure transforms objects by splitting properties that have
 * a dot (.) in their name into sub-properties. For example, an object like this:
 * {
 *     test: 'value',
 *     'group.first': 1,
 *     'group.second': 'other',
 * }
 * will be transformed as follows:
 * {
 *     test: 'value',
 *     group: {
 *         first: 1,
 *         second: 'other',
 *     }
 * }
 * If the property name contains a number, an array will be created:
 * {
 *     test: 'value',
 *     'group.1': 1,
 *     'group.2': 'other',
 * }
 * will be transformed as follows:
 * {
 *     test: 'value',
 *     group: [1, 'other']
 * }
 * @param {*} values The object to process
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nestify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// This function takes a single property and breaks it into sub-objects&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encodeKeyValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nestify: wrong property name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// If the property has no sub-components, assign the value&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// If the value is an object, decompose it recursively&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;encodeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// If values is an array, append the value&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Otherwise create a new property&lt;/span&gt;
                &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// If the property doesn't exist, create it&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNumeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Create an array if the sub-property name is numeric (e.g., "prop.1")&lt;/span&gt;
                &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Otherwise create an object&lt;/span&gt;
                &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// If there are multiple levels (e.g., 'prop.subprop.innerprop'), call itself recursively&lt;/span&gt;
        &lt;span class="nf"&gt;encodeKeyValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// This function processes all properties of "values" and passes them&lt;/span&gt;
    &lt;span class="c1"&gt;// to the encodeKeyValues function&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encodeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Ext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keysList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;encodeKeyValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keysList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;encodeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I think the dot notation in field names provides a clear and intuitive way to define the desired output structure directly in your form definition.&lt;/p&gt;

&lt;p&gt;This approach is particularly useful for complex forms where you need to collect hierarchical data or arrays of objects. It eliminates the need for manual post-processing of form data before sending it to your backend services, making your code cleaner and more maintainable.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>WIRL - Chunked encoding e SSE</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Wed, 02 Apr 2025 06:15:07 +0000</pubDate>
      <link>https://dev.to/lminuti/wirl-chunked-encoding-e-sse-did</link>
      <guid>https://dev.to/lminuti/wirl-chunked-encoding-e-sse-did</guid>
      <description>&lt;p&gt;Nell'ultima versione di WiRL è stato inserito il supporto per &lt;a href="https://en.wikipedia.org/wiki/Server-sent_events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt; (Server-sent events o eventi lato server) e per la modalità di &lt;a href="https://en.wikipedia.org/wiki/Chunked_transfer_encoding" rel="noopener noreferrer"&gt;trasferimento chunked&lt;/a&gt;, che permette di inviare una risposta dividendola in vari blocchi/chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSE - Server-sent events
&lt;/h2&gt;

&lt;p&gt;SSE è una tecnologia molto interessante che permette di inviare degli &lt;em&gt;eventi&lt;/em&gt; dal server al client, cosa che con HTTP normalmente sarebbe impossibile. Quello che avviene è che il client si collega ad un particolare endpoint del server e mantiene la connessione perennemente aperta, anche in caso di disconnessione (per problemi di rete o quant'altro) il client si deve occupare di ripristinarla non appena possibile.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxm17g5gf4m46i3vhiqs8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxm17g5gf4m46i3vhiqs8.gif" alt="Image description" width="732" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementazione
&lt;/h3&gt;

&lt;p&gt;Anche dal punto di vista di &lt;em&gt;WiRL&lt;/em&gt; la risorsa che implementa questi eventi deve essere costruita in maniera un po' particolare visto che non si tratta di inviare un semplice oggetto al client. Vediamo innanzitutto come si dichiara il metodo che andrà ad implementare la risorsa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TMediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEXT_EVENT_STREAM&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;ServerSideEvents&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;QueryParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'tag'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ATag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;TWiRLSSEResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La prima cosa che si nota è l'attributo &lt;code&gt;Produces&lt;/code&gt; con il valore &lt;em&gt;TEXT_EVENT_STREAM&lt;/em&gt;, questo perché il protocollo prevede una specifico &lt;code&gt;content-type&lt;/code&gt; da usare per SSE. Il metodo HTTP in questo caso è &lt;code&gt;GET&lt;/code&gt;; tecnicamente potrebbe essere usato anche un altro metodo ma se il client è scritto in &lt;em&gt;JavaScript&lt;/em&gt; la libreria standard supporta solo GET. Di seguito vediamo dei parametri, che possono essere di qualunque tipo, e infine il tipo di risposta che deve essere per forza &lt;code&gt;TWiRLSSEResponse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Vediamo adesso l'implementazione:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;TMyResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServerSideEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ATag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;TWiRLSSEResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TWiRLSSEResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWriter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IWiRLSSEResponseWriter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt;
      &lt;span class="n"&gt;LMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="c1"&gt;// Continua finche il server è "vivo"
&lt;/span&gt;      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;FServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Active&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="c1"&gt;// Legge un messaggio dalla coda
&lt;/span&gt;        &lt;span class="n"&gt;LMessage&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MessageQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PopItem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;LMessage&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
          &lt;span class="c1"&gt;// Se lo trova lo invia al client
&lt;/span&gt;          &lt;span class="n"&gt;AWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Come si vede nell'esempio la classe &lt;code&gt;TWiRLSSEResponse&lt;/code&gt; prende come primo parametro un metodo anonimo che provvederà ad inviare gli eventi. Il metodo anonimo prosegue finche il server è "vivo" (l'oggetto &lt;code&gt;FServer&lt;/code&gt; può essere recuperato tramite la &lt;em&gt;Context Injection&lt;/em&gt; di WiRL). In questo caso i messaggi sono nell'oggetto &lt;code&gt;MessageQueue&lt;/code&gt; (definito come una coda thread-safe: &lt;code&gt;TThreadedQueue&amp;lt;string&amp;gt;&lt;/code&gt;). Il programma tenta di estrarre un messaggio dalla coda e se lo trova lo invia al client tramite l'oggetto referenziato dall'interfaccia &lt;code&gt;IWiRLSSEResponseWriter&lt;/code&gt;. Ma vediamo quali metodi fornisce questa interfaccia:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;procedure Write(const AValue: string);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Questa è la funzione base che permette di inviare un evento al client. Notare che il contenuto di un evento può essere esclusivamente una stringa. Per inviare oggetto più complessi è necessario usare qualche codifica, come da esempio &lt;em&gt;Base64&lt;/em&gt; nel caso volessimo inviare un dato binario.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;procedure Write(const AEvent, AValue: string);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Questo metodo è simile al precedente ma ha in più il parametro event che permette di "categorizzare" il messaggio. In effetti l'API JavaScript permette al client di ricevere solo i messaggi appartenenti ad una certa categoria.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;procedure Write(const AEvent, AValue: string; ARetry: Integer);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Questa versione di &lt;em&gt;Write&lt;/em&gt; ha in più il parametro &lt;code&gt;ARetry&lt;/code&gt; che indica al client quanto tempo aspettare prima di aprire una nuova connessione una volta che quella corrente venga chiusa. In effetti, a parte errori di rete, il server potrebbe benissimo chiudera la connessione in qualunque momento.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;procedure WriteComment(const AValue: string);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Questo metodo invia un commento, in generale il client dovrebbe ignorarlo, lo scopo è di solito quello di tenere la connessione attiva, questo perché se in una connessione aperta non possano dati per troppo tempo il client o anche un eventuale proxy potrebbero decidere di chiuderla. &lt;/p&gt;

&lt;h3&gt;
  
  
  Il client
&lt;/h3&gt;

&lt;p&gt;Attualmente WiRL non fornisce un modo semplice per leggere gli eventi in arrivo tramite SSE, anche se è ovviamente possibile usare i componenti &lt;em&gt;Indy&lt;/em&gt; (&lt;code&gt;TIdHTTP&lt;/code&gt;) o il nuovo &lt;code&gt;THttpClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Se invece il cilent è il browser è possibile usare l'oggetto &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/EventSource" rel="noopener noreferrer"&gt;EventSource&lt;/a&gt; che serve proprio a questo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evtSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/rest/app/myevent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;evtSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Chunked transfer encoding
&lt;/h2&gt;

&lt;p&gt;L'altra possibilità inserita con l'ultima versione è quella di usare il &lt;em&gt;transfer encoding&lt;/em&gt; di tipo &lt;em&gt;chunked&lt;/em&gt;. Questa funzione permette di inviare i dati dal server al client a blocchi. Questo può essere utile in diversi casi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quando la dimensione totale del contenuto non è nota in anticipo, per esempio durante la generazione dinamica di pagine web o contenuti.&lt;/li&gt;
&lt;li&gt;Per lo streaming di dati in tempo reale, consentendo al client di iniziare a elaborare i dati mentre vengono ancora trasmessi.&lt;/li&gt;
&lt;li&gt;Per migliorare i tempi di risposta percepiti, poiché il browser può iniziare a renderizzare parti della pagina mentre altre sono ancora in fase di download.&lt;/li&gt;
&lt;li&gt;Nei casi di grandi trasferimenti di file, dove inviare il contenuto in chunk (blocchi) può essere più efficiente della trasmissione di un singolo blocco grande.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu3oroj0t0zucynwr9i8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu3oroj0t0zucynwr9i8.gif" alt="Image description" width="732" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementazione
&lt;/h3&gt;

&lt;p&gt;Implementare una risorsa che faccia uso di &lt;em&gt;Chunked transfer encoding&lt;/em&gt; non è troppo diverso dal caso di &lt;em&gt;SSE&lt;/em&gt;, in effetti in entrambi i casi abbiamo una risorsa che produce un risultato in maniera diluita nel tempo. Vendiamo innanzitutto l'interfaccia del metodo che implementa la risorsa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TMediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEXT_PLAIN&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;Chunks&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;QueryParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'chunks'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;DefaultValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'5'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;ANumOfChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;TWiRLChunkedResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In questo caso il metodo HTTP è una &lt;em&gt;GET&lt;/em&gt; ma può essere qualunque; anche il &lt;em&gt;Content-Type&lt;/em&gt;, al quale l'attributo &lt;code&gt;Produces&lt;/code&gt; fa riferimento, può essere qualunque, e non si riferisce al singolo chunk ma all'intero output della risorsa. I parametri possono chiaramente essere di qualsiasi tipo mentre quello che distingue una risorsa &lt;em&gt;chunked&lt;/em&gt; è il tipo restituito, che deve essere &lt;code&gt;TWiRLChunkedResponse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;L'implementazione, come nel caso precedente, dovrà fornire al costruttore di &lt;code&gt;TWiRLChunkedResponse&lt;/code&gt; una &lt;em&gt;procedura anonima&lt;/em&gt; che invierà i singoli chunk al client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;TMyResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ANumOfChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;TWiRLChunkedResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TWiRLChunkedResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWriter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IWiRLResponseWriter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt;
      &lt;span class="n"&gt;LCounter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="c1"&gt;// Invia i dati in ANumOfChunks chunks
&lt;/span&gt;      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;LCounter&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;ANumOfChunks&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="c1"&gt;// Invia il singolo chunk
&lt;/span&gt;        &lt;span class="n"&gt;AWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntToStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LCounter&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="c1"&gt;// Simula l'attesa necessaria per ottenere il dato successivo
&lt;/span&gt;        &lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In questo caso la risposta viene inviata suddivisa in diversi chunk decisi dal client. Un singolo chunk contiene un dato binario. L'oggetto referenziato dall'interfaccia &lt;code&gt;IWiRLResponseWriter&lt;/code&gt; ha però diversi metodi:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;procedure Write(const AValue: string; AEncoding: TEncoding = nil);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Permette di inviare una stringa trasformandola il binario con l'encoding specificato. In assenza di encoding viene usato &lt;em&gt;UTF-8&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;procedure Write(const AValue: TBytes);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Questo metodo permette di inviare dati binary usando direttamente &lt;code&gt;TBytes&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client
&lt;/h3&gt;

&lt;p&gt;Al momento &lt;code&gt;TWiRLClient&lt;/code&gt; non prevede nessun meccanismo particolare per la lettura dei dati divisi in &lt;em&gt;chunk&lt;/em&gt;. Il componete restituirà l'intera risposta una volta avvenuta la ricezione di tutti i chunks. Però sia il componente &lt;code&gt;TIdHTTP&lt;/code&gt; che &lt;code&gt;THttpClient&lt;/code&gt; tramite degli eventi possono leggere i chunk man mano che arrivano. Per esempio è possibile usare il componente TIdHTTP in questo modo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="n"&gt;TForm1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ButtonIdHTTP1Click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="c1"&gt;// Aggancia l'evento OnChunkReceived
&lt;/span&gt;  &lt;span class="n"&gt;IdHTTP1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnChunkReceived&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;IdHTTP1ChunkReceived&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Effettua la chiamata
&lt;/span&gt;  &lt;span class="n"&gt;IdHTTP1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'http://localhost:8080/rest/app/chunk'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="n"&gt;TForm1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdHTTP1ChunkReceived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TIdBytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;
  &lt;span class="n"&gt;LText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="c1"&gt;// Converte il chunk in stringa
&lt;/span&gt;  &lt;span class="n"&gt;LText&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;IndyTextEncoding_UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// e lo aggiunge ad un memo
&lt;/span&gt;  &lt;span class="n"&gt;MemoLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MemoLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;LText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusione
&lt;/h2&gt;

&lt;p&gt;In questo articolo ho cercato di fornire una panoramica sull'utilizzo di &lt;em&gt;chunk&lt;/em&gt; e &lt;em&gt;SSE&lt;/em&gt; con l'ultima versione di WiRL. Scaricando i sorgenti da &lt;a href="https://github.com/delphi-blocks/WiRL" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; è possibile testare il Demo &lt;strong&gt;23.Chunks&lt;/strong&gt; che fornisce vari esempi d'uso. Al momento la parte client è un po' debole ma con le prossime release verrà fornito anche un supporto migliorato per il componente &lt;code&gt;TWiRLClient&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>delphi</category>
      <category>rest</category>
      <category>wirl</category>
    </item>
    <item>
      <title>Siti statici con AWS e CloudFront</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Thu, 23 Jan 2025 21:24:51 +0000</pubDate>
      <link>https://dev.to/lminuti/siti-statici-con-aws-e-cloudfront-1lco</link>
      <guid>https://dev.to/lminuti/siti-statici-con-aws-e-cloudfront-1lco</guid>
      <description>&lt;h2&gt;
  
  
  Introduzione
&lt;/h2&gt;

&lt;p&gt;Negli ultimi mesi ho deciso di trasferire il mio &lt;a href="https://lucaminuti.it/" rel="noopener noreferrer"&gt;sito ufficiale&lt;/a&gt; da una macchina virtuale &lt;a href="https://aws.amazon.com/ec2/" rel="noopener noreferrer"&gt;EC2&lt;/a&gt; di &lt;strong&gt;AWS&lt;/strong&gt; a &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;S3&lt;/a&gt; sempre su AWS. Il motivo è banalmente la semplicità di gestione, infatti, se il sito comprende solo una serie di pagine statiche, fare un upload (eventualmente anche automatizzabile) su S3 è molto più semplice che avere un'intera macchina da amministrare. Lo svantaggio ovviamente è la flessibilità: avere un'intera macchina virtuale ci da certamente più possibilità.&lt;/p&gt;

&lt;p&gt;In questo articolo vedremo prima come &lt;strong&gt;preparare il bucket di S3&lt;/strong&gt; per ospitare e rendere pubbliche le nostre pagine e poi come &lt;strong&gt;abilitare HTTPS attraverso CloudFront&lt;/strong&gt; (sempre su AWS). In un prossimo articolo vedremo anche  come fare &lt;strong&gt;tutto il processo su CloudFlare&lt;/strong&gt; liberandoci completamente di AWS e semplificando molto la configurazione (di contro ci perdiamo tutte le funzionalità di AWS). Quest'ultima soluzione è quella che abbiamo usato per la &lt;a href="https://wirl.delphiblocks.dev/" rel="noopener noreferrer"&gt;documentazione di WiRL&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisiti
&lt;/h2&gt;

&lt;p&gt;In questa guida darò per scontato che sia già presente un account su &lt;em&gt;AWS&lt;/em&gt;. La creazione del account è gratuita e ci sono vari piani gratuiti o a basso costo su entrambe le piattaforme. Ovviamente dipende dalla dimensione del vostro sito e soprattutto dal traffico generato.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creare un nuovo bucket su S2
&lt;/h2&gt;

&lt;p&gt;Per prima cosa è necessario creare il bucket su S3. La scelta del nome del bucket è fondamentale infatti se vogliamo che sia raggiungibile da &lt;a href="http://lucaminuti.it/" rel="noopener noreferrer"&gt;http://lucaminuti.it/&lt;/a&gt; deve chiamarsi &lt;em&gt;lucaminuti.it&lt;/em&gt;, se invece il sito sarà per esempio &lt;a href="https://my.project.com/" rel="noopener noreferrer"&gt;https://my.project.com/&lt;/a&gt; il bucket dovrà chiamarsi &lt;em&gt;my.project.com&lt;/em&gt; (&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html?_ga=2.3211120.1599434423.1720630931-1879539605.1720630931#VirtualHostingCustomURLs" rel="noopener noreferrer"&gt;Guida ufficiale&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Nel mio caso il sito sarà &lt;a href="https://lucaminuti.it" rel="noopener noreferrer"&gt;https://lucaminuti.it&lt;/a&gt; per cui chiamo il bucket &lt;em&gt;lucaminuti.it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Innanzitutto aprite la console di AWS sulla &lt;strong&gt;pagina relativa a S3&lt;/strong&gt; e premete il pulsante &lt;code&gt;Crea bucket&lt;/code&gt;. Vi si dovrebbe presentare una pagina come questa:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkss2zw0dklwbxebiqubu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkss2zw0dklwbxebiqubu.png" alt="Crea bucket" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dato che deve essere ad accesso pubblico sblocco tutti gli accessi:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faycgkie4noc9mqdgypuu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faycgkie4noc9mqdgypuu.png" alt="Permessi" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una volta creato il bucket è possibile caricare i file (io uso &lt;a href="https://winscp.net/" rel="noopener noreferrer"&gt;WinSCP&lt;/a&gt; ma è anche possibile usare la CLI di AWS). Per ottenere l’accesso tramite browser sono necessari altri due passaggi. Nella pagina “Autorizzazioni” modifico le policy del bucket in modo da ottenere l’accesso in lettura. &lt;strong&gt;Attenzione al ARN del bucket&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38houo72d6sdhfqoovsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38houo72d6sdhfqoovsb.png" alt="Policy" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Qui il JSON grezzo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AddPerm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::lucaminuti-it/*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Poi è necessario nella pagina “properties” modificare la voce "Hosting di siti Web statici” in questo modo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9903uuwgnfe6fd9w8jtf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9903uuwgnfe6fd9w8jtf.png" alt="WebSite" width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A questo, punto una volta fatto l'upload dei vostri contenuti nel bucket, il sito dovrebbe essere accessibile. È possibile trovare l’URL di accesso nelle “properties” del bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvrdg8qn58dmrnhtiyqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvrdg8qn58dmrnhtiyqn.png" alt="URL" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;L'URL sarà una cosa del tipo:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lucaminuti.it.s3-website.amazonaws.com/" rel="noopener noreferrer"&gt;http://lucaminuti.it.s3-website.amazonaws.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Non proprio semplice da raggiungere e senza supporto per HTTPS. Per risolvere il problema possiamo usare &lt;em&gt;CloudFront&lt;/em&gt;, per restare in casa AWS o usare un servizio esterno come &lt;em&gt;CloudFlare&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creazione del certificato
&lt;/h2&gt;

&lt;p&gt;Per prima cosa dalla console di AWS apriamo la pagina "AWS certificate manager" e da li selezioniamo "richiedi certificato". Ne scegliamo uno pubblico, gli indichiamo il nome del dominio (es. lucaminuti.it) e lasciamo tutti i default. &lt;strong&gt;Attenzione: il certificato deve essere creato nella regione us-east-1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0yer9satcltekois3h4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0yer9satcltekois3h4.png" alt="Certificato" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una volta effettuata la richiesta create il record CNAME di conferma come specificato attraverso l'interfaccia con cui gestite il vostro dominio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajnda6klofr9i6ockwjp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajnda6klofr9i6ockwjp.png" alt="CName" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Entro pochi minuti (&amp;lt;5) dovrebbe essere approvato.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurazione di CloudFront
&lt;/h2&gt;

&lt;p&gt;Apriamo la pagina di "CloudFront" su AWS e selezioniamo "Create distribution". Qui possiamo impostare il dominio il protocollo è la porta che abbiamo usato nella fase precedete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7neogr4kuvfsisldlysb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7neogr4kuvfsisldlysb.png" alt="Origin" width="648" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Attiviamo il redirect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8w9qdjvhnp9o41z2uli1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8w9qdjvhnp9o41z2uli1.png" alt="Redirect" width="209" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Impostiamo il dominio dal quale raggiungere l'origine inserita nella fase precedente:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F483p24cqncjj10x8eh68.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F483p24cqncjj10x8eh68.png" alt="Domain" width="629" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gli associamo il certificato creato in precedenza:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhtey501w1370inm5oyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhtey501w1370inm5oyl.png" alt="Certificato" width="737" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;E l’oggetto di default:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmedgxgi53t0fms3hcaq7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmedgxgi53t0fms3hcaq7.png" alt="Root" width="713" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A questo punto l'ultima cosa da fare è andare sulla gestione del vostro DNS e associarla al dominio creato da CloudFrond. In particolare è necessario creare un record CNAME che punti al dominio creato da CloundFront (&lt;em&gt;Distribution domain name&lt;/em&gt; nelle proprietà della distribuzione CloudFront). Attenzione: per l'attivazione di CloudFront attendere qualche minuto per il deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusioni
&lt;/h2&gt;

&lt;p&gt;Come penso sia chiaro il processo non è esattamente semplice. Nel prossimo articolo proveremo a fare la stessa cosa usando &lt;a href="https://www.cloudflare.com/" rel="noopener noreferrer"&gt;CloudFlare&lt;/a&gt;, decisamente più semplice, ma che richiede il trasferimento della gestione del dominio su CloudFlare.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Appunti su VitePress</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Mon, 02 Dec 2024 07:26:35 +0000</pubDate>
      <link>https://dev.to/lminuti/appunti-su-vitepress-39ca</link>
      <guid>https://dev.to/lminuti/appunti-su-vitepress-39ca</guid>
      <description>&lt;p&gt;Dopo il &lt;a href="https://dev.to/lminuti/documentazione-con-vitepress-1jk5"&gt;precedente articolo&lt;/a&gt; su &lt;a href="https://vitepress.dev/" rel="noopener noreferrer"&gt;VitePress&lt;/a&gt; mi sono state fatte un po' di domande su come configurarlo e come attivare alcune funzionalità specifiche. Approfitto di questo spazio per raccogliere i miei appunti. Per ogni voce ho messo il link alla guida ufficiale per approfondire.&lt;/p&gt;

&lt;h1&gt;
  
  
  Dove posizionare immagini e altri asset
&lt;/h1&gt;

&lt;p&gt;Le immagini o altri asset referenziate all'interno dei file &lt;em&gt;markdown&lt;/em&gt; dovrebbero sempre essere indicate tramite un path relativo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;An image&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;./image.png&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VitePress si occuperà in fase di build di metterli nel posto giusto. Se invece si desidera caricare un file che non viene usato da nessun markdown nella root del sito (es. robot.txt, favicon, ecc.) deve essere posizionato nella directory &lt;code&gt;./docs/public&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vitepress.dev/guide/asset-handling#referencing-static-assets" rel="noopener noreferrer"&gt;https://vitepress.dev/guide/asset-handling#referencing-static-assets&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Logo
&lt;/h1&gt;

&lt;p&gt;È possibile inserire un logo a destra della pagina andando a modificare la sezione &lt;code&gt;hero&lt;/code&gt; della pagina &lt;code&gt;index.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;hero&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/BigLogo.png&lt;/span&gt;
    &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Logo&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyApp"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L’immagine deve essere inserita nella directory &lt;code&gt;./docs/public&lt;/code&gt; e dovrebbe essere di almeno 192x192 pixel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vitepress.dev/reference/default-theme-home-page#hero-section" rel="noopener noreferrer"&gt;https://vitepress.dev/reference/default-theme-home-page#hero-section&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Un logo più piccolo può essere messo in alto a sinistra su tutte le pagine andando a modificare la configurazione &lt;code&gt;.vitepress/config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitepress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// https://vitepress.dev/reference/site-config&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;themeConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;logo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/small-logo.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://vitepress.dev/reference/default-theme-nav#site-title-and-logo" rel="noopener noreferrer"&gt;https://vitepress.dev/reference/default-theme-nav#site-title-and-logo&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Come personalizzare i CSS
&lt;/h1&gt;

&lt;p&gt;Per personalizzare i colori o altre caratteristiche estetiche del sito è possibile modificare il tema aggiungendo un CSS custom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vitepress/theme/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;DefaultTheme&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitepress/theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./custom.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;DefaultTheme&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nc"&gt;.vitepress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;custom&lt;/span&gt;&lt;span class="nc"&gt;.css&lt;/span&gt;
&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--vp-c-brand-1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d65a4a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--vp-c-brand-2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#005d97&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://vitepress.dev/guide/extending-default-theme" rel="noopener noreferrer"&gt;https://vitepress.dev/guide/extending-default-theme&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Base URL
&lt;/h1&gt;

&lt;p&gt;Se il sito verrà messo in un URL che non è la root del sito è necessario indicarlo nell’opzione &lt;code&gt;base&lt;/code&gt; in &lt;code&gt;.vitepress/config.js&lt;/code&gt;. Per esempio se il vostro sito è accessibile all’URL &lt;a href="https://foo.github.io/bar/" rel="noopener noreferrer"&gt;&lt;code&gt;https://foo.github.io/bar/&lt;/code&gt;&lt;/a&gt; allora &lt;code&gt;base&lt;/code&gt; deve essere uguale a &lt;code&gt;/base/&lt;/code&gt; (deve sempre iniziale con slash).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vitepress.dev/guide/asset-handling#base-url" rel="noopener noreferrer"&gt;https://vitepress.dev/guide/asset-handling#base-url&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Ricerca integrata nel sito
&lt;/h1&gt;

&lt;p&gt;Se si desidera attivare una barra di ricerca è sufficiente inserire in configurazione:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vitepress/config.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitepress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;themeConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In questo modo la ricerca è fatta localmente, se la documentazione fosse molto estesa è preferibile usare un sito esterno. VitePress si integra perfettamente con Algolia. &lt;/p&gt;

&lt;p&gt;Vedi: &lt;a href="https://vitepress.dev/reference/default-theme-search#algolia-search" rel="noopener noreferrer"&gt;https://vitepress.dev/reference/default-theme-search#algolia-search&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Inserire la favicon
&lt;/h1&gt;

&lt;p&gt;Il modo più semplice per aggiungere una favicon è copiarla della directory &lt;code&gt;./docs/public/&lt;/code&gt; e aggiungere questa configurazione:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vitepress/config.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitepress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;head&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/favicon.ico&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://vitepress.dev/reference/site-config#example-adding-a-favicon" rel="noopener noreferrer"&gt;https://vitepress.dev/reference/site-config#example-adding-a-favicon&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Aggiungere il supporto a Mermaid
&lt;/h1&gt;

&lt;p&gt;Se si vuole aggiungere il supporto a &lt;a href="https://mermaid.js.org/" rel="noopener noreferrer"&gt;Mermaid&lt;/a&gt; per i grafici è necessario come prima cosa aggiungere il plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i vitepress-plugin-mermaid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e poi modificare la configurazione come segue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vitepress/config.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitepress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;withMermaid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitepress-plugin-mermaid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;withMermaid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Icone social personalizzate
&lt;/h1&gt;

&lt;p&gt;In alto a destra sul sito sono presenti le icone per i social; è possibile aggiungerle modificando la configurazione:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;    &lt;span class="nx"&gt;socialLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://github.com/foo/bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://youtube.com/foo/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le icone utilizzabili sono tutte quelle presenti su &lt;a href="https://simpleicons.org/" rel="noopener noreferrer"&gt;https://simpleicons.org/&lt;/a&gt;. Se non fossero sufficienti è possibile usare qualunque immagine in formato SVG (per esempio da &lt;a href="https://iconify.design/" rel="noopener noreferrer"&gt;https://iconify.design/&lt;/a&gt;) in questo modo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt; &lt;span class="nx"&gt;socialLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://github.com/EtheaDev/SVGIconImageList&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;svg xmlns="http://www.w3.org/2000/svg" width="0.86em" height="1em" viewBox="0 0 440 512"&amp;gt;  &amp;lt;path fill="currentColor" d="m188.1 318.863l-4.04-19.052l176.073-121.468l17.315 102.649zm151.193 148.26c-2.721 27.2-50.315 44.877-50.315 44.877l-84.315-183.43l25.839-4.08c40.831 49.843 85.165 50.51 108.79 142.634m-104.804 1.518c-35.834-7.263-63.108-9.73-83.65-9.73c-23.054 0-37.62 3.113-46.232 6.055l25.603-58.769l.093-.235c.097-.237 10.483-22.743 96.55-9.58l21.537 46.854zM80.649 223.858L31.276 126.47l53.036-53.036l24.478 108.112c-10.555 11.244-19.887 25.468-28.143 42.312m225.327-76.99l133.271-111.51L339.293 0l-94.77 129.473c20.274 2.15 43.096 10.268 61.453 17.396m-70.014-18.04L310.056 0h-96.552l-39.438 138.028c19.386-6.797 40.046-9.782 61.896-9.2m-68.695 11.24l14.279-118.99l-76.835 31.958l10.84 121.93c13.401-12.673 29.885-24.51 51.716-34.898m-97.69 190.4L4.08 310.055l14.958 44.876h58.296a165 165 0 0 1-7.756-24.465m11.112 32.175l-49.412 7.247l23.458 34.678l39.6-18.029a165 165 0 0 1-13.646-23.896M65.275 277.34L4.08 237.98L0 293.736l66.985 22.984c-1.661-12.666-2.19-25.81-1.71-39.38m8.838-43.44L25.84 165.648L4.08 218.941l61.196 52.28zm47.339 171.971c8.036-25.69 61.777-23.832 101.328-18.352l-28.32-61.61l-12.668 2.534l-6.84-32.263l163.834-113.023C234.333 93 146.248 154.418 115 187.667c-41.667 44.333-72.667 138 6.452 218.205" /&amp;gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; 
        &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://github.com/EtheaDev/SVGIconImageList&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://vitepress.dev/reference/default-theme-config#sociallinks" rel="noopener noreferrer"&gt;https://vitepress.dev/reference/default-theme-config#sociallinks&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Footer
&lt;/h1&gt;

&lt;p&gt;Inoltre è possibile inserire un footer personalizzato in home page in questo modo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vitepress/config.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;themeConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Released under the MIT License.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;copyright&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Copyright © 2024-present Luca Minuti&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sulle singole pagine della documentazione è possibile inserire sia la data di ultima modifica della pagina stessa che il link alla pagina su &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; per poterla modificare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vitepress/config.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;lastUpdated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;themeConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;editLink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://github.com/vuejs/vitepress/edit/main/docs/:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://vitepress.dev/reference/default-theme-edit-link" rel="noopener noreferrer"&gt;https://vitepress.dev/reference/default-theme-edit-link&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vitepress</category>
      <category>vue</category>
      <category>documentation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Documentazione con VitePress</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Mon, 28 Oct 2024 07:12:56 +0000</pubDate>
      <link>https://dev.to/lminuti/documentazione-con-vitepress-1jk5</link>
      <guid>https://dev.to/lminuti/documentazione-con-vitepress-1jk5</guid>
      <description>&lt;p&gt;Da qualche tempo ho cominciato a lavorare su diversi progetti Web con &lt;a href="https://vuejs.org/" rel="noopener noreferrer"&gt;VueJS&lt;/a&gt;, devo dire che mi trovo molto bene, tanto che con &lt;em&gt;Wintech Italia&lt;/em&gt; stiamo organizzando dei &lt;a href="https://www.wintech-italia.it/corsi/vue/" rel="noopener noreferrer"&gt;corsi&lt;/a&gt; e ne parleremo anche al prossimo &lt;a href="https://www.delphiday.it/" rel="noopener noreferrer"&gt;Delphi Day&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Quasi subito ho notato che i siti ufficiali di &lt;em&gt;Vue&lt;/em&gt; stesso e di gran parte dell'ecosistema Vue (&lt;a href="https://router.vuejs.org/" rel="noopener noreferrer"&gt;Vue Router&lt;/a&gt;, &lt;a href="https://pinia.vuejs.org/" rel="noopener noreferrer"&gt;Pinia&lt;/a&gt;, ecc.) sono molto simili e, a mio parere, molto ben fatti. Andando ad indagare ho scoperto che tutti usano &lt;a href="https://vitepress.dev/" rel="noopener noreferrer"&gt;VitePress&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cos'è VitePress
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;VitePress&lt;/strong&gt; è un generatore di siti statici, progettato specificamente per creare siti di documentazione tecnica. VitePress è stato sviluppato da &lt;em&gt;Evan You&lt;/em&gt;, il creatore di Vue.js, garantendo una profonda integrazione con l'ecosistema Vue. Nonostante questo non richiede la conoscenza di Vue per essere usato, infatti permette di generare la documentazione a partire da una serie di pagine scritte in &lt;strong&gt;Markdown&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Devo dire che VitePress mi è piaciuto molto, soprattutto per la sua estrema semplicità d'uso e per la sua flessibilità, tanto che abbiamo deciso di usarlo per riscrivere e aggiornare tutta la documentazione di &lt;a href="https://wirl.delphiblocks.dev/" rel="noopener noreferrer"&gt;WiRL&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flkchswgiupdm6v6s0ms6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flkchswgiupdm6v6s0ms6.png" alt="WiRL docs" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Come si usa
&lt;/h2&gt;

&lt;p&gt;Se andate sul sito ufficiale troverete un'ottima &lt;a href="https://vitepress.dev/guide/getting-started" rel="noopener noreferrer"&gt;guida all'installazione&lt;/a&gt; ma che purtroppo da per scontato abbiate già un progetto basato su &lt;strong&gt;npm&lt;/strong&gt;. Qui invece voglio partire da zero per chi abbia un progetto che, come &lt;em&gt;WiRL&lt;/em&gt;, non ha niente a che fare col mondo Web/JavaScript. In questo esempio creeremo un nuovo progetto contenete solo la documentazione, ma se volete inserire la documentazione in un progetto preesistente i passaggi sono analoghi.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inizializzazione
&lt;/h3&gt;

&lt;p&gt;Innanzitutto, se già non l'avete, è necessario installare NodeJS. Potete scaricarlo dal &lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;sito ufficiale&lt;/a&gt;. Quindi aprite un prompt dei comandi e digitate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;oppure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;dovreste vedere le rispettive versioni. Se non funziona probabilmente non li avete installati correttamente o non sono stati aggiunti al path di sistema.&lt;/p&gt;

&lt;p&gt;Creiamo il folder dove inserire il progetto e ci posizioniamo li:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;MyNewProject
&lt;span class="nb"&gt;cd &lt;/span&gt;MyNewProject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a questo punto inizializziamo il progetto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Npm chiederà un po' di informazioni: nome del progetto, versione, ecc. potete lasciare tutti i default premendo invio o modificarli a vostro piacimento. Al termine verrà creato un file &lt;code&gt;package.json&lt;/code&gt; contenente tutte le informazioni richieste in precedenza, che eventualmente è possibile modificare.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installazione di VitePress
&lt;/h3&gt;

&lt;p&gt;Una volta inizializzato il package possiamo procedere all'installazione di VitePress digitando il seguente comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm add &lt;span class="nt"&gt;-D&lt;/span&gt; vitepress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Questo andrà ad aggiungere al file &lt;code&gt;package.json&lt;/code&gt; una &lt;em&gt;dipendenza di sviluppo&lt;/em&gt; relativa a VitePress e provvederà anche a scaricare il pacchetto nella directory &lt;em&gt;node_modules&lt;/em&gt;. Ricordate di inserire &lt;em&gt;node_modules&lt;/em&gt; tra i file da ignorare se usate un version control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup Wizard
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnlo9dyeooo84kj708vra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnlo9dyeooo84kj708vra.png" alt="Image description" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ora potremo andare direttamente a scrivere la configurazione di VitePress e la documentazione ma in realtà è decisamente più comodo farsi generare lo scheletro del codice direttamente dal wizard di VitePress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx vitepress init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anche in questo caso vi verranno fatte alcune domande. Io di solito le confermo tutte tranne per la gestione dei TypeScript per la configurazione e la creazione dei file nella directory &lt;code&gt;docs&lt;/code&gt; invece che sulla root.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌  Welcome to VitePress!
│
◇  Where should VitePress initialize the config?
│  ./docs
│
◇  Site title:
│  My Awesome Project
│
◇  Site description:
│  A VitePress Site
│
◇  Theme:
│  ● Default Theme (Out of the box, good-looking docs)
│  ○ Default Theme + Customization
│  ○ Custom Theme
│
◇  Use TypeScript for config and theme files?
│  ○ Yes
│  ● No
│  
◇  Add VitePress npm scripts to package.json?
│  ● Yes
│  ○ No  
└
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se avete risposto di &lt;em&gt;sì&lt;/em&gt; alla domanda sulla generazione degli script potete già avviare il vostro nuovo sito con:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run docs:dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In pochi secondi verrà avviato un &lt;em&gt;server HTTP&lt;/em&gt; che potete usare per visualizzare le pagine puntando il browser all'URL indicato. Il server rimane attivo finche non premete &lt;code&gt;q&lt;/code&gt; e se modificate o aggiungete una pagina andrà a forzare il caricamento automatico della nuova versione sul browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creazione del sito
&lt;/h3&gt;

&lt;p&gt;A questo punto si può precedere alla configurazione del sito vero è proprio. il &lt;em&gt;wizard&lt;/em&gt; in effetti avrà creato diversi file che potete andare a modificare.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
 ├─ .vitepress
 │  └─ config.mjs
 ├─ api-examples.md
 ├─ markdown-examples.md
 └─ index.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In particolare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.md&lt;/code&gt;: contiene la home page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.vitepress/config.mjs&lt;/code&gt;: contiene l'indice di tutte le pagine&lt;/li&gt;
&lt;li&gt;gli altri sono file di esempio che potete anche eliminare&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quindi semplicemente andando a scrivere le vostre pagine e aggiungendole all'indice potete creare la vostra guida. Tutte le pagine devono essere scritte in markdown, se non lo conoscete trovate una guida &lt;a href="https://www.markdownguide.org/basic-syntax/" rel="noopener noreferrer"&gt;qui&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;Una volta creato un sito che vi soddisfa potete procedere al deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run docs:build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Questo comando genererà tutti i file necessari nella directory &lt;code&gt;.vitepress\dist&lt;/code&gt;. Potete semplicemente copiarli su qualsiasi sito che vi offre una spazio web e saranno visibili da chiunque.&lt;/p&gt;

&lt;p&gt;Eventualmente sito ufficiale trovate una &lt;a href="https://vitepress.dev/guide/deploy#deploy-your-vitepress-site" rel="noopener noreferrer"&gt;guida&lt;/a&gt; per fare deploment su una miriade di piattaforme:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Netlify / Vercel / Cloudflare Pages / AWS Amplify / Render&lt;/li&gt;
&lt;li&gt;GitHub Pages&lt;/li&gt;
&lt;li&gt;GitLab Pages&lt;/li&gt;
&lt;li&gt;Azure Static Web Apps&lt;/li&gt;
&lt;li&gt;Firebase&lt;/li&gt;
&lt;li&gt;Surge&lt;/li&gt;
&lt;li&gt;Heroku&lt;/li&gt;
&lt;li&gt;Edgio&lt;/li&gt;
&lt;li&gt;Kinsta Static Site Hosting&lt;/li&gt;
&lt;li&gt;Stormkit&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Altro
&lt;/h3&gt;

&lt;p&gt;Come dicevo all'inizio VitePress permette una serie infinita di personalizzazioni, alcune offerte direttamente e altre attraverso plugin di terze parti. Giusto per avere un'idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internazionalizzazione&lt;/li&gt;
&lt;li&gt;Temi custom&lt;/li&gt;
&lt;li&gt;Motore di ricerca integrato&lt;/li&gt;
&lt;li&gt;Supporto a mermaid&lt;/li&gt;
&lt;li&gt;Generazione della sitemap&lt;/li&gt;
&lt;li&gt;Supporto a Frontmatter&lt;/li&gt;
&lt;li&gt;Utilizzo di Vue all'interno dei file markdown&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vitepress</category>
      <category>vue</category>
      <category>documentation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ExtJS and Tailwind CSS</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Mon, 14 Oct 2024 06:23:33 +0000</pubDate>
      <link>https://dev.to/lminuti/extjs-and-tailwind-css-448l</link>
      <guid>https://dev.to/lminuti/extjs-and-tailwind-css-448l</guid>
      <description>&lt;p&gt;Over the past few months, I've been using &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; for some WebApp projects not related to &lt;a href="https://www.sencha.com/products/extjs/" rel="noopener noreferrer"&gt;ExtJS&lt;/a&gt;. Tailwind is a framework very different from Bootstrap or similar products. In fact, it uses a &lt;strong&gt;utility-first&lt;/strong&gt; approach where CSS classes are linked to functionalities rather than components.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Tailwind CSS Works
&lt;/h2&gt;

&lt;p&gt;To try to clarify the concept a bit, I'll use the same example from the official website.&lt;/p&gt;

&lt;p&gt;Let's take this component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8sqh0tc4tsczas9wmp5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8sqh0tc4tsczas9wmp5r.png" alt="Simple web page" width="765" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using a traditional approach, we would need to use code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-notification"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-notification-logo-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-notification-logo"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/img/logo.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"ChitChat Logo"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-notification-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h4&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-notification-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;ChitChat&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-notification-message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;You have a new message!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.chat-notification&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt; &lt;span class="m"&gt;-5px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;-5px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.chat-notification-logo-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;flex-shrink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.chat-notification-logo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.chat-notification-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.chat-notification-title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1a202c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.chat-notification-message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#718096&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Tailwind, however, something like this is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"shrink-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"size-12"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/img/logo.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"ChitChat Logo"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-xl font-medium text-black"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;ChitChat&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-slate-500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;You have a new message!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose of this article is not to go into the details of how Tailwind works, but even at first glance, what we see is the difference in size of the code block, but also a series of horrible CSS classes.&lt;/p&gt;

&lt;p&gt;Personally, I'm used to give classes a name that help me understand their meaning (button, card, grid, column, ...), and this certainly makes it easier to read the HTML code; in some way, it's like adding comments to the code. Instead, with Tailwind, the classes refer to a single specific aspect to assign to HTML tags. For each tag it's often necessary to insert more than one class, making the whole thing particularly complex.&lt;/p&gt;

&lt;p&gt;The big advantage is that you practically don't write CSS anymore and use almost only Tailwind classes. In this way, besides not wasting time inventing class names, often even bizarre ones (item-inner-column-wrapper, etc.), you use conventions that help the site be more homogeneous and coherent.&lt;/p&gt;

&lt;p&gt;Moreover Tailwind has a whole series of interesting features for supporting &lt;strong&gt;responsive&lt;/strong&gt; sites, creating custom &lt;strong&gt;themes&lt;/strong&gt;, &lt;strong&gt;dark&lt;/strong&gt; mode, a plethora of &lt;strong&gt;plugins&lt;/strong&gt;, and much more. For this refer to the &lt;a href="https://tailwindcss.com/docs/installation" rel="noopener noreferrer"&gt;official guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailwind and ExtJS
&lt;/h2&gt;

&lt;p&gt;When some time ago I resumed an ExtJS project and found myself writing complex &lt;strong&gt;templates&lt;/strong&gt;, with CSS classes and many styles, I realized I missed Tailwind. Let's take an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;
        &lt;span class="na"&gt;itemTpl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="list-item"&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="item-icon {iconCls}"&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="list-item-data"&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="list-item-name"&amp;gt;{name}&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="list-item-detail"&amp;gt;{detail}&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with its CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;
&lt;span class="nc"&gt;.list-item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.list-item-data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.list-item-name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.list-item-detail&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Tailwind, the following code is enough without even touching the CSS files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;xtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;
        &lt;span class="na"&gt;itemTpl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="flex items-center p-1"&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="item-icon {iconCls}"&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="flex flex-col"&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="text-lg"&amp;gt;{name}&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div class="italic"&amp;gt;{detail}&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Using Tailwind with ExtJS is quite simple because in the end, what the engine does is scan all our code looking for Tailwind classes (flex, grid, text-lg, p-1, p-2, etc.) and generate a CSS file containing only the classes we've used. If you use the new &lt;a href="https://docs.sencha.com/extjs/7.8.0/guides/using_systems/using_npm/using_npm.html" rel="noopener noreferrer"&gt;Open tooling&lt;/a&gt; based on npm, you can follow Tailwind's &lt;a href="https://tailwindcss.com/docs/installation" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;. Otherwise, if you use SenchaCmd, the simplest thing to do is to download the CLI from &lt;a href="https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.4.13" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this new CLI installed, you can initialize Tailwind by going to the root directory of your project and write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a tailwind.config.js file&lt;/span&gt;
./tailwindcss init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the &lt;code&gt;tailwind.config.js&lt;/code&gt; file. I changed it like this, but of course, it depends on your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type {import('tailwindcss').Config} */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app/**/*.{html,js}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// Remove all corePlugins&lt;/span&gt;
    &lt;span class="na"&gt;corePlugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;preflight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Remove all preset&lt;/span&gt;
    &lt;span class="c1"&gt;//presets: [],&lt;/span&gt;
    &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;boxShadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2px 2px 8px 0 rgba(0, 0, 0, 0.4)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;content&lt;/code&gt; option is the most important, where you must define the directories that Tailwind will scan to find CSS classes. With the &lt;code&gt;corePlugins&lt;/code&gt; and &lt;code&gt;plugins&lt;/code&gt; configuration, I removed all plugins to generate the lightest possible CSS. Finally, I added a customization to the theme to generate a new &lt;code&gt;shadow-outline&lt;/code&gt; class with the specified configuration. If you will, you could remove the standard &lt;code&gt;presets&lt;/code&gt;, but then some classes wouldn't work.&lt;/p&gt;

&lt;p&gt;Then we can use the CLI again to generate the classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tailwindcss -o sass\src\tailwind.css --watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this command, Tailwind will constantly check the files specified in the configuration and generate the classes in the &lt;code&gt;sass\src\tailwind.css&lt;/code&gt; file. Finally we have to tell ExtJS to use that CSS file. We need to modify the application configuration in the scss section of the &lt;code&gt;app.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"sass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"sass/src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"sass/src/tailwind.css"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optionally, you can automate the creation of CSS during the production build by adding the following code to your &lt;code&gt;build.xml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"-before-build"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;if&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;equals&lt;/span&gt; &lt;span class="na"&gt;arg1=&lt;/span&gt;&lt;span class="s"&gt;"production"&lt;/span&gt; &lt;span class="na"&gt;arg2=&lt;/span&gt;&lt;span class="s"&gt;"${build.environment}"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;then&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;x-echo&lt;/span&gt; &lt;span class="na"&gt;message=&lt;/span&gt;&lt;span class="s"&gt;"Build CSS with Tailwind"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;exec&lt;/span&gt; &lt;span class="na"&gt;executable=&lt;/span&gt;&lt;span class="s"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;arg&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"-o"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;arg&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"sass\src\tailwind.css"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/exec&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/then&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>extjs</category>
      <category>tailwindcss</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Delphi WebStencils con WiRL</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Mon, 23 Sep 2024 06:19:24 +0000</pubDate>
      <link>https://dev.to/lminuti/delphi-webstencils-con-wirl-3b76</link>
      <guid>https://dev.to/lminuti/delphi-webstencils-con-wirl-3b76</guid>
      <description>&lt;p&gt;Da pochi giorni è uscita la versione &lt;a href="https://blogs.embarcadero.com/announcing-the-availability-of-rad-studio-12-2-athens/" rel="noopener noreferrer"&gt;12.2 di Delphi&lt;/a&gt;, nonostante sia una &lt;em&gt;dot release&lt;/em&gt;, dove il focus è principalmente sulla risoluzione dei bug, ci sono diverse novità interessanti. Le principali sono il supporto per vari sistemi di AI generativa e l'introduzione di una libreria denominata &lt;a href="https://blog.marcocantu.com/blog/2024-september-introducing-webstencils.html" rel="noopener noreferrer"&gt;WebStenclis&lt;/a&gt; per la creazione HTML attraverso dei template.&lt;/p&gt;

&lt;p&gt;In questo articolo volevo parlare di quest'ultima, in particola di come integrarla con &lt;a href="https://wirl.delphiblocks.dev/" rel="noopener noreferrer"&gt;WiRL&lt;/a&gt;. In realtà, con qualche accorgimento, il contenuto di questo articolo può essere adattato anche ad altre tecnologie.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebStencils
&lt;/h2&gt;

&lt;p&gt;Se volete un guida approfondita su &lt;em&gt;WebStencils&lt;/em&gt; vi consiglio l'ottimo &lt;a href="https://blog.marcocantu.com/blog/2024-september-introducing-webstencils.html" rel="noopener noreferrer"&gt;articolo di Marco Cantù&lt;/a&gt;, ma di base si tratta di un sistema per creare del codice HTML in base ad un template da applicare ad un oggetto Delphi.&lt;/p&gt;

&lt;p&gt;Supponendo di avere un oggetto &lt;code&gt;TPerson&lt;/code&gt; fatto in questo modo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;  &lt;span class="n"&gt;TPerson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt;
    &lt;span class="n"&gt;FName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;FAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt;
    &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FName&lt;/span&gt; &lt;span class="k"&gt;write&lt;/span&gt; &lt;span class="n"&gt;FName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FAge&lt;/span&gt; &lt;span class="k"&gt;write&lt;/span&gt; &lt;span class="n"&gt;FAge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;constructor&lt;/span&gt; &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;AAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e un template come questo (notare i valori che cominciano con &lt;code&gt;@&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"output1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Name: &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;@value.name&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Age: &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;@value.age&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;è possibile generare l'HTML corrispondente con questo codice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;    &lt;span class="n"&gt;LPerson&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TPerson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Luca'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;LStencil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InputFileName&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;LTemplateFileName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;LStencil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LPerson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;False&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;LContent&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;LStencil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I template non si limitano a fare una sorta di "search &amp;amp; replace" ma possono avere anche del codice condizionale come:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Loop&lt;/em&gt;: per ripetere parti del template in caso di oggetto che contengono vari elementi come dataset o liste;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;If&lt;/em&gt;: per includere una parte del template in base a qualche condizione;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Include&lt;/em&gt;: per includere porzioni di HTML;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;loginrequired&lt;/em&gt;: per gestire le autorizzazioni; &lt;/li&gt;
&lt;li&gt;e altro ancora.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WiRL
&lt;/h2&gt;

&lt;p&gt;Ma come possiamo usare questa tecnologia con WiRL? Lo scopo di WiRL è quello di implementare una API ReST e in questo caso non stiamo affatto parlando di ReST. Certo, nessuno ci impedisce di restituire un HTML generato con &lt;em&gt;WebStelcils&lt;/em&gt; da una risorsa ReST, ma non avrebbe molto senso.&lt;/p&gt;

&lt;p&gt;A pensarci bene però i WebStencils, nell'ottica di WiRL, sono in pratica del &lt;a href="https://wirl.delphiblocks.dev/server/message-body.html" rel="noopener noreferrer"&gt;Message Body Writer&lt;/a&gt;, cioè dei moduli che sono in grado di serializzare degli oggetti in un determinato formato, in questo caso HTML.&lt;/p&gt;

&lt;p&gt;L'idea sarebbe quella di creare risorse WiRL in maniera tradizionale, quindi restituendo oggetti e trasformarli in HTML tramite un &lt;em&gt;Message Body Writer&lt;/em&gt; specifico che utilizzi &lt;em&gt;WebStencils&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  La risorsa
&lt;/h2&gt;

&lt;p&gt;La risorsa che andremo a creare sarà quindi una normalissima risorsa WiRL che restituisce un oggetto &lt;code&gt;TPerson&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/person'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;TPersonResource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;TObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TMediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEXT_HTML&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TMediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;GetPerson&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;TPerson&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'implementazione è ancora più semplice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;TPersonResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPerson&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TPerson&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TPerson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Luca'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notate che nella dichiarazione abbiamo indicato che l'oggetto &lt;code&gt;TPerson&lt;/code&gt; può essere serializzato sia un &lt;strong&gt;JSON&lt;/strong&gt; che &lt;strong&gt;HTML&lt;/strong&gt; (WiRL sceglierà uno o altro in base al header &lt;em&gt;Accept&lt;/em&gt; della richiesta). Ora, mentre per il JSON non ci sono problemi, dato che WiRL lo supporta nativamente, per HTML dobbiamo dirgli come comportarsi. Ed è qui che entra in gioco il &lt;em&gt;Message Body Writer&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Message Body Writer
&lt;/h2&gt;

&lt;p&gt;Vediamo subito il codice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="n"&gt;TWiRLStencilsWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TAttributeArray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;AMediaType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TMediaType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;AHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IWiRLHeaders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;AContentStream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;
  &lt;span class="n"&gt;LStencilName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;LStencil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TWebStencilsProcessor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;LContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;LBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TBytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;inherited&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Verifica se ci è stato passato un template specifico nell'header 'x-stencil'
&lt;/span&gt;  &lt;span class="n"&gt;LStencilName&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'x-stencil'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="c1"&gt;// altrimenti ne usa uno di default (nome della classe senza 'T')
&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;LStencilName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;LStencilName&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClassName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;'.html'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;LStencil&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TWebStencilsProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="c1"&gt;// Carica il template
&lt;/span&gt;    &lt;span class="n"&gt;LStencil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InputFileName&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExtractFileDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ParamStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s"&gt;'www'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LStencilName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Carica l'oggetto
&lt;/span&gt;    &lt;span class="n"&gt;LStencil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;False&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Applica il template
&lt;/span&gt;    &lt;span class="n"&gt;LContent&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;LStencil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Mette il risultato nello stream della risposta HTTP
&lt;/span&gt;    &lt;span class="n"&gt;LBuffer&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TEncoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;AContentStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LBuffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LBuffer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;finally&lt;/span&gt;
    &lt;span class="n"&gt;LStencil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Free&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'idea è che per serializzare l'oggetto &lt;code&gt;TPerson&lt;/code&gt; WiRL deve usare un template di nome &lt;code&gt;person.html&lt;/code&gt; oppure il chiamante può indicare un template specifico tramite un header custom chiamato &lt;code&gt;x-stencil&lt;/code&gt;. Il Message Body Writer non fa nient'altro che caricare il template e l'oggetto da serializzare nel WebStencils e chiedergli il risultato.  &lt;/p&gt;

&lt;p&gt;Come si può vedere il codice del Message Body Writer è molto semplice. &lt;/p&gt;

&lt;p&gt;Questo uso di &lt;em&gt;template&lt;/em&gt; e &lt;em&gt;WebStencils&lt;/em&gt; può integrarsi con qualsiasi progetto Web ma è particolarmente interessante accoppiato con &lt;a href="https://htmx.org/" rel="noopener noreferrer"&gt;HTMX&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTMX
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flucaminuti.it%2Fwebstencil-1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flucaminuti.it%2Fwebstencil-1.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anche in questo caso non mi voglio soffermare troppo su HTMX, trovate diversi post di Embarcadero sull'argomento &lt;a href="https://blog.marcocantu.com/blog/2024-july-delphi-htmx.html" rel="noopener noreferrer"&gt;qui&lt;/a&gt;. Ma l'idea di base è quella di estendere l'HTML e riuscire a creare pagine dinamiche che interagiscono col server senza l'uso di JavaScript, quindi con un approccio puramente dichiarativo.&lt;/p&gt;

&lt;p&gt;Per esempio il seguente frammento di HTMX alla pressione del pulsante chiamerà l'URL &lt;code&gt;/clicked&lt;/code&gt; e sostituira il nodo con nome &lt;code&gt;outerHTML&lt;/code&gt; con quanto arrivato dal server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/clicked"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Click Me
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quindi si presume che il server generi frammenti di HTML, che è proprio quello che abbiamo fatto con i WebStencils.&lt;/p&gt;

&lt;p&gt;Qui trovate il progetto di esempio con i sorgenti completi: &lt;a href="https://github.com/lminuti/WebStencilsDemo" rel="noopener noreferrer"&gt;https://github.com/lminuti/WebStencilsDemo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nel progetto troverete una semplice pagina di esempio (ho usato &lt;a href="https://picocss.com/" rel="noopener noreferrer"&gt;PicoCSS&lt;/a&gt; giusto per avere un aspetto un minimo decente), dove ci sono tre DEMO:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5xjwo2d09c248vapjz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5xjwo2d09c248vapjz3.png" alt="Demo Home"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Il primo contiene un pulsante fatto così:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"rest/default/person"&lt;/span&gt;
            &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"click"&lt;/span&gt;
            &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#output1"&lt;/span&gt;
            &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Click Me!
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In questo caso al &lt;code&gt;click&lt;/code&gt; del pulsante viene chiamata la risorsa &lt;code&gt;rest/default/person&lt;/code&gt; e il risultato va a sostituire l'elemento HTML chiamato &lt;code&gt;output1&lt;/code&gt;. Il secondo è identico ma usa un template personalizzato:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"rest/default/person"&lt;/span&gt;
            &lt;span class="na"&gt;wirl-stencil=&lt;/span&gt;&lt;span class="s"&gt;"person_it.html"&lt;/span&gt;
            &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"click"&lt;/span&gt;
            &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#output2"&lt;/span&gt;
            &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Click Me!
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Infatti con l'attributo &lt;code&gt;wirl-stencil&lt;/code&gt; inviamo a WiRL in nome del template da usare. Il terzo è un po' più complesso, infatti il template permette di trasformare un &lt;em&gt;dataset&lt;/em&gt; in una &lt;em&gt;tabella&lt;/em&gt; HTML:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvgr7kvkwy1mi9l64ei5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvgr7kvkwy1mi9l64ei5.png" alt="Tabella"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inoltre cliccando su una specifica riga si può vedere il dettaglio dell'elemento selezionato:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8w64ytx0qgm99ayal2d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8w64ytx0qgm99ayal2d6.png" alt="Demo form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurazione
&lt;/h3&gt;

&lt;p&gt;Per ottenere questa integrazione tra HTMX e WiRL serve una piccola configurazione lato HTMX che è la seguente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;htmx:configRequest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// force text/html&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wirl-stencil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-stencil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wirl-stencil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// add x-stencil header&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In pratica stiamo dicendo a &lt;em&gt;HTMX&lt;/em&gt; che ogni volta che esegue una chiamata &lt;em&gt;Ajax&lt;/em&gt; deve aggiungere due header:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;accept&lt;/code&gt;: che indica a WiRL di restituire la risorsa serializzata in HTML. Questo perché WiRL di default la serializzerebbe in JSON;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x-stencil&lt;/code&gt;: in questo modo se abbiamo usato l'attributo &lt;code&gt;wirl-stencil&lt;/code&gt; sul pulsante HTMX invia il nome dello template attraveso questo header.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusioni
&lt;/h2&gt;

&lt;p&gt;Penso che questo uso degli stencils con WiRL sia interessante e soprattutto che dimostri come è semplice personalizzare WiRL per fargli fare anche cose per cui chiaramente non è nato.&lt;/p&gt;

&lt;p&gt;Trovate tutto il codice mostrato qui:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/lminuti/WebStencilsDemo" rel="noopener noreferrer"&gt;https://github.com/lminuti/WebStencilsDemo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Per poterlo compilare e provare è necessario scaricare e installare WiRL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/delphi-blocks/WiRL" rel="noopener noreferrer"&gt;https://github.com/delphi-blocks/WiRL&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La documentazione ufficiale di WiRL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://wirl.delphiblocks.dev/" rel="noopener noreferrer"&gt;https://wirl.delphiblocks.dev/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wirl</category>
      <category>delphi</category>
      <category>webstencils</category>
      <category>htmx</category>
    </item>
    <item>
      <title>Bruno vs Postman</title>
      <dc:creator>Luca Minuti</dc:creator>
      <pubDate>Tue, 30 Jul 2024 07:17:41 +0000</pubDate>
      <link>https://dev.to/lminuti/bruno-vs-postman-28cg</link>
      <guid>https://dev.to/lminuti/bruno-vs-postman-28cg</guid>
      <description>&lt;p&gt;Per chi sviluppa server ReST l’uso di un tool per testare le proprie API è fondamentale. Fino a poco tempo fa lo standard di fatto per questo tipo di applicazioni era &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt;. Ultimamente però lo scenario è cambiato e si trovano diverse alternative, una delle più interessanti è &lt;a href="https://www.usebruno.com/" rel="noopener noreferrer"&gt;Bruno&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Chi conosce Postman non farà fatica ad usare Bruno, infatti, l’interfaccia utente è molto simile e anche alcuni concetti come le &lt;em&gt;Collection,&lt;/em&gt; &lt;em&gt;Environment, Script&lt;/em&gt; e &lt;em&gt;Test&lt;/em&gt; sono praticamente gli stessi. Però ci sono alcune differenze sostanziali. La più interessante è che, mentre Postman richiede la creazione di un account per condividere le collections, Bruno sfrutta Git (o qualunque altro version control). Questa caratteristica è molto interessante perché permette di “versionare” le collections e i vari test insieme alle API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fru2jmz7mtjhggqc6t2yg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fru2jmz7mtjhggqc6t2yg.png" alt="Bruno UI" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bru Markup Language
&lt;/h2&gt;

&lt;p&gt;In effetti quando viene salvata una &lt;em&gt;collection&lt;/em&gt; vengono fisicamente salvati vari file in formato &lt;strong&gt;Bru&lt;/strong&gt; in una cartelle del file system. La prima volta che ho visto il file Bru mi sono chiesto perché usare l’ennesimo formato custom simil JSON invece di JSON, YAML, TOML o qualcosa di già esistente.&lt;/p&gt;

&lt;p&gt;Andando a leggere la documentazione e vari forum ho capito che c’era un &lt;em&gt;ottimo motivo&lt;/em&gt; per questa scelta. E questo risiede sempre nella volontà di integrarsi con il &lt;em&gt;version control&lt;/em&gt;. Infatti bisogna tener presente che nei file Bru ci sono alcune informazioni molto semplici — come metodo HTTP, headers, queryparams — che si prestano molto bene ad essere strutturate in un JSON; ma ci sono anche blocchi di codice JavaScript (per gli script e i test) e documentazione in Markdown o altro che inseriti in un JSON lo renderebbero pressoché illeggibile ostacolando notevolmente operazioni come diff o merge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Get&lt;/span&gt; &lt;span class="nx"&gt;Users&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;
    &lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//reqres.in/api/users&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;none&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status code is 200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bru command line
&lt;/h2&gt;

&lt;p&gt;Una caratteristica interessante di Bruno è la possibilità di automatizzare l’esecuzione delle varie collection dalla linea di comando. Per esempio questo comando lancerà tutte le richieste della collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bru run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ovviamente è anche possibile lanciare una singola collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bru run request.bru
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;specificare un particolare environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bru run folder &lt;span class="nt"&gt;--env&lt;/span&gt; Local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e molto altro. Questo è un esempio dell’output del comando precedente:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjzuocvcj85dff4ijk2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjzuocvcj85dff4ijk2v.png" alt="Output del comando precedente" width="677" height="421"&gt;&lt;/a&gt;&lt;br&gt;
Nella &lt;a href="https://docs.usebruno.com/bru-cli/overview" rel="noopener noreferrer"&gt;documentazione ufficiale&lt;/a&gt; sono specificati tutti i parametri.&lt;/p&gt;
&lt;h2&gt;
  
  
  Scripting
&lt;/h2&gt;

&lt;p&gt;Bruno offre la possibilità di scrivere anche del codice JavaScript per automatizzare diverse operazioni per esempio:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recuperare automaticamente il token di autenticazione&lt;/li&gt;
&lt;li&gt;Generare dati fittizi&lt;/li&gt;
&lt;li&gt;Validare la risposta&lt;/li&gt;
&lt;li&gt;Integrare la richiesta con dati provenienti da altre API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Per esempio il codice seguente inserito in uno script “post response” permette di recuperare il token dalla risposta e inserirlo in una variabile in modo da poterlo usare in tutte le chiamate successive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bru&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEnvVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jwt_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invece il codice seguente, inserito in uno script “Pre request” permette di popolare il corpo di una richiesta con dei dati fittizi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@faker-js/faker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBody&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;email&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notare che in quest’ultimo caso è stata usata una libreria esterna. Bruno infatti permette di usare una selezione di librerie JavaScript (ajx, axios, node-fetch, lodash, moment, ecc.) semplicemente importandole (require) oppure con un processo un po’ più complicato di usare qualunque libreria. &lt;/p&gt;

&lt;h2&gt;
  
  
  Link
&lt;/h2&gt;

&lt;p&gt;Home page: &lt;a href="https://www.usebruno.com/" rel="noopener noreferrer"&gt;https://www.usebruno.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Documentazione: &lt;a href="https://docs.usebruno.com/introduction/getting-started" rel="noopener noreferrer"&gt;https://docs.usebruno.com/introduction/getting-started&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Discord: &lt;a href="https://discord.com/invite/KgcZUncpjq" rel="noopener noreferrer"&gt;https://discord.com/invite/KgcZUncpjq&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>rest</category>
      <category>postman</category>
      <category>bruno</category>
    </item>
  </channel>
</rss>
