Zum Inhalt springen
Blume
Esc
↑↓navigieren↵öffnen⌘Jvorschau
Auf dieser Seite

Islands

Lege eine interaktive Komponente in islands/ ab und nutze sie in jeder MDX-Seite — automatisch hydriert, ohne Import pro Seite.

Blume rendert deine Dokumentation standardmäßig als statisches HTML mit null JavaScript. Wenn du etwas Interaktives brauchst — eine Live-Demo, ein Diagramm, einen Playground — fügst du eine Island hinzu: eine Framework-Komponente, die JS nur für sich selbst ausliefert, und nur auf den Seiten, die sie verwenden.

Die islands/-Konvention

Lege eine Komponente in einen islands/-Ordner im Stammverzeichnis deines Projekts. Ihr Dateiname wird zu einer Komponente, die du in jeder .mdx-Seite verwenden kannst, ganz ohne Import:

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Clicked {count}</button>;
}
Here's a live counter: <Counter />

Der Dateiname ist der Komponentenname, er muss also ein PascalCase-Bezeichner sein — nur Buchstaben, Ziffern und Unterstriche (Counter.tsx → <Counter />). Kleingeschriebene Dateinamen und Namen mit Bindestrichen/Punkten/Leerzeichen (wie Time-Picker.tsx) werden mit einer Build-Warnung übersprungen. Wenn zwei Islands zum selben Namen aufgelöst werden — Counter.tsx und Counter.vue oder zwei Counter.tsx-Dateien in verschiedenen Unterordnern —, behält Blume die nach Dateipfad erste und ignoriert die andere mit einer Build-Warnung.

Eine Island ist die einzige Möglichkeit, von einer Seite aus Code im Browser auszuführen. Ein JavaScript-Event-Handler an einem einfachen HTML-Element in MDX, etwa <button onClick={() => open()}>, wird nie ausgeführt: Die Seite ist statisches HTML, daher wird die Funktion als Text in das Attribut geschrieben. Blume warnt davor mit BLUME_MDX_EVENT_HANDLER an der betreffenden Zeile. Verschiebe dieses Markup in eine Island und verwende die Island in der Seite.

Islands in components.ts registrieren

Wenn du Islands lieber bei deinen übrigen Komponenten aufbewahren möchtest — oder ihnen einen anderen Namen als der Datei geben willst — registriere sie mit defineComponents als mdx-Eintrag mit einem client-Modus. Mehr ist eine Island nicht: eine MDX-Komponente, die hydriert. Jeder Eintrag steht in jeder MDX-Seite zur Verfügung, und ein Eintrag mit demselben Namen wie eine Datei in islands/ ersetzt diese.

import { defineComponents } from "blume";
import Counter from "./widgets/Counter.tsx";

export default defineComponents({
  mdx: {
    Counter: { component: Counter, client: "visible" }, // <Counter /> in any MDX page, hydrated
  },
});

Referenziere die Komponente per Import oder über einen Pfad-String und lege den Hydrierungsmodus pro Island fest ("media" erwartet zusätzlich eine media-Query):

export default defineComponents({
  mdx: {
    Chart: { component: "./widgets/Chart.tsx", client: "only" },
  },
});

Anders als beim Ordner gibt es hier keinen Standard: Ein mdx-Eintrag ohne client wird als statisches HTML gerendert, und Blume warnt, wenn dieser Eintrag eine React-, Vue- oder Svelte-Komponente ist. Blume liest components.ts statisch, daher muss ein Eintrag eine importierte Komponente, ein Pfad-String oder ein { component, client, media }-Objektliteral sein — unter Anpassung findest du die akzeptierten Formen und den Fehler, den du andernfalls bekommst.

Hydrierung

Standardmäßig nutzt eine Island client:visible: Sie hydriert, sobald die lesende Person sie in den sichtbaren Bereich scrollt, sodass auch eine Seite voller Islands sofort lädt. Entscheide dich mit einem export const client in der Island-Datei für eine andere Strategie:

