Skip to content

Commit

Permalink
feat(async-ssr-manager): rename from server-renderer (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
unstubbable committed Jan 9, 2019
1 parent 5fbfcc6 commit af946fa
Show file tree
Hide file tree
Showing 27 changed files with 147 additions and 240 deletions.
16 changes: 8 additions & 8 deletions docs/guides/sharing-the-browser-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ registers it at the Feature Service registry.
In the browser:

```js
import {defineAsyncSsrManager} from '@feature-hub/async-ssr-manager';
import {FeatureServiceRegistry} from '@feature-hub/core';
import {
defineHistoryService,
createRootLocationTransformer
} from '@feature-hub/history-service';
import {defineServerRenderer} from '@feature-hub/server-renderer';
```

```js
Expand All @@ -122,16 +122,16 @@ const rootLocationTransformer = createRootLocationTransformer({
});

const featureServiceDefinitions = [
defineHistoryService(rootLocationTransformer),
defineServerRenderer()
defineAsyncSsrManager(undefined),
defineHistoryService(rootLocationTransformer)
];

registry.registerFeatureServices(featureServiceDefinitions, 'acme:integrator');
```

On the server, the integrator defines the server renderer using the request. The
History Service depends on the server renderer to obtain its request and use it
for the initial history location:
On the server, the integrator defines the Async SSR Manager using the request.
The History Service depends on the Async SSR Manager to obtain its request and
use it for the initial history location:

```js
const registry = new FeatureServiceRegistry();
Expand All @@ -145,8 +145,8 @@ const request = {
};

const featureServiceDefinitions = [
defineHistoryService(rootLocationTransformer),
defineServerRenderer(request)
defineAsyncSsrManager(request),
defineHistoryService(rootLocationTransformer)
];

registry.registerFeatureServices(featureServiceDefinitions, 'acme:integrator');
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,41 +1,40 @@
# @feature-hub/server-renderer
# @feature-hub/async-ssr-manager

[![Package Version][package-badge]][package-npm]
[![Website][website-badge]][website] [![API][api-badge]][api]

A Feature Service to manage server-side rendering.
A Feature Service to manage asynchronous server-side rendering.

**Note:** This Feature Service is under active development (see
[#25][issue-25]). It currently doesn't implement everything necessary for
server-side rendering, but instead only provides read-access to the incoming
request.
server-side rendering.

## Installation

### Using Yarn

```sh
yarn add @feature-hub/server-renderer
yarn add @feature-hub/async-ssr-manager
```

### Using NPM

```sh
npm install @feature-hub/server-renderer
npm install @feature-hub/async-ssr-manager
```

---

Copyright (c) 2018-2019 SinnerSchrader Deutschland GmbH. Released under the
terms of the [MIT License][license].

[api]: https://feature-hub.io/@feature-hub/server-renderer/
[api]: https://feature-hub.io/@feature-hub/async-ssr-manager/
[api-badge]:
https://img.shields.io/badge/API-%40feature--hub%2Fserver--renderer-%23ea3458.svg
https://img.shields.io/badge/API-%40feature--hub%2Fasync--ssr--manager-%23ea3458.svg
[issue-25]: https://github.com/sinnerschrader/feature-hub/issues/25
[license]: https://github.com/sinnerschrader/feature-hub/blob/master/LICENSE
[package-badge]: https://img.shields.io/npm/v/@feature-hub/server-renderer.svg
[package-npm]: https://www.npmjs.com/package/@feature-hub/server-renderer
[package-badge]: https://img.shields.io/npm/v/@feature-hub/async-ssr-manager.svg
[package-npm]: https://www.npmjs.com/package/@feature-hub/async-ssr-manager
[website]: https://feature-hub.io/
[website-badge]:
https://img.shields.io/badge/Website-feature--hub.io-%23500dc5.svg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@feature-hub/server-renderer",
"version": "0.11.0",
"description": "A Feature Service to manage server-side rendering.",
"name": "@feature-hub/async-ssr-manager",
"version": "0.0.0",
"description": "A Feature Service to manage asynchronous server-side rendering.",
"homepage": "https://feature-hub.io/",
"bugs": {
"url": "https://github.com/sinnerschrader/feature-hub/issues"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import {
FeatureServiceProviderDefinition
} from '@feature-hub/core';
import mockConsole from 'jest-mock-console';
import {ServerRendererV1, ServerRequest, defineServerRenderer} from '..';
import {ServerRendererConfig} from '../config';
import {
AsyncSsrManagerConfig,
AsyncSsrManagerV1,
ServerRequest,
defineAsyncSsrManager
} from '..';
import {useFakeTimers} from './use-fake-timers';

describe('defineServerRenderer', () => {
let mockEnv: FeatureAppEnvironment<ServerRendererConfig, {}>;
let serverRendererDefinition: FeatureServiceProviderDefinition;
describe('defineAsyncSsrManager', () => {
let mockEnv: FeatureAppEnvironment<AsyncSsrManagerConfig, {}>;
let asyncSsrManagerDefinition: FeatureServiceProviderDefinition;
let serverRequest: ServerRequest;

beforeEach(() => {
Expand All @@ -28,19 +32,19 @@ describe('defineServerRenderer', () => {
headers: {}
};

serverRendererDefinition = defineServerRenderer(serverRequest);
asyncSsrManagerDefinition = defineAsyncSsrManager(serverRequest);
});

it('creates a server renderer definition', () => {
expect(serverRendererDefinition.id).toBe('s2:server-renderer');
expect(serverRendererDefinition.dependencies).toBeUndefined();
it('creates an Async SSR Manager definition', () => {
expect(asyncSsrManagerDefinition.id).toBe('s2:async-ssr-manager');
expect(asyncSsrManagerDefinition.dependencies).toBeUndefined();
});

describe('#create', () => {
it('creates a shared Feature Service containing version 1.0', () => {
const sharedServerRenderer = serverRendererDefinition.create(mockEnv);
const sharedAsyncSsrManager = asyncSsrManagerDefinition.create(mockEnv);

expect(sharedServerRenderer['1.0']).toBeDefined();
expect(sharedAsyncSsrManager['1.0']).toBeDefined();
});

for (const invalidConfig of [null, {timeout: false}]) {
Expand All @@ -49,41 +53,42 @@ describe('defineServerRenderer', () => {
)}`, () => {
it('throws an error', () => {
expect(() =>
serverRendererDefinition.create({
asyncSsrManagerDefinition.create({
featureServices: {},
config: invalidConfig
})
).toThrowError(new Error('The ServerRenderer config is invalid.'));
).toThrowError(new Error('The Async SSR Manager config is invalid.'));
});
});
}
});

