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

docs: interactive resumability Demo component to place on homepage #4990

Merged
merged 3 commits into from
Sep 11, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const Portal = component$<{ name: string }>(({ name }) => {
return (
<>
{myPortals.map((portal) => (
<div class="modal">
<div data-portal={name}>
<WrapJsxInContext jsx={portal.jsx} contexts={portal.contexts} />
</div>
))}
Expand Down
83 changes: 83 additions & 0 deletions packages/docs/src/routes/demo/resumability/component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.group {
display: inline-flex;
flex-direction: row;
align-items: center;
margin: 0;
padding: 0;
}

.thread {
border: 1px dashed #ccc;
margin-bottom: 5px;
border-radius: 5px;
}

.box,
.legend-box {
margin: 5px;
}

.html {
width: 100px;
background-color: rgb(183, 218, 244);
}

.js,
.js-chunk {
width: 250px;
background-color: rgb(160, 220, 172);
}

.execution {
width: 500px;
background-color: rgb(250, 183, 184);
}

.reconciliation {
width: 250px;
background-color: rgb(219, 207, 204);
}

.js-chunk {
width: 15px;
margin: 0;
margin-right: 2px;
}

.thread-label {
float: right;
}

.legend-box {
width: 30px !important;
height: 30px !important;
}

.legend > .group {
margin-right: 30px;
}
h1.label {
font-size: larger;
margin-top: 20px;
}

.legend {
display: flex;
flex-direction: row;
align-items: center;
margin: 0;
padding: 0;
}

.box,
.legend-box {
min-width: 5px;
height: 50px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
264 changes: 264 additions & 0 deletions packages/docs/src/routes/demo/resumability/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import {
type QwikIntrinsicElements,
component$,
useStylesScoped$,
Slot,
} from '@builder.io/qwik';
import CSS from './component.css?inline';
import { type HoverEvent } from './index';

export const UnderstandingResumability = component$(() => {
useStylesScoped$(CSS);
return (
<div class="demo">
<div class="hydration">
<div class="strategy">Hydration</div>

<div class="thread">
<div class="thread-label">(main)</div>
<div class="group">
<div
class="html box"
onHover$={(e: HoverEvent) =>
e.detail(
<Callout target={e.target as HTMLElement}>
<p>
Server side rendered HTML to show application instantly
</p>
<ul>
<li>HTML from CDN (or SSG)</li>
</ul>
</Callout>
)
}
/>
<div
class="js box"
onHover$={(e: HoverEvent) =>
e.detail(
<Callout target={e.target as HTMLElement}>
<p>HTML triggers downloading of application JavaScript.</p>
<ul>
<li>
Duplication: JS contains all string which are in HTML.
</li>
<li>
Any user interactions are lost (unless some form of
event replay system exist.)
</li>
</ul>
</Callout>
)
}
/>
<div
class="execution box"
onHover$={(e: HoverEvent) =>
e.detail(
<Callout target={e.target as HTMLElement}>
<p>
The application must be executed for the framework to
collect listeners, components, and state.
</p>
<ul>
<li>
This is a recursive process which starts with root
component.
</li>
<li>
The code is executed in slow interpretive mode (no
JIT.)
</li>
</ul>
</Callout>
)
}
/>
<div
class="reconciliation box"
onHover$={(e: HoverEvent) =>
e.detail(
<Callout target={e.target as HTMLElement}>
<p>
Events are attached to make the application interactive.
</p>
</Callout>
)
}
/>
<div
onHover$={(e: HoverEvent) =>
e.detail(
<Callout target={e.target as HTMLElement}>
<p>Application can now be interacted with.</p>
</Callout>
)
}
>
<ReadyIcon />
</div>
</div>
</div>
</div>
<div class="space"></div>
<div class="resumability">
<div class="strategy">Resumability</div>
<div class="thread">
<div class="thread-label">(main)</div>
<div class="group">
<div
class="html box"
onHover$={(e: HoverEvent) =>
e.detail(
<Callout target={e.target as HTMLElement}>
<p>
Server side rendered HTML to show application instantly
</p>
<ul>
<li>HTML from CDN (or SSG)</li>
<li>Contains QwikLoader global listener (1kb / ~1ms)</li>
</ul>
</Callout>
)
}
></div>
<div
onHover$={(e: HoverEvent) =>
e.detail(
<Callout target={e.target as HTMLElement}>
<p>Application can now be interacted with.</p>
<ul>
<li>Notice that JS is downloaded in parallel.</li>
<li>
In an unlikely event that user interacts before JS is
downloaded there may be small delay. (But always less
than hydration cost.)
</li>
</ul>
</Callout>
)
}
>
<ReadyIcon />
</div>
</div>
</div>
<div class="thread">
<div class="thread-label">(worker)</div>
<div
class="group"
style={{ 'margin-left': '120px' }}
onHover$={(e: HoverEvent) =>
e.detail(
<Callout target={e.target as HTMLElement}>
<p>JavaScript downloaded in parallel.</p>
<ul>
<li>
JS is eagerly downloaded in service worker off the main
thread into browser cache. Once downloaded the
application interactivity does not depend on network.
</li>
<li>
JS is not brought to main thread until user interaction.
This keeps main thread free for other tasks.
</li>
<li>
JS is split into many smaller chunks, this allows the
service worker to prioritize the order of chunk download
in case user interacts before all JS is downloaded.
</li>
<li>
Related code is automatically grouped into chunks so that
each chunk only has what is needed to process user
interaction.
</li>
</ul>
</Callout>
)
}
>
<div class="js-chunk box"></div>
<div class="js-chunk box"></div>
<div class="js-chunk box"></div>
<div class="js-chunk box"></div>
<div class="js-chunk box"></div>
</div>
</div>
</div>
<h1 class="label">Legend</h1>
<div class="legend">
<div class="group">
<div class="html legend-box" />
<div class="label">HTML</div>
</div>
<div class="group">
<div class="js legend-box" />
<div class="label">JS download</div>
</div>
<div class="group">
<div class="execution legend-box" />
<div class="label">JS execution</div>
</div>
<div class="group">
<div class="reconciliation legend-box" />
<div class="label">DOM Listeners</div>
</div>
<div class="group">
<div class="legend-box">
<ReadyIcon />
</div>
<div class="label">Ready</div>
</div>
</div>
</div>
);
});

export function ReadyIcon(props: QwikIntrinsicElements['svg'], key: string) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="3em"
height="3em"
viewBox="0 0 48 48"
{...props}
key={key}
>
<g
fill="green"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="4"
>
<path d="M24 4v8"></path>
<path
d="m22 22l20 4l-6 4l6 6l-6 6l-6-6l-4 6l-4-20Z"
clipRule="evenodd"
></path>
<path d="m38.142 9.858l-5.657 5.657M9.858 38.142l5.657-5.657M4 24h8M9.858 9.858l5.657 5.657"></path>
</g>
</svg>
);
}

export const Callout = component$<{ target: HTMLElement }>(({ target }) => {
const rect = target.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height;
return (
<div
style={{
position: 'absolute',
top: y + 'px',
left: x + 'px',
border: '1px solid black',
backgroundColor: 'white',
color: 'black',
padding: '.5em',
}}
>
<Slot />
</div>
);
});
Loading