In andere Websites einbetten

Füge eine App mit einem Script-Tag zu einer anderen Website hinzu: text build --embed, <text-console>, serverseitig gerenderte Routen und Hosting.

text build --embed baut deine App so, dass eine andere Website sie mit einem einzigen Script-Tag einbinden kann, etwa eine Marketingseite, eine Dokumentationswebsite, eine WordPress- oder Webflow-Seite oder eine React-18-App. Besucher, die DevTools öffnen, sehen deine App in der Konsole. Andere Besucher laden embed.js herunter (etwa 3 kB mit gzip) und sonst nichts, sofern die Seite kein <text-console> enthält.

Embeds sind experimentell. text build --embed, die Option embed, <text-console> und die Exporte aus @textjs/app/experimental können sich in einem Minor-Release ändern. Siehe Inkompatible Änderungen.

Eine App in eine Website einbinden

Baue die App:

text build --embed

Lade dist/ auf einen beliebigen statischen Host oder ein CDN hoch oder kopiere es in den public/-Ordner der Website und füge dann ein Tag auf den Seiten der Website hinzu:

<script type="module" src="https://console.example.com/embed.js"></script>

embed.js lädt seine anderen Dateien relativ zur eigenen URL, sodass dist/ unter jedem Pfad und jeder Origin funktioniert. Es enthält nur embed.js und assets/, ohne index.html oder 404.html, die die entsprechenden Dateien der Website ersetzen könnten. Eine App mit serverseitig gerenderten Routen, Route-Handlern oder Middleware braucht einen Server statt eines rein statischen Uploads. Die Adapter vercel und cloudflare führen Route-Handler und Middleware aus, und serverseitig gerenderte Routen benötigen text start oder den bun-Adapter.

Um Besuchern zu zeigen, wo die App ist, füge <text-console> an einer beliebigen Stelle der Seite hinzu. Auch jedes Element mit data-textjs-engage startet die App beim Drücken:

<button type="button" data-textjs-engage>Open in the console</button>

Um es lokal auszuprobieren, führe text dev --embed aus. Es liefert /embed.js bei jeder Anfrage mit einem frischen ersten Frame aus und stellt unter / (mit standalone unter /__textjs/embed) eine Vorschauseite bereit, die es wie eine andere Website lädt. Eine Website auf einem anderen lokalen Port kann ebenfalls http://localhost:3000/embed.js laden.

Konfiguration

text build --embed entspricht embed: true in text.config.ts. Übergib ein Objekt, um die Standardwerte zu ändern:

text.config.tsts
import { defineConfig } from "@textjs/app";export default defineConfig({  embed: {    initialPath: "/status",  },});
OptionStandardwertBeschreibung
initialPath"/"Die Route, mit der sich die Konsole öffnet. Muss mit / beginnen
globalName"text"Der globale Name, unter dem die App beim Start ihre Konsolen-API installiert. false installiert keine
initialClearfalseDie Konsole der Seite vor dem ersten Frame leeren. Standardmäßig bleiben frühere Ausgaben der Seite über der App stehen
standalonefalseAuch die eigenen Seiten der App (index.html) bauen, wie text build ohne embed
allowOrigins[]Websites, deren Seiten serverseitig gerenderte Routen von diesem Server ausführen dürfen. Siehe Serverseitig gerenderte Routen
credentialsfalseBrowser-Anmeldedaten für diesen Server bei Anfragen von Live-Sitzungen aus allowOrigins mitsenden, statt Cookies und Authorization zu verwerfen. Erfordert exakte Origins

viewport.width × viewport.height (Standard: 120 × 36) ist die Größe des ersten Frames. title, hints und action sind die Standardwerte für <text-console>. Mit ppr: false oder wenn middleware.ts auf initialPath zutreffen könnte, enthält embed.js keinen vorgerenderten ersten Frame, da jeder embed.js herunterladen kann. Eine Spiegelansicht bleibt leer, bis die App zeichnet.

