Heim > Artikel > Web-Frontend > Interaktive Komponenten in Markdown
Kürzlich habe ich einen Kalender implementiert und er hat so gut geklappt, dass ich den Ansatz dokumentieren und mit einem breiteren Publikum teilen wollte. Und ich wollte unbedingt direkt im Artikel ein tatsächlich überprüfbares Ergebnis meiner Arbeit haben.
Ich arbeite schon seit einiger Zeit an meiner Website und dieser Ideenschub startete die nächste Iteration. Und letztendlich zu einem weiteren kompletten Neuaufbau geführt, aber in dieser Geschichte geht es nicht um meinen Kampf mit dem Perfektionismus. Es geht darum, dies umzudrehen:
HTML comes with a lot of ready to use and flexible elements, but date selector has a handful of limitations and the need to write your own calendar / date input emerges sooner rather than later. In this tutorial I'll walk you through implementing a calendar view and show how you can extend its functionality to fit your booking widget or dashboard filter. Here's how the final result might look like: <!--render:custom-calendar-component/CalendarExample.ssi.tsx-->
Dazu:
Meine Website läuft auf Deno und verwendet seit kurzem Hono und Hono/JSX, aber der Ansatz funktioniert mit jeder JS-basierten Laufzeit und JSX.
Blog-Beiträge sind, wie Sie bereits bemerkt haben, Markdown-Dateien mit Attributen, die beim Erstellen mit Marked und Front Matter in statisches HTML konvertiert werden.
Nach einigem Hin und Her habe ich mich für diesen Workflow entschieden:
Der Kommentar würde eine Art Präfix benötigen, z.B. render und im Grunde nur ein Pfad zu dieser Komponente:
<!--render:custom-calendar-component/CalendarExample.ssi.tsx-->
Man könnte nach dem Pfad auch Requisiten hinzufügen, aber für meinen Anwendungsfall war das nicht nötig, also habe ich diesen Teil übersprungen.
Bevor wir etwas im Browser hydrieren können, müssen wir HTML aus der JSX-Komponente rendern. Dazu müssen wir „nur“ die HTML-Rendering-Logik mit einem benutzerdefinierten Renderer überschreiben:
export default class Renderer extends Marked.Renderer { constructor(private baseUrl: string, options?: Marked.marked.MarkedOptions) { super(options); } override html(html: string): string { const ssiMatch = /<!--render:(.+)-->/.exec(html); if (ssiMatch?.length) { const filename = ssiMatch[1]; const ssi = SSIComponents.get(filename); if (!ssi) return html; const content = render(createElement(ssi.Component, {})); return [ content, `<script type="module" src="${ssi.script}"></script>`, ].join(""); } return html; } }
Die Logik ist ganz einfach: Überprüfen Sie, ob die HTML-Zeichenfolge mit // übereinstimmt, und rendern Sie dann JSX. Ganz einfach, wenn man die Komponente zur Hand hat.
Mein Blog-Inhalt wird statisch generiert, daher habe ich mich natürlich für den gleichen Ansatz entschieden:
So sieht mein Build-Skript aus:
const rawContent = await readDir("./content"); const content: Record<string, Article> = {}; const ssi: Array<string> = []; for (const pathname in rawContent) { if (pathname.endsWith(".ssi.tsx")) { ssi.push(pathname); continue; } } const scripts = await compileSSI(ssi.map((name) => `./content/${name}`)); const ssiContents = ` import type { FC } from 'hono/jsx'; const SSIComponents = new Map<string,{ Component: FC, script: string }>(); ${ scripts ? ssi .map( (pathname, i) => `SSIComponents.set("${pathname}", { Component: (await import("./${pathname}")).default, script: "${scripts[i]}" })` ) .join("\n") : "" } export default SSIComponents; `; await Deno.writeFile("./content/ssi.ts", new TextEncoder().encode(ssiContents));
Lassen Sie sich nicht zu sehr an Deno-spezifische Funktionen hängen, es kann leicht mit Node oder etwas anderem umgeschrieben werden.
Die Magie liegt darin, eine Textdatei zu schreiben, die JavaScript-Code ähnelt.
Dieses Skript:
const ssiContents = ` import type { FC } from 'hono/jsx'; const SSIComponents = new Map<string,{ Component: FC, script: string }>(); ${ scripts ? ssi .map( (pathname, i) => `SSIComponents.set("${pathname}", { Component: (await import("./${pathname}")).default, script: "${scripts[i]}" })` ) .join("\n") : "" } export default SSIComponents; `;
Gibt eine Zeichenfolge wie diese zurück:
import type { FC } from 'hono/jsx'; const SSIComponents = new Map<string,{ Component: FC, script: string }>(); SSIComponents.set("custom-calendar-component/CalendarExample.ssi.tsx", { Component: (await import("./custom-calendar-component/CalendarExample.ssi.tsx")).default, script: "/content/custom-calendar-component/CalendarExample.ssi.js" }) export default SSIComponents;
Die dann importiert und im Renderer verwendet werden können :)
Code, der den Code schreibt! Magie! Und keine KI wurde dabei verletzt: nur Ihre Metaprogrammierung der alten Schule.
Und schließlich besteht das letzte Puzzleteil darin, die Komponente im Frontend mit Feuchtigkeit zu versorgen. Ich habe dafür esbuild verwendet, aber ich persönlich plane, es auf Vite oder irgendetwas anderes umzustellen, das mit HMR geliefert wird.
Trotzdem sieht es so aus:
HTML comes with a lot of ready to use and flexible elements, but date selector has a handful of limitations and the need to write your own calendar / date input emerges sooner rather than later. In this tutorial I'll walk you through implementing a calendar view and show how you can extend its functionality to fit your booking widget or dashboard filter. Here's how the final result might look like: <!--render:custom-calendar-component/CalendarExample.ssi.tsx-->
Sie können einen Dummy-Einstiegspunkt bemerken, der den Wert Null hat, aber esbuild zwingt, Dateien in seinen eigenen Ordnern zu erstellen und vorhersehbare Pfade zu haben.
Und ssi-tsconfig.json ist ziemlich allgemein gehalten:
<!--render:custom-calendar-component/CalendarExample.ssi.tsx-->
Was die eigentliche Frontend-Hydratisierung angeht, habe ich den einfachen Weg gewählt und einfach Folgendes am Ende meiner .ssi.tsx-Datei hinzugefügt:
export default class Renderer extends Marked.Renderer { constructor(private baseUrl: string, options?: Marked.marked.MarkedOptions) { super(options); } override html(html: string): string { const ssiMatch = /<!--render:(.+)-->/.exec(html); if (ssiMatch?.length) { const filename = ssiMatch[1]; const ssi = SSIComponents.get(filename); if (!ssi) return html; const content = render(createElement(ssi.Component, {})); return [ content, `<script type="module" src="${ssi.script}"></script>`, ].join(""); } return html; } }
Ich bin mir sicher, dass der Leser einen eleganteren Weg finden würde, aber das ist auch schon alles!
Fühlen Sie sich frei, den Code zu optimieren (Link zum Repo finden Sie unten), fügen Sie Ihre eigene Note hinzu und teilen Sie Ihre Gedanken mit!
Das obige ist der detaillierte Inhalt vonInteraktive Komponenten in Markdown. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!