Skip to content

Commit

Permalink
feat: html streaming
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalsadhu committed Sep 25, 2024
1 parent df1d40e commit 80b02c6
Show file tree
Hide file tree
Showing 10 changed files with 598 additions and 3 deletions.
45 changes: 45 additions & 0 deletions example/streaming/podlets/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Podlet from '@podium/podlet';
import express from 'express';

const podlet = new Podlet({
name: 'content',
version: Date.now().toString(),
pathname: '/',
});

podlet.css({ value: 'http://localhost:6103/css' });

const app = express();

app.use(podlet.middleware());

app.get('/manifest.json', (req, res) => {
res.send(podlet);
});

app.get('/css', (req, res) => {
res.set('Content-Type', 'text/css');
res.send(`
.content {
border: 1px solid black;
border-radius: 5px;
width: 100%;
padding: 20px;
margin: 0;
margin-bottom: 20px;
box-sizing: border-box;
}
`);
});

app.get('/', (req, res) => {
res.send(`
<section class="content">
main content goes here
</section>
`);
});

app.listen(6103, () => {
console.log(`content podlet server running at http://localhost:6103`);
});
45 changes: 45 additions & 0 deletions example/streaming/podlets/footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Podlet from '@podium/podlet';
import express from 'express';

const podlet = new Podlet({
name: 'footer',
version: Date.now().toString(),
pathname: '/',
});

podlet.css({ value: 'http://localhost:6104/css' });

const app = express();

app.use(podlet.middleware());

app.get('/manifest.json', (req, res) => {
res.send(podlet);
});

app.get('/css', (req, res) => {
res.set('Content-Type', 'text/css');
res.send(`
footer {
border: 1px solid black;
border-radius: 5px;
width: 100%;
padding: 20px;
margin: 0;
margin-bottom: 20px;
box-sizing: border-box;
}
`);
});

app.get('/', (req, res) => {
res.send(`
<footer>
footer content
</footer>
`);
});

app.listen(6104, () => {
console.log(`footer podlet server running at http://localhost:6104`);
});
50 changes: 50 additions & 0 deletions example/streaming/podlets/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Podlet from '@podium/podlet';
import express from 'express';

const podlet = new Podlet({
name: 'header',
version: Date.now().toString(),
pathname: '/',
});

podlet.css({ value: 'http://localhost:6101/css' });

const app = express();

app.use(podlet.middleware());

app.get('/manifest.json', (req, res) => {
res.send(podlet);
});

app.get('/css', (req, res) => {
res.set('Content-Type', 'text/css');
res.send(`
header {
border: 1px solid black;
border-radius: 5px;
width: 100%;
padding: 20px;
margin: 0;
margin-bottom: 20px;
box-sizing: border-box;
}
header h1 {
text-align: center;
margin: 0;
padding: 0;
}
`);
});

app.get('/', (req, res) => {
res.send(`
<header>
<h1>Header</h1>
</header>
`);
});

app.listen(6101, () => {
console.log(`header podlet server running at http://localhost:6101`);
});
62 changes: 62 additions & 0 deletions example/streaming/podlets/menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Podlet from '@podium/podlet';
import express from 'express';

const podlet = new Podlet({
name: 'menu',
version: Date.now().toString(),
pathname: '/',
});

podlet.css({ value: 'http://localhost:6102/css' });

const app = express();

app.use(podlet.middleware());

app.get('/manifest.json', (req, res) => {
res.send(podlet);
});

app.get('/css', (req, res) => {
res.set('Content-Type', 'text/css');
res.send(`
menu {
border: 1px solid black;
border-radius: 5px;
width: 100%;
padding: 10px;
margin: 0;
margin-bottom: 20px;
box-sizing: border-box;
}
menu ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
justify-content: space-evenly;
align-items: center;
}
menu ul li {
margin: 0;
padding: 0;
}
`);
});

app.get('/', (req, res) => {
res.send(`
<menu>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/things">Things</a></li>
<li><a href="/stuff">Stuff</a></li>
</ul>
</menu>
`);
});