Verhalten

  • Bis zur erkannten Konsolennutzung wird nichts weiter geladen. embed.js gibt den vorgerenderten ersten Frame aus und wartet dann. Die App lädt, wenn DevTools angedockt ist (beim Laden oder später), wenn der Besucher auf der Seite ein DevTools-Tastenkürzel drückt (F12, Ctrl+Shift+J, I, C oder K oder auf einem Mac ⌘⌥J, I, C oder K) oder wenn er ein <text-console>- oder [data-textjs-engage]-Element drückt. Danach startet sie beim Neuladen im selben Tab sofort. DevTools in einem eigenen Fenster ändert die Größe der Seite nicht. Besucher, die es über ein Menü geöffnet haben, sehen daher den ersten Frame, bis sie eines dieser Elemente drücken.
  • Auf Touchscreens startet die App nicht. Auf einem Gerät, das (hover: none) and (pointer: coarse) entspricht, gibt embed.js nichts aus und fügt keine Listener hinzu. Es lädt nur die Datei von <text-console> für eine Spiegelansicht.
  • Tasteneingaben erreichen die App erst, nachdem der Besucher sie aktiviert hat. Dazu drückt er ein <text-console>- oder [data-textjs-engage]-Element oder klickt auf die Seite (nicht auf einen Link, eine Schaltfläche oder ein Feld), um den Fokus von DevTools zurückzuholen. Eingaben in den eigenen Feldern der Seite oder in einem [data-textjs-bypass]-Element erreichen die App nie, und das Mausrad scrollt immer die Seite.
  • URL, Verlauf und Titel der Seite ändern sich nie. Routen, <Link> und useRouter() navigieren innerhalb der Konsole. In clientseitig gerenderten Apps öffnen sich absolute URLs in einem neuen Tab.
  • Eine Text.js-App auf der Seite hat Vorrang. Wenn sie startet, wird ein laufendes Embed ausgehängt, liest keine Tasteneingaben mehr und entfernt seinen globalen Namen. Ein Embed, das lädt, während eine andere App die Konsole belegt, gibt eine Warnung aus und startet nicht.
  • Das Embed bündelt sein eigenes React 19. Es enthält weder react-dom noch Bare Imports, sodass die React-Version der Seite, eine Import Map für react und React DevTools keinen Einfluss darauf haben.

Ladezeitpunkte

DateiLadezeitpunktgzip
embed.jsMit der Seite2,9 kB
assets/text-console-[hash].jsWenn die Seite ein <text-console> enthält4,0 kB
assets/embed-client-[hash].js und assets/yoga-[hash].wasmBei erkannter Konsolennutzung112,8 kB + 29,0 kB
assets/embed-player-[hash].jsBei erkannter Konsolennutzung, anstelle des Clients, für eine serverseitig gerenderte Route22,7 kB
assets/bidi-[hash].jsWenn die App erstmals Text von rechts nach links anzeigt (siehe Die Bidi-Engine laden)10,1 kB

Die Größen gelten für einen umrandeten Statuskasten mit 120 × 36. text build --embed gibt die Größen deiner App aus. assets/embed-[hash].js ist eine Kopie von embed.js unter einem Namen, der sich nie ändert (siehe Deployments).

<text-console>

embed.js definiert <text-console> und lädt dessen Datei nur, wenn die Seite ein solches Element enthält. Es verwendet Shadow DOM, sodass das CSS der Seite nicht ins Innere gelangt und eine style-src 'self'-Richtlinie keine Änderungen benötigt.

<text-console></text-console><text-console mirror></text-console><text-console chrome></text-console>

Ohne Attribute ist es eine einzeilige Leiste. Sie zeigt das Tastenkürzel des Browsers zum Öffnen der Konsole, fordert dann zum Klicken auf und listet anschließend die Tasten der App auf, die aufleuchten, sobald sie die App erreichen. In Safari fordert der erste Schritt dazu auf, „Funktionen für Web-Entwickler anzeigen“ in den Safari-Einstellungen zu aktivieren.

Mit mirror zeigt es den Frame der App auf der Seite: den vorgerenderten ersten Frame, bevor etwas anderes lädt, danach die laufende App. Ein Druck darauf startet die App und sendet ihr die Tasteneingaben des Besuchers, sodass die App bei geschlossenem DevTools funktioniert. chrome zeichnet ein DevTools-Fenster um die Spiegelansicht und schließt mirror ein.

