Skip to content

Commit

Permalink
Server: Initial support for @storybook/server (#9722)
Browse files Browse the repository at this point in the history
Server: Initial support for @storybook/server
  • Loading branch information
shilman committed Feb 4, 2020
2 parents 107d813 + 3007fe5 commit fc07ec1
Show file tree
Hide file tree
Showing 89 changed files with 1,914 additions and 52 deletions.
25 changes: 25 additions & 0 deletions app/server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Storybook for Server

---

Storybook for Server is a UI development environment for your plain HTML snippets rendered by your server backend.
With it, you can visualize different states of your UI components and develop them interactively.

![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/master/media/storybook-intro.gif)

Storybook runs outside of your app.
So you can develop UI components in isolation without worrying about app specific dependencies and requirements.

## Getting Started

```sh
cd my-app
npx -p @storybook/cli sb init -t server
```

For more information visit: [storybook.js.org](https://storybook.js.org)

---

Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish.
You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want.
4 changes: 4 additions & 0 deletions app/server/bin/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node

process.env.NODE_ENV = process.env.NODE_ENV || 'production';
require('../dist/server/build');
3 changes: 3 additions & 0 deletions app/server/bin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

require('../dist/server');
59 changes: 59 additions & 0 deletions app/server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "@storybook/server",
"version": "6.0.0-alpha.4",
"description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.",
"keywords": [
"storybook"
],
"homepage": "https://github.com/storybookjs/storybook/tree/master/app/server",
"bugs": {
"url": "https://github.com/storybookjs/storybook/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/storybookjs/storybook.git",
"directory": "app/html"
},
"license": "MIT",
"files": [
"bin/**/*",
"dist/**/*",
"README.md",
"*.js",
"*.d.ts"
],
"main": "dist/client/index.js",
"types": "dist/client/index.d.ts",
"bin": {
"build-storybook": "./bin/build.js",
"start-storybook": "./bin/index.js",
"storybook-server": "./bin/index.js"
},
"scripts": {
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.0.0-alpha.4",
"@storybook/core": "6.0.0-alpha.4",
"@storybook/node-logger": "^5.2.8",
"@types/webpack-env": "^1.13.9",
"core-js": "^3.0.1",
"global": "^4.3.2",
"regenerator-runtime": "^0.13.3",
"safe-identifier": "^0.3.1",
"ts-dedent": "^1.1.0"
},
"devDependencies": {
"fs-extra": "^8.0.1"
},
"peerDependencies": {
"babel-loader": "^7.0.0 || ^8.0.0"
},
"engines": {
"node": ">=8.0.0"
},
"publishConfig": {
"access": "public"
},
"gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a"
}
14 changes: 14 additions & 0 deletions app/server/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export {
storiesOf,
setAddon,
addDecorator,
addParameters,
configure,
getStorybook,
forceReRender,
raw,
} from './preview';

if (module && module.hot && module.hot.decline) {
module.hot.decline();
}
3 changes: 3 additions & 0 deletions app/server/src/client/preview/globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { window } from 'global';

window.STORYBOOK_ENV = 'SERVER';
43 changes: 43 additions & 0 deletions app/server/src/client/preview/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { start } from '@storybook/core/client';
import { ClientStoryApi, Loadable } from '@storybook/addons';

import './globals';
import { renderMain as render, setFetchStoryHtml } from './render';
import { StoryFnServerReturnType, IStorybookSection, ConfigureOptionsArgs } from './types';

const framework = 'server';

interface ClientApi extends ClientStoryApi<StoryFnServerReturnType> {
setAddon(addon: any): void;
configure(loader: Loadable, module: NodeModule, options?: ConfigureOptionsArgs): void;
getStorybook(): IStorybookSection[];
clearDecorators(): void;
forceReRender(): void;
raw: () => any; // todo add type
}

const api = start(render);

export const storiesOf: ClientApi['storiesOf'] = (kind, m) => {
return (api.clientApi.storiesOf(kind, m) as ReturnType<ClientApi['storiesOf']>).addParameters({
framework,
});
};

const setRenderFetchAndConfigure: ClientApi['configure'] = (loader, module, options) => {
if (options && options.fetchStoryHtml) {
setFetchStoryHtml(options.fetchStoryHtml);
}
api.configure(loader, module, framework);
};

export const configure: ClientApi['configure'] = setRenderFetchAndConfigure;
export const {
addDecorator,
addParameters,
clearDecorators,
setAddon,
forceReRender,
getStorybook,
raw,
} = api.clientApi;
61 changes: 61 additions & 0 deletions app/server/src/client/preview/render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { document, fetch, Node } from 'global';
import dedent from 'ts-dedent';
import { RenderMainArgs, FetchStoryHtmlType } from './types';

const rootElement = document.getElementById('root');

let fetchStoryHtml: FetchStoryHtmlType = async (url, path, params) => {
const fetchUrl = new URL(`${url}/${path}`);
fetchUrl.search = new URLSearchParams(params).toString();

const response = await fetch(fetchUrl);
return response.text();
};

export async function renderMain({
storyFn,
id,
selectedKind,
selectedStory,
showMain,
showError,
forceRender,
parameters,
}: RenderMainArgs) {
const storyParams = storyFn();

const {
server: { url, id: storyId, params },
} = parameters;

const fetchId = storyId || id;
const fetchParams = { ...params, ...storyParams };
const element = await fetchStoryHtml(url, fetchId, fetchParams);

showMain();
if (typeof element === 'string') {
rootElement.innerHTML = element;
} else if (element instanceof Node) {
// Don't re-mount the element if it didn't change and neither did the story
if (rootElement.firstChild === element && forceRender === true) {
return;
}

rootElement.innerHTML = '';
rootElement.appendChild(element);
} else {
showError({
title: `Expecting an HTML snippet or DOM node from the story: "${selectedStory}" of "${selectedKind}".`,
description: dedent`
Did you forget to return the HTML snippet from the story?
Use "() => <your snippet or node>" or when defining the story.
`,
});
}
}

export const setFetchStoryHtml: any = (fetchHtml: FetchStoryHtmlType) => {
if (fetchHtml !== undefined) {
fetchStoryHtml = fetchHtml;
}
};
35 changes: 35 additions & 0 deletions app/server/src/client/preview/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { StoryFn } from '@storybook/addons';

export type StoryFnServerReturnType = any;

export type FetchStoryHtmlType = (url: string, id: string, params: any) => Promise<string | Node>;

export interface IStorybookStory {
name: string;
render: () => any;
}

export interface IStorybookSection {
kind: string;
stories: IStorybookStory[];
}

export interface ShowErrorArgs {
title: string;
description: string;
}

export interface ConfigureOptionsArgs {
fetchStoryHtml: FetchStoryHtmlType;
}

export interface RenderMainArgs {
storyFn: () => StoryFn<StoryFnServerReturnType>;
id: string;
selectedKind: string;
selectedStory: string;
showMain: () => void;
showError: (args: ShowErrorArgs) => void;
forceRender: boolean;
parameters: any;
}
15 changes: 15 additions & 0 deletions app/server/src/lib/compiler/__testfixtures__/a11y.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "Addons/a11y",
"addons": ["a11y"],
"parameters": {
"options": { "selectedPanel": "storybook/a11y/panel" }
},
"stories": [
{
"name": "Label",
"parameters": {
"server": { "id": "addons/a11y/label" }
}
}
]
}
28 changes: 28 additions & 0 deletions app/server/src/lib/compiler/__testfixtures__/a11y.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`json-to-csf-compiler a11y.json 1`] = `
"import { withA11y } from '@storybook/addon-a11y';

export default {
title: 'Addons/a11y',
decorators: [
withA11y
],
parameters: {
options: {
selectedPanel: 'storybook/a11y/panel'
}
}
};

export const Label = () => {};
Label.story = {
name: 'Label',
parameters: {
server: {
id: 'addons/a11y/label'
}
}
};
"
`;
16 changes: 16 additions & 0 deletions app/server/src/lib/compiler/__testfixtures__/actions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"title": "Addons/Actions",
"addons": ["actions"],
"parameters": {
"options": { "selectedPanel": "storybook/actions/panel" }
},
"stories": [
{
"name": "Multiple actions + config",
"parameters": {
"server": { "id": "addons/actions/story3" }
},
"actions": ["click", "contextmenu", { "clearOnStoryChange": false }]
}
]
}
34 changes: 34 additions & 0 deletions app/server/src/lib/compiler/__testfixtures__/actions.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`json-to-csf-compiler actions.json 1`] = `
"import { withActions } from '@storybook/addon-actions';

export default {
title: 'Addons/Actions',
parameters: {
options: {
selectedPanel: 'storybook/actions/panel'
}
}
};

export const Multiple_actions_config = () => {};
Multiple_actions_config.story = {
decorators: [
withActions(
'click',
'contextmenu',
{
clearOnStoryChange: false
}
)
],
name: 'Multiple actions + config',
parameters: {
server: {
id: 'addons/actions/story3'
}
}
};
"
`;
17 changes: 17 additions & 0 deletions app/server/src/lib/compiler/__testfixtures__/backgrounds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"title": "Addons/Backgrounds",
"parameters": {
"backgrounds": [
{ "name": "light", "value": "#eeeeee" },
{ "name": "dark", "value": "#222222", "default": true }
]
},
"stories": [
{
"name": "Story 1",
"parameters": {
"server": { "id": "addons/backgrounds/story1" }
}
}
]
}

0 comments on commit fc07ec1

Please sign in to comment.