Skip to content

Commit

Permalink
feat(demos): add state serialization to async-ssr-manager demo (#291)
Browse files Browse the repository at this point in the history
  • Loading branch information
unstubbable authored Jan 23, 2019
1 parent 938c8bc commit bdbcdb1
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 35 deletions.
1 change: 1 addition & 0 deletions packages/demos/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@feature-hub/module-loader-amd": "^0.12.0",
"@feature-hub/module-loader-commonjs": "^0.12.0",
"@feature-hub/react": "^0.12.0",
"@feature-hub/serialized-state-manager": "^0.0.0",
"@feature-hub/server-request": "^0.12.0",
"@types/express": "^4.16.0",
"@types/get-port": "^4.0.0",
Expand Down
37 changes: 35 additions & 2 deletions packages/demos/src/async-ssr-manager/feature-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,56 @@ import {Card, Label} from '@blueprintjs/core';
import {AsyncSsrManagerV0} from '@feature-hub/async-ssr-manager';
import {FeatureAppDefinition} from '@feature-hub/core';
import {ReactFeatureApp} from '@feature-hub/react';
import {SerializedStateManagerV0} from '@feature-hub/serialized-state-manager';
import * as React from 'react';

interface Dependencies {
's2:async-ssr-manager': AsyncSsrManagerV0 | undefined;
's2:serialized-state-manager': SerializedStateManagerV0;
}

async function fetchSubject(): Promise<string> {
return 'Universe';
}

const featureAppDefinition: FeatureAppDefinition<
ReactFeatureApp,
undefined,
{'s2:async-ssr-manager': AsyncSsrManagerV0}
Dependencies
> = {
id: 'test:hello-world',

dependencies: {
's2:serialized-state-manager': '^0.1'
},

optionalDependencies: {
's2:async-ssr-manager': '^0.1'
},

create: env => {
let subject = 'World';

const asyncSsrManager = env.featureServices['s2:async-ssr-manager'];
asyncSsrManager.rerenderAfter((async () => (subject = 'Universe'))());

const serializedStateManager =
env.featureServices['s2:serialized-state-manager'];

// We use the presence of the asyncSsrManager to determine whether we are
// rendered on the server or on the client.
if (asyncSsrManager) {
serializedStateManager.register(() => subject);

asyncSsrManager.rerenderAfter(
(async () => (subject = await fetchSubject()))()
);
} else {
const serializedSubject = serializedStateManager.getSerializedState();

if (serializedSubject) {
subject = serializedSubject;
}
}

return {
render: () => (
Expand Down
21 changes: 18 additions & 3 deletions packages/demos/src/async-ssr-manager/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Server} from 'http';
import {AddressInfo} from 'net';
import {Browser} from '../browser';
import {startServer} from '../start-server';
import renderMainHtml from './integrator.node';
import renderApp from './integrator.node';
import webpackConfigs from './webpack-config';

jest.setTimeout(60000);
Expand All @@ -15,18 +15,33 @@ describe('integration test: "Async SSR Manager"', () => {
const browser = new Browser(5000);

let server: Server;
let url: string;

beforeAll(async () => {
server = await startServer(webpackConfigs, renderMainHtml);
server = await startServer(webpackConfigs, renderApp);

const {port} = server.address() as AddressInfo;

await browser.goto(`http://localhost:${port}`, 60000);
url = `http://localhost:${port}/`;

await browser.goto(url, 60000);
});

afterAll(done => server.close(done));

it('loads the server-side rendered Feature App HTML', async () => {
// We need to disable JavaScript for this test to ensure that the server-rendered HTML is observed.
await page.setJavaScriptEnabled(false);
await browser.goto(url);

await expect(page).toMatch('Hello, Universe!');

// Re-enable JavaScript to restore the default behavior for all other tests.
await page.setJavaScriptEnabled(true);
await browser.goto(url);
});

it('hydrates the server-side rendered Feature App HTML', async () => {
await expect(page).toMatch('Hello, Universe!');
});
});
25 changes: 19 additions & 6 deletions packages/demos/src/async-ssr-manager/integrator.node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,29 @@ import {
import {FeatureAppManager, FeatureServiceRegistry} from '@feature-hub/core';
import {loadCommonJsModule} from '@feature-hub/module-loader-commonjs';
import {FeatureAppLoader} from '@feature-hub/react';
import {
SerializedStateManagerV0,
serializedStateManagerDefinition
} from '@feature-hub/serialized-state-manager';
import * as React from 'react';
import * as ReactDOM from 'react-dom/server';
import {MainHtmlRendererOptions} from '../start-server';
import {AppRendererOptions, AppRendererResult} from '../start-server';

export default async function renderMainHtml({
export default async function renderApp({
port
}: MainHtmlRendererOptions): Promise<string> {
}: AppRendererOptions): Promise<AppRendererResult> {
const integratorDefinition = {
id: 'test:integrator',
dependencies: {
[asyncSsrManagerDefinition.id]: '^0.1'
[asyncSsrManagerDefinition.id]: '^0.1',
[serializedStateManagerDefinition.id]: '^0.1'
}
};

const featureServiceRegistry = new FeatureServiceRegistry();

featureServiceRegistry.registerFeatureServices(
[asyncSsrManagerDefinition],
[asyncSsrManagerDefinition, serializedStateManagerDefinition],
integratorDefinition.id
);

Expand All @@ -38,7 +43,7 @@ export default async function renderMainHtml({
moduleLoader: loadCommonJsModule
});

return asyncSsrManager.renderUntilCompleted(() =>
const html = await asyncSsrManager.renderUntilCompleted(() =>
ReactDOM.renderToString(
<FeatureAppLoader
asyncSsrManager={asyncSsrManager}
Expand All @@ -48,4 +53,12 @@ export default async function renderMainHtml({
/>
)
);

const serializedStateManager = featureServices[
serializedStateManagerDefinition.id
] as SerializedStateManagerV0;

const serializedStates = serializedStateManager.serializeStates();

return {html, serializedStates};
}
1 change: 0 additions & 1 deletion packages/demos/src/async-ssr-manager/integrator.ts

This file was deleted.

66 changes: 66 additions & 0 deletions packages/demos/src/async-ssr-manager/integrator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {FeatureAppManager, FeatureServiceRegistry} from '@feature-hub/core';
import {defineExternals, loadAmdModule} from '@feature-hub/module-loader-amd';
import {FeatureAppLoader} from '@feature-hub/react';
import {
SerializedStateManagerV0,
serializedStateManagerDefinition
} from '@feature-hub/serialized-state-manager';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import '../blueprint-css';

const featureAppUrl = 'feature-app.umd.js';

function getSerializedStatesFromDom(): string | undefined {
const scriptElement = document.querySelector(
'script[type="x-feature-hub/serialized-states"]'
);

return (scriptElement && scriptElement.textContent) || undefined;
}

(async () => {
const integratorDefinition = {
id: 'test:integrator',
dependencies: {
[serializedStateManagerDefinition.id]: '^0.1'
}
};

const featureServiceRegistry = new FeatureServiceRegistry();

featureServiceRegistry.registerFeatureServices(
[serializedStateManagerDefinition],
integratorDefinition.id
);

const {featureServices} = featureServiceRegistry.bindFeatureServices(
integratorDefinition
);

const featureAppManager = new FeatureAppManager(featureServiceRegistry, {
moduleLoader: loadAmdModule
});

defineExternals({react: React});

await featureAppManager.preloadFeatureApp(featureAppUrl);

const serializedStateManager = featureServices[
serializedStateManagerDefinition.id
] as SerializedStateManagerV0;

const serializedStates = getSerializedStatesFromDom();

if (serializedStates) {
serializedStateManager.setSerializedStates(serializedStates);
}

ReactDOM.hydrate(
<FeatureAppLoader
featureAppManager={featureAppManager}
src={featureAppUrl}
/>,
document.querySelector('main')
);
})().catch(console.error);
14 changes: 13 additions & 1 deletion packages/demos/src/async-ssr-manager/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ import {Configuration} from 'webpack';
import {webpackBaseConfig} from '../webpack-base-config';

export default [
{
...webpackBaseConfig,
entry: join(__dirname, './feature-app.tsx'),
externals: {
react: 'react'
},
output: {
filename: 'feature-app.umd.js',
libraryTarget: 'umd',
publicPath: '/'
}
},
{
...webpackBaseConfig,
entry: join(__dirname, './feature-app.tsx'),
Expand All @@ -15,7 +27,7 @@ export default [
},
{
...webpackBaseConfig,
entry: join(__dirname, './integrator.ts'),
entry: join(__dirname, './integrator.tsx'),
output: {
filename: 'integrator.js',
publicPath: '/'
Expand Down
4 changes: 2 additions & 2 deletions packages/demos/src/history-service/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {AddressInfo} from 'net';
import {ElementHandle} from 'puppeteer';
import {Browser} from '../browser';
import {startServer} from '../start-server';
import renderMainHtml from './integrator.node';
import renderApp from './integrator.node';
import webpackConfigs from './webpack-config';

jest.setTimeout(60000);
Expand Down Expand Up @@ -72,7 +72,7 @@ describe('integration test: "history-service"', () => {
let url: string;

beforeAll(async () => {
server = await startServer(webpackConfigs, renderMainHtml);
server = await startServer(webpackConfigs, renderApp);

const {port} = server.address() as AddressInfo;

Expand Down
12 changes: 8 additions & 4 deletions packages/demos/src/history-service/integrator.node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import {defineHistoryService} from '@feature-hub/history-service';
import {defineServerRequest} from '@feature-hub/server-request';
import * as React from 'react';
import * as ReactDOM from 'react-dom/server';
import {MainHtmlRendererOptions} from '../start-server';
import {AppRendererOptions, AppRendererResult} from '../start-server';
import {App} from './app';
import {rootLocationTransformer} from './root-location-transformer';

export default async function renderMainHtml({
export default async function renderApp({
req
}: MainHtmlRendererOptions): Promise<string> {
}: AppRendererOptions): Promise<AppRendererResult> {
const featureServiceRegistry = new FeatureServiceRegistry();

const featureServiceDefinitions = [
Expand All @@ -24,5 +24,9 @@ export default async function renderMainHtml({

const featureAppManager = new FeatureAppManager(featureServiceRegistry);

return ReactDOM.renderToString(<App featureAppManager={featureAppManager} />);
const html = ReactDOM.renderToString(
<App featureAppManager={featureAppManager} />
);

return {html};
}
4 changes: 2 additions & 2 deletions packages/demos/src/module-loader-commonjs/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Server} from 'http';
import {AddressInfo} from 'net';
import {Browser} from '../browser';
import {startServer} from '../start-server';
import renderMainHtml from './integrator.node';
import renderApp from './integrator.node';
import webpackConfigs from './webpack-config';

jest.setTimeout(60000);
Expand All @@ -17,7 +17,7 @@ describe('integration test: "commonjs module loader"', () => {
let server: Server;

beforeAll(async () => {
server = await startServer(webpackConfigs, renderMainHtml);
server = await startServer(webpackConfigs, renderApp);

const {port} = server.address() as AddressInfo;

Expand Down
10 changes: 6 additions & 4 deletions packages/demos/src/module-loader-commonjs/integrator.node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import {loadCommonJsModule} from '@feature-hub/module-loader-commonjs';
import {FeatureAppLoader} from '@feature-hub/react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/server';
import {MainHtmlRendererOptions} from '../start-server';
import {AppRendererOptions, AppRendererResult} from '../start-server';

export default async function renderMainHtml({
export default async function renderApp({
port
}: MainHtmlRendererOptions): Promise<string> {
}: AppRendererOptions): Promise<AppRendererResult> {
const featureAppNodeUrl = `http://localhost:${port}/feature-app.commonjs.js`;
const featureServiceRegistry = new FeatureServiceRegistry();

Expand All @@ -17,11 +17,13 @@ export default async function renderMainHtml({

await featureAppManager.preloadFeatureApp(featureAppNodeUrl);

return ReactDOM.renderToString(
const html = ReactDOM.renderToString(
<FeatureAppLoader
featureAppManager={featureAppManager}
src=""
serverSrc={featureAppNodeUrl}
/>
);

return {html};
}
Loading

0 comments on commit bdbcdb1

Please sign in to comment.