AttributBeschreibung
mirrorDie Frames der App anzeigen
chromeDie Frames in einem DevTools-Fenster anzeigen
titleDer Titel der Leiste (Standard: title in text.config.ts)
hintsDie aufzulistenden Tasten, etwa "← →:switch region, Enter:open" (Standard: hints in text.config.ts)
actionDie erste Aktion, angezeigt als „Klicke hier und …“ (Standard: action in text.config.ts oder die Taste des ersten Hinweises drücken)

Auf Touchscreens zeigt eine Spiegelansicht den ersten Frame, ohne die App zu starten, und die Leiste ist ausgeblendet. Der Text des Elements ist auf Englisch.

Platz reservieren

text build --embed gibt eine Zeile CSS für das Raster deiner App aus. Füge sie in das Stylesheet der Seite ein. Sie legt die Größe von <text-console> fest, bevor die Datei des Elements lädt, sodass sich auf der Seite nichts verschiebt, wenn die Datei ankommt. Für das Standardraster mit 120 × 36 lautet die Zeile:

text-console{display:block;min-height:58px}text-console:is([mirror],[chrome]){position:relative;box-sizing:content-box;min-height:0;max-width:885px;aspect-ratio:885/522}text-console[chrome]{padding-top:43px}@media (hover:none) and (pointer:coarse){text-console:not([mirror],[chrome]){display:none}}

Die Leiste ist 58 px hoch. Eine Spiegelansicht behält das Seitenverhältnis des Rasters bis zu seiner natürlichen Breite bei (885 px bei 120 Spalten), und chrome fügt 43 px hinzu. Die Größe hängt nicht von Schriftarten ab. reserveCss(cols, rows) aus @textjs/app/experimental gibt dieselbe Zeile zurück.

Eine Spiegelansicht im anfangs sichtbaren Bereich ist meist das größte Element der Seite und bestimmt daher deren Largest Contentful Paint (LCP). Ihr erster Frame wird gezeichnet, nachdem erst embed.js und dann die Datei des Elements nacheinander geladen wurden. Der Rest der Seite wird so gezeichnet wie ohne das Embed. Eine Spiegelansicht weiter unten auf der Seite beeinflusst LCP nicht. Auf einer Seite mit eigenen verzögert ausgeführten Skripten, etwa einer React-App, führt <script type="module" async src="…"> embed.js aus, ohne auf sie zu warten, sodass eine Spiegelansicht im anfangs sichtbaren Bereich früher gezeichnet wird.

Gestaltung

Um eine Spiegelansicht zu zentrieren, gib ihr automatische Inline-Außenabstände:

text-console {  margin-inline: auto;}

Standardmäßig ist sie dunkel. Setze color-scheme: light für helle Farben oder lege die beiden Farben fest, aus denen alle anderen Farbtöne gemischt werden. ::part(frame) gestaltet den Frame selbst. chrome behält den eigenen Hintergrund von DevTools bei.