describe('ServerRendererV1', () => {
let serverRendererBinder: FeatureServiceBinder<ServerRendererV1>;
describe('AsyncSsrManagerV1', () => {
let asyncSsrManagerBinder: FeatureServiceBinder<AsyncSsrManagerV1>;

beforeEach(() => {
serverRendererBinder = serverRendererDefinition.create(mockEnv)[
asyncSsrManagerBinder = asyncSsrManagerDefinition.create(mockEnv)[
'1.0'
] as FeatureServiceBinder<ServerRendererV1>;
] as FeatureServiceBinder<AsyncSsrManagerV1>;
});

it('exposes a serverRequest', () => {
const serverRenderer = serverRendererBinder('test:1').featureService;
const asyncSsrManager = asyncSsrManagerBinder('test:1').featureService;

expect(serverRenderer.serverRequest).toEqual(serverRequest);
expect(asyncSsrManager.serverRequest).toEqual(serverRequest);
});

describe('rendering', () => {
const createServerRendererConsumer = (consumerUid: string) => {
const serverRenderer = serverRendererBinder(consumerUid).featureService;
const createAsyncSsrManagerConsumer = (consumerUid: string) => {
const asyncSsrManager = asyncSsrManagerBinder(consumerUid)
.featureService;

let firstRender = true;

const render = () => {
if (firstRender) {
firstRender = false;
serverRenderer.rerenderAfter(Promise.resolve());
asyncSsrManager.rerenderAfter(Promise.resolve());
}
};

Expand All @@ -92,9 +97,9 @@ describe('defineServerRenderer', () => {

describe('with an integrator as the only consumer', () => {
it('resolves with the result of the given render function after the first render pass', async () => {
const serverRenderer = serverRendererBinder('test').featureService;
const asyncSsrManager = asyncSsrManagerBinder('test').featureService;
const mockRender = jest.fn(() => 'testHtml');
const html = await serverRenderer.renderUntilCompleted(mockRender);
const html = await asyncSsrManager.renderUntilCompleted(mockRender);

expect(html).toEqual('testHtml');
expect(mockRender).toHaveBeenCalledTimes(1);
Expand All @@ -103,21 +108,21 @@ describe('defineServerRenderer', () => {

describe('with an integrator, and a consumer that triggers a rerender', () => {
it('resolves with an html string after the second render pass', async () => {
const serverRendererIntegrator = serverRendererBinder(
const asyncSsrManagerIntegrator = asyncSsrManagerBinder(
'test:integrator'
).featureService;

const serverRendererConsumer = createServerRendererConsumer(
const asyncSsrManagerConsumer = createAsyncSsrManagerConsumer(
'test:consumer'
);

const mockRender = jest.fn(() => {
serverRendererConsumer.render();
asyncSsrManagerConsumer.render();

return 'testHtml';
});

const html = await serverRendererIntegrator.renderUntilCompleted(
const html = await asyncSsrManagerIntegrator.renderUntilCompleted(
mockRender
);

Expand All @@ -128,26 +133,26 @@ describe('defineServerRenderer', () => {

describe('with an integrator, and two consumers that both trigger a rerender in the first render pass', () => {
it('resolves with an html string after the second render pass', async () => {
const serverRendererIntegrator = serverRendererBinder(
const asyncSsrManagerIntegrator = asyncSsrManagerBinder(
'test:integrator'
).featureService;

const serverRendererConsumer1 = createServerRendererConsumer(
const asyncSsrManagerConsumer1 = createAsyncSsrManagerConsumer(
'test:consumer:1'
);

const serverRendererConsumer2 = createServerRendererConsumer(
const asyncSsrManagerConsumer2 = createAsyncSsrManagerConsumer(
'test:consumer:2'
);

const mockRender = jest.fn(() => {
serverRendererConsumer1.render();
serverRendererConsumer2.render();
asyncSsrManagerConsumer1.render();
asyncSsrManagerConsumer2.render();

return 'testHtml';
});

const html = await serverRendererIntegrator.renderUntilCompleted(
const html = await asyncSsrManagerIntegrator.renderUntilCompleted(
mockRender
);

Expand All @@ -158,31 +163,31 @@ describe('defineServerRenderer', () => {

describe('when the given render function throws an error', () => {
it('rejects with the error', async () => {
const serverRenderer = serverRendererBinder('test').featureService;
const asyncSsrManager = asyncSsrManagerBinder('test').featureService;
const mockError = new Error('Failed to render.');

const mockRender = jest.fn(() => {
throw mockError;
});

return expect(
serverRenderer.renderUntilCompleted(mockRender)
asyncSsrManager.renderUntilCompleted(mockRender)
).rejects.toEqual(mockError);
});
});

describe('when rendering takes longer than the configured timeout', () => {
it('rejects with an error after the configured timeout', async () => {
const serverRenderer = serverRendererBinder('test').featureService;
const asyncSsrManager = asyncSsrManagerBinder('test').featureService;
const mockRender = jest.fn(() => {
serverRenderer.rerenderAfter(new Promise<void>(() => undefined));
asyncSsrManager.rerenderAfter(new Promise<void>(() => undefined));

return 'testHtml';
});

return expect(
useFakeTimers(
async () => serverRenderer.renderUntilCompleted(mockRender),
async () => asyncSsrManager.renderUntilCompleted(mockRender),
5
)
).rejects.toEqual(new Error('Got rendering timeout after 5 ms.'));
Expand All @@ -191,23 +196,23 @@ describe('defineServerRenderer', () => {

describe('when no timeout is configured', () => {
beforeEach(() => {
serverRendererBinder = serverRendererDefinition.create({
asyncSsrManagerBinder = asyncSsrManagerDefinition.create({
config: undefined,
featureServices: {}
})['1.0'] as FeatureServiceBinder<ServerRendererV1>;
})['1.0'] as FeatureServiceBinder<AsyncSsrManagerV1>;
});

it('logs a warning', async () => {
const serverRenderer = serverRendererBinder('test').featureService;
const asyncSsrManager = asyncSsrManagerBinder('test').featureService;
const mockRender = jest.fn(() => 'testHtml');
const restoreConsole = mockConsole();

await useFakeTimers(async () =>
serverRenderer.renderUntilCompleted(mockRender)
asyncSsrManager.renderUntilCompleted(mockRender)
);

expect(console.warn).toHaveBeenCalledWith(
'No timeout is configured for the server renderer. This could lead to unexpectedly long render times or, in the worst case, never resolving render calls!'
'No timeout is configured for the Async SSR Manager. This could lead to unexpectedly long render times or, in the worst case, never resolving render calls!'
);

restoreConsole();
Expand Down
47 changes: 47 additions & 0 deletions packages/async-ssr-manager/src/define-async-ssr-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
FeatureServiceBinder,
FeatureServiceProviderDefinition,
SharedFeatureService
} from '@feature-hub/core';
import {AsyncSsrManager} from './internal/async-ssr-manager';
import {validateConfig} from './internal/validate-config';

export interface ServerRequest {
readonly path: string;
readonly cookies: Record<string, string>;
readonly headers: Record<string, string>;
}

export interface AsyncSsrManagerConfig {
readonly timeout?: number;
}

export interface AsyncSsrManagerV1 {
readonly serverRequest: ServerRequest | undefined;

renderUntilCompleted(render: () => string): Promise<string>;
rerenderAfter(promise: Promise<unknown>): void;
}

interface SharedAsyncSsrManager extends SharedFeatureService {
readonly '1.0': FeatureServiceBinder<AsyncSsrManagerV1>;
}

export function defineAsyncSsrManager(
serverRequest: ServerRequest | undefined
): FeatureServiceProviderDefinition {
return {
id: 's2:async-ssr-manager',

create: (env): SharedAsyncSsrManager => {
const {timeout} =
validateConfig(env.config) || ({} as AsyncSsrManagerConfig);

const asyncSsrManager = new AsyncSsrManager(serverRequest, timeout);

return {
'1.0': () => ({featureService: asyncSsrManager})
};
}
};
}
1 change: 1 addition & 0 deletions packages/async-ssr-manager/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './define-async-ssr-manager';
Loading

0 comments on commit af946fa

Please sign in to comment.