SUPER_OLEG_DEV Telegram 129
Там где раньше мы отдавали весь HTML, я пишу в стрим первым чанком всю разметку до <APP />:

<head>
<META />
<LINKS />
<SCRIPTS />
</head>
<body>
...


Для стриминга, на каждый запрос создаю и сохраняю в DI отдельный Duplex стрим, который и отдаю в ответ через Fastify reply.send(stream). Это позволяет легко переиспользовать его в разных трамвайных модулях.

Во время отдачи первого чанка с <head>, renderToPipeableStream уже был запущен (и для сохранения существующего жизненного цикла запроса и для ускорения ответа), и тут нам важно избежать гонки, реакт должен отдавать разметку в стрим строго на слот <APP />, после отдачи первого чанка.

Еще раз подчеркну, что renderToPipeableStream приходится запускать раньше чем мы можем использовать данные из него, то есть один из основных челленджей в задаче идет из необходимости поддержать текущую архитектуру с минимумом доработок.

Для решения проблемы завел сущность ResponseTaskManager с такими возможностями:

- метод для добавления асинхронной задачи (push)
- метод для запуска и ожидания всех задач (process)

Пока максимально простой, в будущем скорее всего понадобится добавлять различные приоритеты для задач, если менять текущую архитектуру со схемой HTML страницы.

Этот менеджер задач позволяет в любое время запушить в очередь таски с записью разметки в стрим, но выполнить их строго в определенный момент времени - в нашем случае после того как отдали клиенту открывающий тег <body> в первом чанке.

Итого, у нас примерно так выглядит ответ клиенту, после того как сгенерировали начальный HTML по схеме:

const [headAndBodyStart, bodyEnd] = html.split('<APP />')

// передаем стрим в ответ клиенту
reply.send(stream)

// пишем первый чанк с head и открывающим body
stream.push(headAndBodyStart)

// тут выполняются задачи, поставленные во время работы renderToPipeableStream
await taskManager.process()

// пишем закрывающий body
stream.push(bodyEnd)

// завершаем ответ
stream.push(null)

Для того что бы данные из renderToPipeableStream писать в стрим по требованию а не по факту их получения:

- Создаем кастомный Writable стрим, который и передаем в метод pipe в коллбэке onShellReady (когда готова первая часть HTML но все отложенные Suspense компоненты вернули fallback)
- Этот стрим на каждый write от реакта добавляет задачу в taskManager с записью HTML в поток
- Также добавляем еще одну задачу в taskManager которая зарезолвится после события finish у нашего Writable стрима (когда все отложенные Suspense компоненты зарезолвлены и реакт выполнил всю работу)

Пример кода без лишних подробностей:

// этот стрим откладывает запись разметки в стрим ответа
class HtmlWritable extends Writable {
_write(chunk, encoding, callback) {
// stream - это именно поток ответа клиенту с предыдущего шага
taskManager.push(() => {
stream.push(chunk)
})

callback()
}
}

// реализацию Deferred добавлю позже
const allReadyDeferred = Deferred();

// задача на ожидание окончания рендера
taskManager.push(() => {
return allReadyDeferred.promise;
})

// событие сработает когда pipe завершит работу
htmlWritable.on('finish', () => {
allReadyDeferred.resolve()
})

const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
// pipe готов передавать данные
pipe(htmlWritable)
}
})
👍5



tgoop.com/super_oleg_dev/129
Create:
Last Update:

Там где раньше мы отдавали весь HTML, я пишу в стрим первым чанком всю разметку до <APP />:

<head>
<META />
<LINKS />
<SCRIPTS />
</head>
<body>
...


Для стриминга, на каждый запрос создаю и сохраняю в DI отдельный Duplex стрим, который и отдаю в ответ через Fastify reply.send(stream). Это позволяет легко переиспользовать его в разных трамвайных модулях.

Во время отдачи первого чанка с <head>, renderToPipeableStream уже был запущен (и для сохранения существующего жизненного цикла запроса и для ускорения ответа), и тут нам важно избежать гонки, реакт должен отдавать разметку в стрим строго на слот <APP />, после отдачи первого чанка.

Еще раз подчеркну, что renderToPipeableStream приходится запускать раньше чем мы можем использовать данные из него, то есть один из основных челленджей в задаче идет из необходимости поддержать текущую архитектуру с минимумом доработок.

Для решения проблемы завел сущность ResponseTaskManager с такими возможностями:

- метод для добавления асинхронной задачи (push)
- метод для запуска и ожидания всех задач (process)

Пока максимально простой, в будущем скорее всего понадобится добавлять различные приоритеты для задач, если менять текущую архитектуру со схемой HTML страницы.

Этот менеджер задач позволяет в любое время запушить в очередь таски с записью разметки в стрим, но выполнить их строго в определенный момент времени - в нашем случае после того как отдали клиенту открывающий тег <body> в первом чанке.

Итого, у нас примерно так выглядит ответ клиенту, после того как сгенерировали начальный HTML по схеме:

const [headAndBodyStart, bodyEnd] = html.split('<APP />')

// передаем стрим в ответ клиенту
reply.send(stream)

// пишем первый чанк с head и открывающим body
stream.push(headAndBodyStart)

// тут выполняются задачи, поставленные во время работы renderToPipeableStream
await taskManager.process()

// пишем закрывающий body
stream.push(bodyEnd)

// завершаем ответ
stream.push(null)

Для того что бы данные из renderToPipeableStream писать в стрим по требованию а не по факту их получения:

- Создаем кастомный Writable стрим, который и передаем в метод pipe в коллбэке onShellReady (когда готова первая часть HTML но все отложенные Suspense компоненты вернули fallback)
- Этот стрим на каждый write от реакта добавляет задачу в taskManager с записью HTML в поток
- Также добавляем еще одну задачу в taskManager которая зарезолвится после события finish у нашего Writable стрима (когда все отложенные Suspense компоненты зарезолвлены и реакт выполнил всю работу)

Пример кода без лишних подробностей:

// этот стрим откладывает запись разметки в стрим ответа
class HtmlWritable extends Writable {
_write(chunk, encoding, callback) {
// stream - это именно поток ответа клиенту с предыдущего шага
taskManager.push(() => {
stream.push(chunk)
})

callback()
}
}

// реализацию Deferred добавлю позже
const allReadyDeferred = Deferred();

// задача на ожидание окончания рендера
taskManager.push(() => {
return allReadyDeferred.promise;
})

// событие сработает когда pipe завершит работу
htmlWritable.on('finish', () => {
allReadyDeferred.resolve()
})

const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
// pipe готов передавать данные
pipe(htmlWritable)
}
})

BY SuperOleg dev notes


Share with your friend now:
tgoop.com/super_oleg_dev/129

View MORE
Open in Telegram


Telegram News

Date: |

The group also hosted discussions on committing arson, Judge Hui said, including setting roadblocks on fire, hurling petrol bombs at police stations and teaching people to make such weapons. The conversation linked to arson went on for two to three months, Hui said. To edit your name or bio, click the Menu icon and select “Manage Channel.” When choosing the right name for your Telegram channel, use the language of your target audience. The name must sum up the essence of your channel in 1-3 words. If you’re planning to expand your Telegram audience, it makes sense to incorporate keywords into your name. Commenting about the court's concerns about the spread of false information related to the elections, Minister Fachin noted Brazil is "facing circumstances that could put Brazil's democracy at risk." During the meeting, the information technology secretary at the TSE, Julio Valente, put forward a list of requests the court believes will disinformation. Write your hashtags in the language of your target audience.
from us


Telegram SuperOleg dev notes
FROM American