app.listen(6102, () => {
console.log(`menu podlet server running at http://localhost:6102`);
});
169 changes: 169 additions & 0 deletions example/streaming/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import express from 'express';
import Layout from '../../lib/layout.js';
import { template } from './views/template.js';

const layout = new Layout({
pathname: '/foo',
logger: console,
name: 'demo',
});

// use our custom streaming template
layout.view(template);

const content = layout.client.register({
name: 'content',
uri: 'http://localhost:6103/manifest.json',
});

const header = layout.client.register({
name: 'header',
uri: 'http://localhost:6101/manifest.json',
});

const menu = layout.client.register({
name: 'menu',
uri: 'http://localhost:6102/manifest.json',
});

const footer = layout.client.register({
name: 'footer',
uri: 'http://localhost:6104/manifest.json',
});

layout.css({ value: '/css' });

const app = express();

app.use(layout.pathname(), layout.middleware());

app.get('/foo/css', (req, res) => {
res.set('Content-Type', 'text/css');
res.send(`
@keyframes pulse {
0% {
background-color: #e0e0e0;
}
50% {
background-color: #f0f0f0;
}
100% {
background-color: #e0e0e0;
}
}
.skeleton {
width: 100%;
background-color: #e0e0e0;
border-radius: 5px;
animation: pulse 1.5s infinite ease-in-out;
margin: 0;
margin-bottom: 20px;
box-sizing: border-box;
}
.skeleton.header {
height:79px;
}
.skeleton.menu {
height:40px;
}
.skeleton.content {
height:60px;
}
.skeleton.footer {
height:60px;
}
`);
});

app.get(layout.pathname(), async (req, res) => {
const incoming = res.locals.podium;

incoming.view = {
title: 'Example streaming application',
};

const headerFetch = header.fetch(incoming);
const menuFetch = menu.fetch(incoming);
const contentFetch = content.fetch(incoming);
const footerFetch = footer.fetch(incoming);

incoming.hints.on('complete', async ({ js, css }) => {
// set the assets on httpincoming so that they are available in the document template
incoming.js = [...incoming.js, ...js];
incoming.css = [...incoming.css, ...css];

// set up the stream which will send the document template head
const stream = res.podiumStream();

// stream in the document body with slot placeholders for podlets
stream.send(`
<template shadowrootmode="open">
<link href="/foo/css" type="text/css" rel="stylesheet">
<div class="container">
<div>
<div>
<slot name="header"><div class="skeleton header"></div></slot>
</div>
</div>
<div>
<div>
<slot name="menu"><div class="skeleton menu"></div></slot>
</div>
<div>
<slot name="content"><div class="skeleton content"></div></slot>
</div>
</div>
<div>
<div>
<slot name="footer"><div class="skeleton footer"></div></slot>
</div>
</div>
</div>
</template>
`);

// fake 1 second delay
await new Promise((res) => setTimeout(res, 1000));

// stream in podlet content when available...
headerFetch.then((content) => {
stream.send(`<div slot="header">${content}</div>`);
});

await new Promise((res) => setTimeout(res, 1000));

menuFetch.then((content) => {
stream.send(`<div slot="menu">${content}</div>`);
});

await new Promise((res) => setTimeout(res, 1000));

contentFetch.then((content) => {
stream.send(`<div slot="content">${content}</div>`);
});

await new Promise((res) => setTimeout(res, 1000));

footerFetch.then((content) => {
stream.send(`<div slot="footer">${content}</div>`);
});

// close out the dom and the stream
await Promise.all([headerFetch, menuFetch, contentFetch, footerFetch]);
stream.done();
});
});

app.use(`${layout.pathname()}/assets`, express.static('assets'));

// eslint-disable-next-line no-unused-vars
app.use((error, req, res, next) => {
console.error(error);
res.status(500).send(
'<html><body><h1>Internal server error</h1></body></html>',
);
});

app.listen(6123, () => {
console.log(`layout server running at http://localhost:6123`);
});
Loading

0 comments on commit 80b02c6

Please sign in to comment.