text-console {  --text-console-bg: #fafafa;  --text-console-fg: #171717;}text-console::part(frame) {  border-radius: 0;}

Serverseitig gerenderte Routen

Wenn initialPath eine Route mit export const runtime = "server" ist, lädt embed.js den Frame-Player statt der Client-Laufzeit. Er streamt Frames von dem Server, von dem embed.js stammt. Führe daher text start oder den bun-Adapter aus. Bei anderen Adaptern schlägt der Build fehl. Siehe Server-Rendering.

Eine Seite auf einer anderen Website kann nur dann eine Live-Sitzung öffnen, wenn ihre Origin in allowOrigins steht:

text.config.tsts
import { defineConfig } from "@textjs/app";export default defineConfig({  embed: {    initialPath: "/status",    allowOrigins: ["https://www.example.com", "https://*.example.com"],  },});
  • Eine Sitzung antwortet nur der Origin, die sie geöffnet hat.
  • Standardmäßig verwirft Text.js die Cookies und den Authorization-Header von Sitzungen anderer Websites, sodass die Middleware einen abgemeldeten Besucher sieht. credentials: true behält sie bei und erfordert exakte Origins. Der Browser sendet sie, wenn die Seite und der Server eine registrierbare Domain teilen (www.example.com und console.example.com) und das Cookie für den Server gilt (Domain=example.com oder auf console.example.com gesetzt). Von anderen Websites sendet er nur Cookies, die mit SameSite=None; Secure gesetzt wurden, und Browser, die Drittanbieter-Cookies blockieren, senden keine.
  • Eine Live-Sitzung sendet OPTIONS-Preflights an /__textjs/live, sobald sie sich verbindet und wenn sie sich erneut verbindet. Preflights enthalten keine Cookies und kein Authorization, daher muss Middleware, die auf diesen Pfad zutrifft, OPTIONS-Anfragen ohne Anmeldung durchlassen.
  • Setze hinter einem Reverse Proxy oder CDN server.trustProxy (siehe Hinter einem Proxy). Ohne diese Einstellung hat jeder Besucher die Adresse des Proxys, sodass alle dasselbe Sitzungslimit pro Client teilen.

text dev akzeptiert Seiten auf localhost, 127.0.0.1 und [::1] an jedem Port ohne allowOrigins, sofern credentials nicht gesetzt ist.

Hosting

text start und die Adapter bun, vercel und cloudflare liefern einen Embed-Build mit diesen Headern aus. Der static-Adapter schreibt sie in _headers, das Cloudflare Pages und Netlify lesen. Auf jedem anderen Host, etwa einem Storage-Bucket oder im eigenen public/-Ordner der Website, musst du sie selbst setzen:

PfadHeader
/embed.jsCache-Control: public, max-age=0, must-revalidate
/assets/*Cache-Control: public, max-age=31536000, immutable
Beide, wenn die Website auf einer anderen Origin liegtAccess-Control-Allow-Origin: * und Cross-Origin-Resource-Policy: cross-origin

Browser rufen Modulskripte und WebAssembly mit CORS ab, daher benötigen Dateien auf einer anderen Origin Access-Control-Allow-Origin. Liefere yoga-[hash].wasm komprimiert als application/wasm aus (siehe WebAssembly).

Deployments

embed.js wird bei jedem Laden der Seite erneut validiert, sodass neue Besucher sofort ein neues Deployment erhalten. Ein Tab, der die vorherige embed.js geladen hat, importiert weiterhin die Dateien des vorherigen Builds, wenn der Besucher die Konsole öffnet, möglicherweise erst Stunden später. Halte assets/ des vorherigen Deployments online: Kopiere neue Dateien über alte, statt beim Synchronisieren Dateien zu löschen, und entferne alte Dateien nach einem oder zwei Tagen. Um einen Build festzuschreiben, trage seine assets/embed-[hash].js im Tag ein. Auch die Dateien dieses Builds müssen online bleiben.

Wenn die Dateien nicht mehr verfügbar sind, gibt embed.js eine Warnung aus, die den Besucher zum Neuladen auffordert, und startet die App nicht und lädt die Seite nicht selbst neu.

Content Security Policy

embed.js benötigt keine Inline-Skripte oder -Styles. Ergänze die Richtlinie der Host-Seite um diese Einträge und verwende 'self' anstelle des Ursprungs, wenn dist/ auf demselben Ursprung liegt:

DirektiveWertFür
script-srchttps://console.example.comembed.js und seine Chunks
script-src'wasm-unsafe-eval'Yoga in clientseitig gerenderten Apps
connect-srchttps://console.example.comyoga-[hash].wasm und /__textjs/live für serverseitig gerenderte Routen
img-srcdata:Nur ein <text-console mirror>, dessen App <Image> oder <Pixels> zeichnet

Die App selbst laden

@textjs/app/experimental exportiert die Bestandteile von embed.js für Seiten, die eine App auf eigene Weise laden:

ExportBeschreibung
startEmbed(paint, boot, src, element?)Was embed.js ausführt: den ersten Frame ausgeben, auf die Absicht warten, die Konsole zu nutzen, dann boot aufrufen
bootEmbedClient(options)Eine clientseitig gerenderte App mit In-Memory-Routing und für die Seite gesteuerter Tastatureingabe starten
bootEmbedPlayer(options)Den Frame-Player für eine serverseitig gerenderte Route starten
createHostInput(options)Tastatureingabe, die den obigen Regeln folgt
createTextConsoleMount(config)Gibt eine mount(element, shell?)-Funktion für <text-console> zurück
reserveCss(cols, rows)Das CSS, das den Platz für <text-console> reserviert

ENGAGE_SELECTOR und BYPASS_SELECTOR sind die Selektoren für Elemente, die die App starten, und Elemente, die ihre Tastatureingaben selbst verarbeiten.

Diese Seite auf GitHub bearbeiten