Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(request-state): remove data race on early state load #179

Merged
merged 6 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/neat-panthers-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@inox-tools/request-state': minor
---

Fixes flash of page with unsynchronized state due to response streaming.
4 changes: 4 additions & 0 deletions docs/src/content/docs/request-nanostores.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ $comments.set(await loadCommentsForArticle());
<Comments client:load />
```

## Caveats

Enabling Request-Scoped Nanostores disables [response streaming](https://docs.astro.build/en/recipes/streaming-improve-page-performance/). This behavior caused by [request-state](/request-state) package that prevents race condition where nanostores not initialized before client components starts hydrating.

## License

Request Nanostores is available under the MIT license.
4 changes: 4 additions & 0 deletions docs/src/content/docs/request-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ document.addEventListener('@it-astro:server-state-loaded', (event) => {
});
```

## Caveats

Enabling Request State disables [response streaming](https://docs.astro.build/en/recipes/streaming-improve-page-performance/). The entire application state must be generated before shipping closing head tag to the client. This behavior prevents data race when client components start hydrating before entire document is loaded.

## License

Astro Request State is available under the MIT license.
43 changes: 16 additions & 27 deletions packages/request-state/src/runtime/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,20 @@ export const onRequest = defineMiddleware(async (_, next) => {

if (mediaType !== 'text/html' && !mediaType.startsWith('text/html+')) return result;

const newBody = result.body
?.pipeThrough(new TextDecoderStream())
.pipeThrough(injectState(getState))
.pipeThrough(new TextEncoderStream());

return new Response(newBody, result);
const originalBody = await result.text();

const headCloseIndex = originalBody.indexOf('</head>');
if (headCloseIndex > -1) {
const state = getState();
if (state) {
const stateScript = `<script id="it-astro-state" type="application/json+devalue">${state}</script>`;

return new Response(
originalBody.slice(0, headCloseIndex) + stateScript + originalBody.slice(headCloseIndex),
result
);
}
}

return new Response(originalBody, result);
});

function injectState(getState: () => string | false) {
let injected = false;
return new TransformStream({
transform(chunk, controller) {
if (!injected) {
const bodyCloseIndex = chunk.indexOf('</body>');
if (bodyCloseIndex > -1) {
const state = getState();
if (state) {
const stateScript = `<script id="it-astro-state" type="application/json+devalue">${state}</script>`;

chunk = chunk.slice(0, bodyCloseIndex) + stateScript + chunk.slice(bodyCloseIndex);
}
injected = true;
}
}
controller.enqueue(chunk);
},
});
}
Loading