Skip to content

Commit

Permalink
[ssr] Add global mode demo (#1881)
Browse files Browse the repository at this point in the history
* Move ssr vm-modules demo into its own dir
* Fix some typos in ssr demo
* Add global ssr demo
  • Loading branch information
aomarks committed May 12, 2021
1 parent 576cde1 commit a83f616
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 8 deletions.
3 changes: 2 additions & 1 deletion packages/labs/ssr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"start": "node --experimental-vm-modules ./demo/server.js",
"demo:vm-modules": "node --experimental-vm-modules ./demo/vm-modules/server.js",
"demo:global": "node ./demo/global/server.js",
"test": "npm run test:unit && npm run test:integration",
"test:integration": "npm run test:integration:dev && npm run test:integration:prod",
"test:integration:dev": "MODE=dev NODE_OPTIONS=--experimental-vm-modules wtr",
Expand Down
File renamed without changes.
52 changes: 52 additions & 0 deletions packages/labs/ssr/src/demo/global/app-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/

/**
* This is a server-only module that renders the HTML file shell.
*/

import {render} from '../../lib/render-with-global-dom-shim.js';
import {template, initialData} from './module.js';

export function renderAppWithInitialData() {
return renderApp(initialData);
}

// This module runs in the app context with the client-side code, but is a
// server-only module. It doesn't use lit-html so that it can render the HTML
// shell in unbalanced fragments. By yielding the HTML preamable immediately
// with no lit-html template preparation or rendering needed, we minimize TTFB,
// And can get the browser to start prefetch as soon as possible.
export function* renderApp(data: typeof initialData) {
yield `
<!doctype html>
<html>
<head>
<!-- This little script loads the client script on a button click. This
lets us see that only the HTML loads for first render -->
<script type="module">
const button = document.querySelector('button');
button.addEventListener('click', () => {
import('/demo/global/app-client.js');
});
</script>
<script src="./node_modules/@webcomponents/template-shadowroot/template-shadowroot.min.js"></script>
`;
yield `
</head>
<body>
<button>Hydrate</button>
<div>`;

// Call the SSR render() function to render a client/server shared template.
yield* render(template(data));

yield `
</div>
<script>TemplateShadowRoot.hydrateShadowRoots(document.body);</script>
</body>
</html>`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import {html} from 'lit';
import {LitElement, css} from 'lit';
import {property} from 'lit/decorators/property.js';
//import {repeat} from 'lit/directives/repeat.js';

export const initialData = {
name: 'SSR',
Expand Down
40 changes: 40 additions & 0 deletions packages/labs/ssr/src/demo/global/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/

import Koa from 'koa';
import staticFiles from 'koa-static';
import koaNodeResolve from 'koa-node-resolve';
import {URL} from 'url';
import * as path from 'path';
import {Readable} from 'stream';
import {renderAppWithInitialData} from './app-server.js';

const {nodeResolve} = koaNodeResolve;

const moduleUrl = new URL(import.meta.url);
const packageRoot = path.resolve(moduleUrl.pathname, '../../..');

const port = 8080;

// This is a fairly standard Koa server that represents how the SSR API might
// be used.
const app = new Koa();
app.use(async (ctx: Koa.Context, next: Function) => {
// Pass through anything not the root path to static file serving
if (ctx.URL.pathname !== '/') {
await next();
return;
}

const ssrResult = renderAppWithInitialData();
ctx.type = 'text/html';
ctx.body = Readable.from(ssrResult);
});
app.use(nodeResolve({}));
app.use(staticFiles(packageRoot));
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
37 changes: 37 additions & 0 deletions packages/labs/ssr/src/demo/vm-modules/app-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/

/**
* This is a client-only file used to boot the page.
*/

import 'lit/experimental-hydrate-support.js';
import {render} from 'lit';
import {hydrate} from 'lit/experimental-hydrate.js';
import {template, initialData} from './module.js';

console.log('Page hydrating with same data as rendered with SSR.');
// The hydrate() function is run with the same data as used in the server
// render. It doesn't update the DOM, and just updates the lit-html part values
// so that future renders() will do the minimal DOM updates.
hydrate(template(initialData), window.document.body);

window.setTimeout(() => {
console.log('Page updating with new data...');
// The first call to render() can use new data, and will only update the DOM
// where this data differs from that passed to hydrate().
render(
template({
name: 'Hydration',
message: 'We have now been hydrated and updated with new data.',
items: ['hy', 'dra', 'ted'],
prop: 'prop-updated',
attr: 'attr-updated',
wasUpdated: true,
}),
window.document.body
);
}, 0);
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
*/

/**
* This is a server-old module that renders the HTML file shell.
* This is a server-only module that renders the HTML file shell.
*/

import {render} from '../lib/render-lit-html.js';
import {render} from '../../lib/render-lit-html.js';
import {template, initialData} from './module.js';

export function renderAppWithInitialData() {
Expand All @@ -17,7 +17,7 @@ export function renderAppWithInitialData() {

// This module runs in the app context with the client-side code, but is a
// server-only module. It doesn't use lit-html so that it can render the HTML
// shell in unbalanced fragments. By yielding the HTML pramable immediately
// shell in unbalanced fragments. By yielding the HTML preamable immediately
// with no lit-html template preparation or rendering needed, we minimize TTFB,
// And can get the browser to start prefetch as soon as possible.
export function* renderApp(data: typeof initialData) {
Expand All @@ -30,7 +30,7 @@ export function* renderApp(data: typeof initialData) {
<script type="module">
const button = document.querySelector('button');
button.addEventListener('click', () => {
import('/demo/app-client.js');
import('/demo/vm-modules/app-client.js');
});
</script>
<script src="./node_modules/@webcomponents/template-shadowroot/template-shadowroot.min.js"></script>
Expand Down
89 changes: 89 additions & 0 deletions packages/labs/ssr/src/demo/vm-modules/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/

/**
* This is a shared client/server module.
*/

import {html} from 'lit';
import {LitElement, css} from 'lit';
import {property} from 'lit/decorators/property.js';

export const initialData = {
name: 'SSR',
message: 'This is a test.',
items: ['foo', 'bar', 'qux'],
prop: 'prop-value',
attr: 'attr-value',
wasUpdated: false,
};

export class MyElement extends LitElement {
static styles = css`
:host {
display: inline-block;
border: 1px dashed gray;
margin: 4px;
padding: 4px;
}
:host > * {
padding: 4px;
}
header {
font-weight: bold;
}
:host([wasUpdated]) {
background: lightgreen;
}
`;

@property({type: String})
prop = 'initial-prop';
@property({type: String})
attr = 'initial-attr';
@property({type: Boolean, reflect: true})
wasUpdated = false;

render() {
return html`
<header>I'm a my-element!</header>
<div><i>this.prop</i>: ${this.prop}</div>
<div><i>this.attr</i>: ${this.attr}</div>
`;
}
}
customElements.define('my-element', MyElement);

export const header = (name: string) => html` <h1>Hello ${name}!</h1> `;

export const template = (data: {
name: string;
message: string;
items: Array<string>;
prop: string;
attr: string;
wasUpdated: boolean;
}) =>
html`
${header(data.name)}
<p>${data.message}</p>
<h4>repeating:</h4>
<div>${data.items.map((item, i) => html` <p>${i}) ${item}</p> `)}</div>
${Array(3)
.fill(1)
.map(
(_item, i) => html`
<my-element
?wasUpdated=${data.wasUpdated}
.prop=${`${data.prop}: ${i}`}
attr=${`${data.attr}: ${i}`}
></my-element>
`
)}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import koaNodeResolve from 'koa-node-resolve';
import {URL} from 'url';
import * as path from 'path';

import {renderModule} from '../lib/render-module.js';
import {renderModule} from '../../lib/render-module.js';
import {Readable} from 'stream';

const {nodeResolve} = koaNodeResolve;

const moduleUrl = new URL(import.meta.url);
const packageRoot = path.resolve(moduleUrl.pathname, '../..');
const packageRoot = path.resolve(moduleUrl.pathname, '../../..');

const port = 8080;

Expand Down

0 comments on commit a83f616

Please sign in to comment.