rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
419 lines (418 loc) • 19.1 kB
JavaScript
import { describe, expect, it } from "vitest";
import { stitchDocumentAndAppStreams } from "./stitchDocumentAndAppStreams.js";
function stringToStream(str) {
const encoder = new TextEncoder();
return new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(str));
controller.close();
},
});
}
function streamToString(stream) {
const decoder = new TextDecoder();
const reader = stream.getReader();
let result = "";
return new Promise((resolve, reject) => {
function pump() {
return reader
.read()
.then(({ done, value }) => {
if (done) {
resolve(result);
return;
}
result += decoder.decode(value, { stream: true });
return pump();
})
.catch(reject);
}
return pump();
});
}
function createChunkedStream(chunks) {
const encoder = new TextEncoder();
return new ReadableStream({
start(controller) {
for (let i = 0; i < chunks.length; i++) {
controller.enqueue(encoder.encode(chunks[i]));
}
controller.close();
},
});
}
describe("stitchDocumentAndAppStreams", () => {
const startMarker = '<div id="rwsdk-app-start" />';
const endMarker = '<div id="rwsdk-app-end"></div>';
describe("meta tag hoisting", () => {
it("places hoisted tags inside head, after existing head content", async () => {
const outerHtml = `
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<title>Page Title</title><div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(`<title>Page Title</title>`);
expect(result).toMatch(/<head>[\s\S]*<meta charset="utf-8" \/>[\s\S]*<title>Page Title<\/title>[\s\S]*<\/head>/);
expect(result).toContain(`<div>App content</div>`);
const doctypeIndex = result.indexOf(``);
const headIndex = result.indexOf(`<head>`);
const charsetIndex = result.indexOf(`<meta charset="utf-8" />`);
const titleIndex = result.indexOf(`<title>Page Title</title>`);
const headCloseIndex = result.indexOf(`</head>`);
expect(doctypeIndex).toBe(0);
expect(doctypeIndex).toBeLessThan(headIndex);
expect(charsetIndex).toBeLessThan(titleIndex);
expect(titleIndex).toBeLessThan(headCloseIndex);
});
it("places multiple hoisted tags inside head, after existing head content", async () => {
const outerHtml = `
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<title>Page Title</title><meta name="description" content="Test" /><link rel="stylesheet" href="/styles.css" /><div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(`<title>Page Title</title>`);
expect(result).toContain(`<meta name="description" content="Test" />`);
expect(result).toContain(`<link rel="stylesheet" href="/styles.css" />`);
expect(result).toMatch(/<head>[\s\S]*<meta charset="utf-8" \/>[\s\S]*<title>Page Title<\/title>[\s\S]*<meta name="description" content="Test" \/>[\s\S]*<link rel="stylesheet" href="\/styles.css" \/>[\s\S]*<\/head>/);
const doctypeIndex = result.indexOf(``);
const charsetIndex = result.indexOf(`<meta charset="utf-8" />`);
const titleIndex = result.indexOf(`<title>Page Title</title>`);
const headCloseIndex = result.indexOf(`</head>`);
expect(doctypeIndex).toBe(0);
expect(charsetIndex).toBeLessThan(titleIndex);
expect(titleIndex).toBeLessThan(headCloseIndex);
});
it("handles app stream with no hoisted tags", async () => {
const outerHtml = `
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(`<div>App content</div>`);
expect(result).not.toContain(`<title>`);
});
it("handles hoisted tags split across chunks", async () => {
const outerHtml = `
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtmlChunks = [
`<title>Page `,
`Title</title><meta name="description" `,
`content="Test" /><div>App content</div>${endMarker}`,
];
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), createChunkedStream(innerHtmlChunks), startMarker, endMarker));
expect(result).toContain(`<title>Page Title</title>`);
expect(result).toContain(`<meta name="description" content="Test" />`);
expect(result).toMatch(/<head>[\s\S]*<meta charset="utf-8" \/>[\s\S]*<title>Page Title<\/title>[\s\S]*<\/head>/);
const doctypeIndex = result.indexOf(``);
const charsetIndex = result.indexOf(`<meta charset="utf-8" />`);
const titleIndex = result.indexOf(`<title>Page Title</title>`);
const headCloseIndex = result.indexOf(`</head>`);
expect(doctypeIndex).toBe(0);
expect(charsetIndex).toBeLessThan(titleIndex);
expect(titleIndex).toBeLessThan(headCloseIndex);
});
it("ensures doctype is always first", async () => {
const outerHtml = `
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<title>Page Title</title><div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result.trim().startsWith(``)).toBe(true);
});
});
describe("basic stitching flow", () => {
it("stitches document head, app shell, and document tail", async () => {
const outerHtml = `
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(``);
expect(result).toContain(`<head>`);
expect(result).toContain(`<meta charset="utf-8" />`);
expect(result).toContain(`<div>App content</div>`);
expect(result).toContain(`<script src="/client.js"></script>`);
expect(result).toContain(`</body>`);
expect(result).toContain(`</html>`);
const doctypeIndex = result.indexOf(``);
const appContentIndex = result.indexOf(`<div>App content</div>`);
const scriptIndex = result.indexOf(`<script src="/client.js"></script>`);
const bodyCloseIndex = result.indexOf(`</body>`);
expect(doctypeIndex).toBeLessThan(appContentIndex);
expect(appContentIndex).toBeLessThan(scriptIndex);
expect(scriptIndex).toBeLessThan(bodyCloseIndex);
});
it("removes start marker from output", async () => {
const outerHtml = `
<html>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).not.toContain(startMarker);
});
it("preserves end marker in output", async () => {
const outerHtml = `
<html>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(endMarker);
});
});
describe("suspense boundaries (suspended content)", () => {
it("streams suspended content after script tag", async () => {
const outerHtml = `
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div>Initial content</div>${endMarker}<div>Suspended content</div>`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(`<div>Initial content</div>`);
expect(result).toContain(`<script src="/client.js"></script>`);
expect(result).toContain(`<div>Suspended content</div>`);
const initialIndex = result.indexOf(`<div>Initial content</div>`);
const scriptIndex = result.indexOf(`<script src="/client.js"></script>`);
const suspendedIndex = result.indexOf(`<div>Suspended content</div>`);
const bodyCloseIndex = result.indexOf(`</body>`);
expect(initialIndex).toBeLessThan(scriptIndex);
expect(scriptIndex).toBeLessThan(suspendedIndex);
expect(suspendedIndex).toBeLessThan(bodyCloseIndex);
});
it("handles multiple suspended content chunks", async () => {
const outerHtml = `
<html>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div>Initial</div>${endMarker}<div>Suspended 1</div><div>Suspended 2</div>`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(`<div>Initial</div>`);
expect(result).toContain(`<div>Suspended 1</div>`);
expect(result).toContain(`<div>Suspended 2</div>`);
const scriptIndex = result.indexOf(`<script src="/client.js"></script>`);
const suspended1Index = result.indexOf(`<div>Suspended 1</div>`);
const suspended2Index = result.indexOf(`<div>Suspended 2</div>`);
expect(scriptIndex).toBeLessThan(suspended1Index);
expect(suspended1Index).toBeLessThan(suspended2Index);
});
it("handles app stream with no suspended content", async () => {
const outerHtml = `
<html>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div>Initial content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(`<div>Initial content</div>`);
expect(result).toContain(`<script src="/client.js"></script>`);
expect(result).not.toContain(`Suspended`);
});
});
describe("chunked streams", () => {
it("handles document stream split across chunks", async () => {
const outerHtmlChunks = [
`\n<html>\n<head>\n`,
` <meta charset="utf-8" />\n</head>\n<body>\n`,
` ${startMarker}\n <script src="/client.js"></script>\n</body>\n</html>`,
];
const innerHtml = `<div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(createChunkedStream(outerHtmlChunks), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(``);
expect(result).toContain(`<div>App content</div>`);
expect(result).toContain(`<script src="/client.js"></script>`);
});
it("handles app stream split across chunks", async () => {
const outerHtml = `
<html>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtmlChunks = [
`<div>Initial `,
`content</div>${endMarker}`,
`<div>Suspended content</div>`,
];
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), createChunkedStream(innerHtmlChunks), startMarker, endMarker));
expect(result).toContain(`<div>Initial content</div>`);
expect(result).toContain(`<div>Suspended content</div>`);
});
it("handles markers split across chunks", async () => {
const outerHtml = `
<html>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtmlChunks = [
`<div>App content</div><div id="rwsdk-app-end`,
`"></div>`,
];
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), createChunkedStream(innerHtmlChunks), startMarker, endMarker));
expect(result).toContain(`<div>App content</div>`);
expect(result).toContain(endMarker);
});
});
describe("edge cases", () => {
it("handles empty app stream", async () => {
const outerHtml = `
<html>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = ``;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(``);
expect(result).toContain(`<script src="/client.js"></script>`);
expect(result).toContain(`</body>`);
});
it("handles empty outer stream", async () => {
const outerHtml = ``;
const innerHtml = `<div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toBe(`<div>App content</div>${endMarker}`);
});
it("handles app stream ending before end marker", async () => {
const outerHtml = `
<html>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div>App content</div>`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(`<div>App content</div>`);
expect(result).toContain(`<script src="/client.js"></script>`);
});
it("handles outer stream ending before start marker", async () => {
const outerHtml = `
<html>
<head>
<meta charset="utf-8" />
</head>`;
const innerHtml = `<div>App content</div>${endMarker}`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(``);
expect(result).toContain(`<meta charset="utf-8" />`);
expect(result).toContain(`<div>App content</div>`);
});
});
describe("complete flow verification", () => {
it("correctly orders all phases: head -> shell -> script -> suspended -> close", async () => {
const outerHtml = `
<html>
<head>
<meta charset="utf-8" />
<title>Test Page</title>
</head>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div>Initial content</div>${endMarker}<div>Suspended content</div>`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
const doctypeIndex = result.indexOf(``);
const headIndex = result.indexOf(`<head>`);
const titleIndex = result.indexOf(`<title>Test Page</title>`);
const initialIndex = result.indexOf(`<div>Initial content</div>`);
const scriptIndex = result.indexOf(`<script src="/client.js"></script>`);
const suspendedIndex = result.indexOf(`<div>Suspended content</div>`);
const bodyCloseIndex = result.indexOf(`</body>`);
const htmlCloseIndex = result.indexOf(`</html>`);
expect(doctypeIndex).toBeLessThan(headIndex);
expect(headIndex).toBeLessThan(titleIndex);
expect(titleIndex).toBeLessThan(initialIndex);
expect(initialIndex).toBeLessThan(scriptIndex);
expect(scriptIndex).toBeLessThan(suspendedIndex);
expect(suspendedIndex).toBeLessThan(bodyCloseIndex);
expect(bodyCloseIndex).toBeLessThan(htmlCloseIndex);
});
it("preserves content structure and markers", async () => {
const outerHtml = `
<html>
<body>
${startMarker}
<script src="/client.js"></script>
</body>
</html>`;
const innerHtml = `<div id="app">
<h1>Hello</h1>
<p>World</p>
</div>${endMarker}<div>More content</div>`;
const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
expect(result).toContain(`<div id="app">`);
expect(result).toContain(`<h1>Hello</h1>`);
expect(result).toContain(`<p>World</p>`);
expect(result).toContain(endMarker);
expect(result).toContain(`<div>More content</div>`);
expect(result).not.toContain(startMarker);
});
});
});