Встраивание на другие сайты
Добавьте приложение на другой сайт одним тегом script: text build --embed, <text-console>, отрендеренные на сервере маршруты и хостинг.
text build --embed собирает ваше приложение так, чтобы другой сайт мог добавить его одним тегом script: например, маркетинговая страница, сайт документации, страница WordPress или Webflow либо приложение React 18. Посетители, открывшие DevTools, увидят ваше приложение в консоли. Остальные посетители загрузят embed.js (около 3 КБ с gzip) и больше ничего, если на странице нет <text-console>.
Встраивание экспериментально. text build --embed, опция embed, <text-console> и экспорты @textjs/app/experimental могут измениться в минорном релизе. См. Несовместимые изменения.
Добавить приложение на сайт
Соберите приложение:
text build --embedЗагрузите dist/ на любой статический хостинг или CDN либо скопируйте его в папку public/ сайта, затем добавьте один тег на страницы сайта:
<script type="module" src="https://console.example.com/embed.js"></script>embed.js загружает остальные файлы относительно собственного URL, поэтому dist/ работает с любым путём или источником. В нём находятся только embed.js и assets/, без index.html или 404.html, которые могли бы заменить собственные файлы сайта. Приложению с маршрутами, отрендеренными на сервере, обработчиками маршрутов или middleware нужен сервер вместо обычного размещения статических файлов. Адаптеры vercel и cloudflare выполняют обработчики маршрутов и middleware, а маршрутам, отрендеренным на сервере, нужен text start или адаптер bun.
Чтобы показать посетителям, где находится приложение, добавьте <text-console> в любом месте страницы. Любой элемент с data-textjs-engage тоже запускает приложение при нажатии:
<button type="button" data-textjs-engage>Open in the console</button>Чтобы попробовать локально, запустите text dev --embed. Он отдаёт /embed.js с новым первым кадром на каждый запрос и страницу предпросмотра по адресу / (/__textjs/embed с standalone), которая загружает его так же, как другой сайт. Сайт на другом локальном порту тоже может загрузить http://localhost:3000/embed.js.
Конфигурация
text build --embed равнозначно embed: true в text.config.ts. Передайте объект, чтобы изменить значения по умолчанию:
import { defineConfig } from "@textjs/app";export default defineConfig({ embed: { initialPath: "/status", },});| Опция | По умолчанию | Описание |
|---|---|---|
initialPath | "/" | Маршрут, на котором открывается консоль. Должен начинаться с / |
globalName | "text" | Глобальная переменная, в которой приложение размещает свой консольный API после запуска. false отключает её создание |
initialClear | false | Очистить консоль страницы перед первым кадром. По умолчанию предыдущие сообщения страницы остаются над приложением |
standalone | false | Собирать и собственные страницы приложения (index.html), как это делает text build без embed |
allowOrigins | [] | Сайты, страницы которых могут запускать маршруты с серверным рендерингом с этого сервера. См. Маршруты, отрендеренные на сервере |
credentials | false | Отправлять учётные данные браузера для этого сервера с запросами активных сессий из allowOrigins вместо удаления cookie и Authorization. Требует точных источников |
viewport.width × viewport.height (по умолчанию 120 × 36) задаёт размер первого кадра. title, hints и action задают значения по умолчанию для <text-console>. В embed.js нет первого кадра, подготовленного пререндерингом, при ppr: false или когда middleware.ts может соответствовать initialPath, поскольку любой может скачать embed.js. Зеркало остаётся пустым, пока приложение не отрисует кадр.
Поведение
- Больше ничего не загружается до намерения открыть консоль.
embed.jsвыводит первый кадр, подготовленный пререндерингом, затем ждёт. Приложение загружается, когда DevTools закреплены (при загрузке или позже), когда посетитель нажимает на странице сочетание клавиш DevTools (F12, Ctrl+Shift+J, I, C или K либо ⌘⌥J, I, C или K на Mac) или когда нажимает<text-console>либо элемент[data-textjs-engage]. После этого перезагрузки в той же вкладке запускают его сразу. DevTools в отдельном окне не меняют размер страницы, поэтому посетители, открывшие их через меню, видят первый кадр, пока не нажмут один из этих элементов. - На сенсорных экранах приложение не запускается. На устройстве, соответствующем
(hover: none) and (pointer: coarse),embed.jsничего не выводит и не добавляет обработчиков событий. Единственный загружаемый им файл — файл<text-console>для зеркала. - Нажатия клавиш доходят до приложения только после того, как посетитель его активирует. Для этого нужно нажать
<text-console>или элемент[data-textjs-engage]либо щёлкнуть по странице (не по ссылке, кнопке или полю), чтобы вернуть фокус из DevTools. Ввод в собственных полях страницы или в элементе[data-textjs-bypass]никогда не попадает в приложение, а колесо всегда прокручивает страницу. - URL, история и заголовок страницы никогда не меняются. Маршруты,
<Link>иuseRouter()выполняют навигацию внутри консоли. В приложениях с клиентским рендерингом абсолютные URL открываются в новой вкладке. - Приложение Text.js на странице имеет приоритет. При его запуске работающее встроенное приложение размонтируется, перестаёт считывать клавиши и удаляет свою глобальную переменную. Встроенное приложение, загружаемое, пока другое приложение занимает консоль, выводит одно предупреждение и не запускается.
- Встроенное приложение включает собственный React 19. В нём нет
react-domили импортов без пути, поэтому версия React на странице, карта импортов дляreactи React DevTools на него не влияют.
Порядок загрузки
| Файл | Когда загружается | gzip |
|---|---|---|
embed.js | Вместе со страницей | 2,9 КБ |
assets/text-console-[hash].js | Если на странице есть <text-console> | 4,0 КБ |
assets/embed-client-[hash].js и assets/yoga-[hash].wasm | При намерении открыть консоль | 112,8 КБ + 29,0 КБ |
assets/embed-player-[hash].js | При намерении открыть консоль, вместо клиента, для маршрута, отрендеренного на сервере | 22,7 КБ |
assets/bidi-[hash].js | Когда приложение впервые показывает текст справа налево (см. Загрузка движка bidi) | 10,1 КБ |
Размеры указаны для блока состояния с рамкой при 120 × 36. text build --embed выводит размеры вашего приложения. assets/embed-[hash].js — копия embed.js под именем, которое никогда не меняется (см. Развёртывания).
<text-console>
embed.js определяет <text-console> и загружает его файл, только если такой элемент есть на странице. Он использует Shadow DOM, поэтому CSS страницы не проникает внутрь, а политика style-src 'self' не требует изменений.
<text-console></text-console><text-console mirror></text-console><text-console chrome></text-console>Без атрибутов это однострочная панель. Она показывает сочетание клавиш браузера для открытия консоли, затем предлагает щёлкнуть, затем перечисляет клавиши приложения, которые подсвечиваются, когда их нажатия доходят до приложения. В Safari первый шаг предлагает включить «Показывать функции для веб-разработчиков» в настройках Safari.
С mirror элемент показывает кадр приложения на странице: первый кадр, подготовленный пререндерингом, до загрузки всего остального, затем работающее приложение. Нажатие запускает приложение и передаёт ему нажатия клавиш посетителя, поэтому приложение работает с закрытыми DevTools. chrome рисует окно DevTools вокруг зеркала и подразумевает mirror.
| Атрибут | Описание |
|---|---|
mirror | Показывать кадры приложения |
chrome | Показывать кадры в окне DevTools |
title | Заголовок панели (по умолчанию: title в text.config.ts) |
hints | Список клавиш в формате "← →:switch region, Enter:open" (по умолчанию: hints в text.config.ts) |
action | Первое действие, отображаемое как «Щёлкните здесь и …» (по умолчанию: action в text.config.ts или нажатие клавиши из первой подсказки) |
На сенсорных экранах зеркало показывает первый кадр, не запуская приложение, а панель скрыта. Текст элемента написан на английском.
Резервирование места
text build --embed выводит одну строку CSS для сетки вашего приложения. Вставьте её в таблицу стилей страницы. Она задаёт размер <text-console> до загрузки файла элемента, поэтому при его загрузке ничего на странице не сдвигается. Для стандартной сетки 120 × 36 строка выглядит так:
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}}Высота панели составляет 58 px. Зеркало сохраняет соотношение сторон сетки до её естественной ширины (885 px при 120 столбцах), а chrome добавляет 43 px. Размер не зависит от шрифтов. reserveCss(cols, rows) из @textjs/app/experimental возвращает ту же строку.
Зеркало в начальной видимой области обычно является самым большим элементом страницы, поэтому определяет её Largest Contentful Paint (LCP). Первый кадр отрисовывается после последовательной загрузки embed.js и файла элемента. Остальная страница отрисовывается так же, как без встроенного приложения. Зеркало ниже по странице не влияет на LCP. На странице с собственными отложенными скриптами, например в приложении React, <script type="module" async src="…"> запускает embed.js, не дожидаясь их, поэтому зеркало в начальной видимой области отрисовывается раньше.
Стили
Чтобы выровнять зеркало по центру, задайте ему автоматические отступы по строчной оси:
text-console { margin-inline: auto;}По умолчанию оно тёмное. Задайте color-scheme: light для светлых цветов или задайте два цвета, из которых смешиваются все остальные оттенки. ::part(frame) задаёт стили самого кадра. chrome сохраняет собственный фон DevTools.
text-console { --text-console-bg: #fafafa; --text-console-fg: #171717;}text-console::part(frame) { border-radius: 0;}Маршруты, отрендеренные на сервере
Когда initialPath указывает на маршрут с export const runtime = "server", embed.js загружает проигрыватель кадров вместо клиентской среды выполнения. Он получает поток кадров с сервера, откуда загружен embed.js, поэтому запустите text start или адаптер bun. С другими адаптерами сборка завершается ошибкой. См. Серверный рендеринг.
Страница на другом сайте может открыть активную сессию, только если её источник указан в allowOrigins:
import { defineConfig } from "@textjs/app";export default defineConfig({ embed: { initialPath: "/status", allowOrigins: ["https://www.example.com", "https://*.example.com"], },});- Сессия отвечает только источнику, который её открыл.
- По умолчанию Text.js удаляет cookie и заголовок
Authorizationу сессий с других сайтов, поэтому middleware видит посетителя, не вошедшего в систему.credentials: trueсохраняет их и требует точных источников. Браузер отправляет их, когда страница и сервер имеют общий регистрируемый домен (www.example.comиconsole.example.com), а cookie распространяется на сервер (Domain=example.comили установлен наconsole.example.com). С других сайтов он отправляет только cookie сSameSite=None; Secure, а браузеры, блокирующие сторонние cookie, не отправляют ничего. - Активная сессия отправляет предварительные запросы
OPTIONSк/__textjs/liveсразу при подключении и при повторном подключении. Предварительные запросы не содержат cookie илиAuthorization, поэтому middleware, соответствующее этому пути, должно пропускать запросыOPTIONSбез входа в систему. - При работе за обратным прокси или CDN задайте
server.trustProxy(см. За прокси). Без него у каждого посетителя будет адрес прокси, поэтому все они будут делить один лимит сессий на клиента.
text dev принимает страницы с localhost, 127.0.0.1 и [::1] на любом порту без allowOrigins, если не задано credentials.
Хостинг
text start и адаптеры bun, vercel и cloudflare отдают сборку для встраивания с этими заголовками. Адаптер static записывает их в _headers, который читают Cloudflare Pages и Netlify. На любом другом хостинге, например в бакете хранилища или собственной папке public/ сайта, задайте их самостоятельно:
| Путь | Заголовки |
|---|---|
/embed.js | Cache-Control: public, max-age=0, must-revalidate |
/assets/* | Cache-Control: public, max-age=31536000, immutable |
| Оба пути, если сайт находится на другом источнике | Access-Control-Allow-Origin: * и Cross-Origin-Resource-Policy: cross-origin |
Браузеры загружают модульные скрипты и WebAssembly с CORS, поэтому файлам на другом источнике нужен Access-Control-Allow-Origin. Отдавайте yoga-[hash].wasm как application/wasm со сжатием (см. WebAssembly).
Развёртывания
embed.js проходит повторную валидацию при каждой загрузке страницы, поэтому новые посетители сразу получают новую версию. Вкладка, загрузившая предыдущий embed.js, по-прежнему импортирует файлы предыдущей сборки, когда посетитель открывает консоль, возможно, спустя несколько часов. Сохраняйте assets/ предыдущей версии на сервере: копируйте новые файлы поверх старых вместо синхронизации с удалением и удаляйте старые файлы через день или два. Чтобы закрепить сборку, укажите её assets/embed-[hash].js в теге. Файлы этой сборки тоже должны оставаться доступны.
Если файлы удалены, embed.js выводит одно предупреждение с просьбой перезагрузить страницу, не запускает приложение и не перезагружает страницу самостоятельно.
Политика безопасности контента
embed.js не нужны встроенные скрипты или стили. Добавьте следующие значения в политику страницы, на которой размещено приложение, заменив origin на 'self', если dist/ находится на том же origin:
| Директива | Значение | Назначение |
|---|---|---|
script-src | https://console.example.com | embed.js и его чанки |
script-src | 'wasm-unsafe-eval' | Yoga в приложениях с клиентским рендерингом |
connect-src | https://console.example.com | yoga-[hash].wasm и /__textjs/live для маршрутов, отрендеренных на сервере |
img-src | data: | Только для <text-console mirror>, если приложение рисует <Image> или <Pixels> |
Самостоятельная загрузка приложения
@textjs/app/experimental экспортирует части, из которых состоит embed.js, для страниц, загружающих приложение своим способом:
| Экспорт | Описание |
|---|---|
startEmbed(paint, boot, src, element?) | Выполняет те же действия, что и embed.js: выводит первый кадр, ждёт намерения открыть консоль, затем вызывает boot |
bootEmbedClient(options) | Запускает приложение с клиентским рендерингом, маршрутизацией в памяти и ограничением клавиатурного ввода со страницы |
bootEmbedPlayer(options) | Запускает проигрыватель кадров для маршрута, отрендеренного на сервере |
createHostInput(options) | Клавиатурный ввод, подчиняющийся правилам выше |
createTextConsoleMount(config) | Возвращает функцию mount(element, shell?) для <text-console> |
reserveCss(cols, rows) | CSS, резервирующий место для <text-console> |
ENGAGE_SELECTOR и BYPASS_SELECTOR — селекторы элементов, запускающих приложение, и элементов, которые обрабатывают свои клавиши самостоятельно.