Markdown in JavaScript nel browser con Marked
Marked è una libreria JavaScript leggera e performante che consente di convertire testo scritto in formato Markdown in codice HTML valido. Nata per essere utilizzata sia in ambiente Node.js sia direttamente nel browser, rappresenta una delle soluzioni più diffuse e consolidate per il parsing del Markdown lato client. In questo articolo analizzeremo nel dettaglio il suo funzionamento attraverso un esempio pratico completo, partendo dalla struttura HTML necessaria fino al codice JavaScript che orchestra la conversione.
Installazione e inclusione nel browser
A differenza dell'ambiente Node.js, dove Marked viene installato tramite npm, nel browser è sufficiente
includere lo script della libreria attraverso un tag <script>. La versione più recente
è disponibile tramite CDN:
<!-- Inclusione della libreria Marked tramite CDN -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
Una volta caricato lo script, l'oggetto globale marked diventa disponibile nel contesto della pagina.
Questo oggetto espone il metodo principale marked.parse(), che accetta una stringa in formato Markdown
e restituisce la corrispondente rappresentazione HTML.
Struttura HTML di base
Per costruire un convertitore Markdown funzionante nel browser servono tre elementi fondamentali: un campo di input in cui l'utente scrive il testo Markdown, un pulsante che avvia la conversione e un campo di output in cui viene mostrato il risultato HTML. Una possibile struttura è la seguente:
<!-- Area di testo per l'inserimento del Markdown -->
<textarea id="markdown" rows="10" cols="60"
placeholder="Scrivi il tuo Markdown qui..."></textarea>
<!-- Pulsante per avviare la conversione -->
<button id="convert" type="button">Converti</button>
<!-- Area di testo per la visualizzazione dell'HTML generato -->
<textarea id="output" rows="10" cols="60" readonly></textarea>
<!-- Inclusione della libreria Marked -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Script dell'applicazione -->
<script src="app.js"></script>
Il campo output è contrassegnato come readonly perché il suo contenuto viene generato
programmaticamente e non deve essere modificato manualmente dall'utente.
Analisi del codice JavaScript
Il cuore dell'applicazione risiede nel file JavaScript che gestisce la logica di conversione. Esaminiamo il codice nella sua interezza:
'use strict';
(function() {
// Funzione che converte il contenuto Markdown in HTML
// utilizzando la libreria Marked
function markDownToHTML(sourceEl = null, targetEl = null) {
// Verifica che la libreria Marked sia disponibile
if(!marked) return;
// Verifica che gli elementi sorgente e destinazione esistano
if(!sourceEl || !targetEl) return;
// Esegue la conversione e assegna il risultato
// all'elemento di destinazione
targetEl.value = marked.parse(sourceEl.value);
}
// Attende il caricamento completo del DOM
// prima di registrare gli eventi
document.addEventListener('DOMContentLoaded', () => {
// Recupera il riferimento al pulsante di conversione
const convertBtn = document.getElementById('convert');
// Se il pulsante non esiste, interrompe l'esecuzione
if(!convertBtn) return;
// Recupera i riferimenti agli elementi di input e output
const sourceEl = document.getElementById('markdown');
const targetEl = document.getElementById('output');
// Registra l'evento click sul pulsante
convertBtn.addEventListener('click', () => {
markDownToHTML(sourceEl, targetEl);
}, false);
}, false);
})();
La direttiva 'use strict'
La prima riga del file attiva la modalità rigorosa (strict mode) di JavaScript. Questa modalità impone regole più stringenti durante l'esecuzione del codice: impedisce l'uso di variabili non dichiarate, vieta l'assegnazione a proprietà di sola lettura e genera errori espliciti in situazioni che normalmente verrebbero ignorate silenziosamente. L'adozione dello strict mode è considerata una buona pratica nello sviluppo JavaScript moderno.
La IIFE (Immediately Invoked Function Expression)
L'intero codice è racchiuso in una IIFE, ovvero una funzione che viene definita e immediatamente eseguita.
La sintassi (function() { ... })(); crea un ambito di visibilità (scope) isolato,
evitando che variabili e funzioni dichiarate al suo interno inquinino lo scope globale. Questo pattern è
particolarmente utile quando si lavora con più script nella stessa pagina, poiché previene conflitti
di nomi tra variabili e funzioni definite in file diversi.
La funzione markDownToHTML
La funzione markDownToHTML è il nucleo della logica di conversione. Accetta due parametri, entrambi
con valore predefinito null: sourceEl, che rappresenta l'elemento da cui leggere il
testo Markdown, e targetEl, l'elemento in cui scrivere il risultato HTML.
La funzione esegue due controlli di sicurezza prima di procedere. Il primo verifica che l'oggetto globale
marked sia disponibile: se la libreria non è stata caricata correttamente, la funzione termina
senza errori. Il secondo controllo assicura che entrambi gli elementi DOM siano stati forniti come argomenti.
Solo dopo aver superato entrambi i controlli, la funzione invoca marked.parse() passando il valore
testuale dell'elemento sorgente e assegna il risultato alla proprietà value dell'elemento
di destinazione.
La registrazione degli eventi
L'evento DOMContentLoaded garantisce che il codice venga eseguito solo quando il DOM è stato
completamente costruito dal browser, indipendentemente dal fatto che risorse esterne come immagini o fogli
di stile siano ancora in fase di caricamento. All'interno del gestore di questo evento vengono recuperati
i riferimenti ai tre elementi HTML tramite document.getElementById().
Il codice include un ulteriore controllo difensivo: se il pulsante di conversione non viene trovato nel DOM,
l'esecuzione si interrompe immediatamente. Questo impedisce errori nel caso in cui il markup HTML non contenga
l'elemento atteso. Solo dopo aver ottenuto tutti i riferimenti necessari viene registrato l'evento
click sul pulsante, che a sua volta invoca la funzione markDownToHTML.
Il terzo parametro false passato ad addEventListener indica che l'evento deve essere
gestito nella fase di bubbling (propagazione dal basso verso l'alto nel DOM) anziché nella fase di
capturing (dall'alto verso il basso). Si tratta del comportamento predefinito, ma specificarlo
esplicitamente rende il codice più chiaro nelle sue intenzioni.
Il metodo marked.parse()
Il metodo marked.parse() è il punto di ingresso principale della libreria. Accetta una stringa
contenente testo Markdown e restituisce una stringa HTML. Supporta la sintassi Markdown standard, inclusi
titoli, paragrafi, elenchi puntati e numerati, link, immagini, blocchi di codice, testo in grassetto e corsivo,
tabelle e citazioni.
La libreria è inoltre altamente configurabile. È possibile personalizzare il comportamento del parser
attraverso il metodo marked.setOptions():
// Configurazione delle opzioni di Marked
marked.setOptions({
// Abilita le interruzioni di riga con un singolo ritorno a capo
breaks: true,
// Attiva il supporto alla specifica GitHub Flavored Markdown
gfm: true
});
L'opzione breaks modifica il comportamento predefinito del Markdown rispetto agli a capo: di norma
un singolo ritorno a capo viene ignorato e per creare un nuovo paragrafo servono due ritorni a capo consecutivi.
Attivando questa opzione, ogni ritorno a capo produce un elemento <br>.
L'opzione gfm abilita il supporto a GitHub Flavored Markdown, un'estensione della specifica
standard che aggiunge funzionalità come le tabelle, i blocchi di codice delimitati da triple backtick,
il testo barrato e le liste di attività.
Gestione della sicurezza
Quando si converte Markdown in HTML all'interno del browser, è fondamentale considerare gli aspetti legati alla sicurezza. Se il testo Markdown proviene da fonti non controllate, come l'input di un utente, potrebbe contenere codice JavaScript malevolo incorporato in tag HTML. Per mitigare questo rischio, Marked offre la possibilità di utilizzare una libreria di sanitizzazione come DOMPurify:
// Importazione e utilizzo di DOMPurify per sanificare l'output HTML
// generato dalla conversione Markdown
const rawHTML = marked.parse(sourceEl.value);
// Rimozione di eventuali script o attributi pericolosi dall'HTML
const sanitizedHTML = DOMPurify.sanitize(rawHTML);
targetEl.value = sanitizedHTML;
DOMPurify analizza il codice HTML prodotto da Marked e rimuove qualsiasi elemento o attributo potenzialmente
pericoloso, come tag <script>, gestori di eventi inline (onclick,
onerror) e URL con schema javascript:. L'uso combinato di Marked e DOMPurify
rappresenta una strategia solida per la conversione sicura del Markdown in ambienti in cui l'input
dell'utente non può essere considerato attendibile.
Rendering HTML nell'interfaccia
Nel codice di esempio, l'HTML generato viene inserito in un campo <textarea> tramite la
proprietà value. Questo approccio mostra il codice sorgente HTML come testo leggibile, utile
a scopo didattico o per copiare il markup risultante. Se invece si desidera visualizzare l'HTML renderizzato
dal browser, è possibile utilizzare la proprietà innerHTML su un elemento contenitore:
// Funzione che renderizza il Markdown come HTML visibile
// nel browser, anziché mostrarne il codice sorgente
function renderMarkdown(sourceEl = null, targetEl = null) {
if(!marked) return;
if(!sourceEl || !targetEl) return;
// Inserisce l'HTML generato come contenuto dell'elemento,
// permettendo al browser di renderizzarlo visivamente
targetEl.innerHTML = marked.parse(sourceEl.value);
}
In questo caso, l'elemento targetEl non sarà più un <textarea> ma un generico
contenitore HTML in cui il browser interpreterà e visualizzerà i tag generati da Marked, mostrando titoli
formattati, elenchi strutturati, link cliccabili e tutti gli altri elementi del Markdown convertito.
Utilizzo di Marked con i renderer personalizzati
Marked mette a disposizione un sistema di renderer che consente di sovrascrivere il modo in cui specifici elementi Markdown vengono tradotti in HTML. Ad esempio, è possibile fare in modo che tutti i link generati si aprano in una nuova scheda del browser:
// Creazione di un renderer personalizzato per modificare
// il comportamento predefinito dei link
const customRenderer = new marked.Renderer();
// Sovrascrittura del metodo di rendering dei link
// per aggiungere l'attributo target="_blank"
customRenderer.link = function(href, title, text) {
// Costruzione del tag <a> con apertura in nuova scheda
const titleAttr = title ? ` title="${title}"` : '';
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
};
// Applicazione del renderer personalizzato alla configurazione
marked.setOptions({
renderer: customRenderer
});
L'attributo rel="noopener noreferrer" è una misura di sicurezza che impedisce alla pagina
di destinazione di accedere all'oggetto window.opener della pagina di origine, prevenendo
potenziali attacchi di tipo tabnapping.
Demo
JavaScript Marked: Markdown in browser
Conclusioni
La libreria Marked si distingue per la sua semplicità di integrazione e la sua versatilità. Il codice analizzato in questo articolo mostra come, con poche righe di JavaScript, sia possibile costruire un convertitore Markdown funzionante direttamente nel browser. I pattern utilizzati (strict mode, IIFE, controlli difensivi, registrazione degli eventi dopo il caricamento del DOM) rappresentano pratiche consolidate nello sviluppo JavaScript e contribuiscono a rendere il codice robusto e manutenibile.
Per applicazioni più complesse, Marked offre ampie possibilità di personalizzazione attraverso le opzioni di configurazione, i renderer personalizzati e le estensioni. Combinata con strumenti di sanitizzazione come DOMPurify, la libreria può essere impiegata in modo sicuro anche in contesti dove l'input dell'utente rappresenta un potenziale vettore di attacco.