// Skip server rendering entirely — for components that touch the DOM/window.
export const client = "only";

export default function Chart() {
  /* ... */
}
client-Wert Hydriert Verwende es für
"visible" (Standard) Beim Scrollen in den sichtbaren Bereich Die meisten Islands
"load" Sofort beim Laden der Seite UI oberhalb der Falz, die sofort da sein muss
"idle" Wenn der Haupt-Thread untätig ist Nicht dringende Interaktivität
"only" Nur clientseitig, nie serverseitig gerendert Bibliotheken, die window/document brauchen (Diagramme, Editoren)
"media" (nur components.ts) Wenn eine CSS-Media-Query zutrifft UI, die nur bei bestimmten Größen läuft, etwa ein reines Mobile-Menü

"media" braucht die Query direkt daneben, daher steht es nur einem components.ts-Eintrag zur Verfügung — { component: Menu, client: "media", media: "(max-width: 50em)" }. Eine islands/-Datei, die es deklariert, fällt mit einer Warnung auf "visible" zurück.

Frameworks

React funktioniert sofort — Blume aktiviert es automatisch, sobald dein Projekt eine .tsx/.jsx-Island enthält.

Der React Compiler ist standardmäßig aktiv, sobald React aktiviert ist, sodass deine Islands automatisch memoisiert werden — handgeschriebene useMemo/useCallback sind nicht nötig. Er wird mit Blume ausgeliefert; es gibt nichts zu installieren. Deaktivieren kannst du ihn in blume.config.ts:

export default defineConfig({
  react: { compiler: false },
});

Vue und Svelte werden ebenfalls unterstützt; installiere die passende Astro-Integration, und Blume verdrahtet den Renderer, sobald es eine .vue- oder .svelte-Island sieht:

# Vue
npm install @astrojs/vue vue

# Svelte
npm install @astrojs/svelte svelte
<script setup>
import { ref } from "vue";
const on = ref(false);
</script>

<template>
  <button @click="on = !on">{{ on ? "On" : "Off" }}</button>
</template>

Props, die du in MDX übergibst (<Counter start={5} />), werden an die Komponente weitergereicht, und Kindelemente (<Counter>label</Counter>) kommen als Standard-Slot an.

Hooks

Islands hydrieren eigenständig, es gibt also keinen React-Context, durch den sich Projektdaten durchreichen ließen. Stattdessen liest blume/hooks einen kleinen Snapshot, den das Layout in die Seite serialisiert — kein Prop-Drilling nötig:

import { useBlume, usePage } from "blume/hooks";

export default function PageInfo() {
  const blume = useBlume();
  const page = usePage();
  if (!(blume && page)) {
    return null;
  }
  return (
    <p>
      You're reading <strong>{page.title}</strong> on {blume.config.title}.
    </p>
  );
}
Hook Gibt zurück
useBlume() { config, navigation } für die Website, oder null vor dem Mount
usePage() { route, title } für die aktuelle Seite, oder null vor dem Mount
useSearch() { search, results, loading } — den konfigurierten Suchanbieter abfragen
useAssistant() { ask, messages, loading, reset } — vom Assistenten-Endpunkt streamen

useBlume() und usePage() geben null zurück, bis die Island mountet (damit Server und Client denselben ersten Frame rendern) — sichere das ab. Der Snapshot wird nur auf Seiten ausgegeben, die React ausliefern, eine vollständig statische Website zahlt also nichts dafür.

Auf benutzerdefinierten Seiten, die mit PageLayout gebaut sind, übergib clientData, damit Islands dort darauf zugreifen können:

<PageLayout
  clientData={{ config: data.config, navigation: data.navigation, page: { route: "/", title: "Home" } }}
  {/* …other props… */}
/>

Zuletzt aktualisiert am

War diese Seite hilfreich?