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

WIP Add support for idiomatic svelte3 stories #7023

Closed
wants to merge 1 commit into from
Closed
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
12 changes: 12 additions & 0 deletions app/svelte3/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
extends: '../../.eslintrc.js',
overrides: [
{
files: ['*.svelte'],
plugins: ['svelte3'],
rules: {
'import/no-mutable-exports': 'off',
},
},
],
};
2 changes: 2 additions & 0 deletions app/svelte3/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
docs
.babelrc
32 changes: 32 additions & 0 deletions app/svelte3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Storybook for Svelte

Storybook for Svelte is a UI development environment for your Svelte components.
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-svelte-app
npx -p @storybook/cli sb init
```

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.

## TODOs

- [ ] Support `addon-info`
- [ ] Support Svelte markup directly in stories
- [ ] Add Svelte storybook generator
- [ ] Provide stories that show advanced Svelte use cases
- [ ] Hydratable
- [ ] Advanced mount options
5 changes: 5 additions & 0 deletions app/svelte3/bin/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env node

process.env.NODE_ENV = process.env.NODE_ENV || 'production';

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

require('../dist/server');
54 changes: 54 additions & 0 deletions app/svelte3/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@storybook/svelte3",
"version": "5.1.1",
"description": "Storybook for Svelte3: Develop Svelte Component in isolation with Hot Reloading.",
"keywords": [
"storybook"
],
"homepage": "https://github.com/storybookjs/storybook/tree/master/app/svelte3",
"bugs": {
"url": "https://github.com/storybookjs/storybook/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/storybookjs/storybook.git",
"directory": "app/svelte3"
},
"license": "MIT",
"main": "dist/client/index.js",
"jsnext:main": "src/client/index.js",
"bin": {
"build-storybook": "./bin/build.js",
"start-storybook": "./bin/index.js",
"storybook-server": "./bin/index.js"
},
"scripts": {
"prepare": "node ../../scripts/prepare.js",
"watch": "node ../../scripts/watch-babel.js"
},
"dependencies": {
"@storybook/core": "5.1.1",
"@storybook/addon-centered": "5.1.1",
"common-tags": "^1.8.0",
"core-js": "^3.0.1",
"eslint-plugin-svelte3": "^1.2.3",
"global": "^4.3.2",
"lodash": "^4.17.11",
"regenerator-runtime": "^0.12.1"
},
"devDependencies": {
"svelte": "^3.4.1",
"svelte-loader": "^2.13.4"
},
"peerDependencies": {
"babel-loader": "^7.0.0 || ^8.0.0",
"svelte": "^3.2",
"svelte-loader": "^2.13"
},
"engines": {
"node": ">=8.0.0"
},
"publishConfig": {
"access": "public"
}
}
17 changes: 17 additions & 0 deletions app/svelte3/src/addons/centered/Centered.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script>
/* eslint-disable import/no-mutable-exports */

import styles from '@storybook/addon-centered/dist/styles';
import json2CSS from '@storybook/addon-centered/dist/helpers/json2CSS';

const style = json2CSS(styles.style);
const innerStyle = json2CSS(styles.innerStyle);

export let Story;
</script>

<div style={style}>
<div style={innerStyle}>
<svelte:component this={Story} />
</div>
</div>
8 changes: 8 additions & 0 deletions app/svelte3/src/addons/centered/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Centered from './Centered.svelte';

export default storyFn =>
class extends Centered {
constructor(opts) {
super({ ...opts, props: { ...opts.props, Story: storyFn() } });
}
};
15 changes: 15 additions & 0 deletions app/svelte3/src/client/components/Preview.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
import { setContext } from 'svelte';

import { CTX_RENDER } from '../constants';

export let Stories;
export let selectedKind;
export let selectedStory;

const render = { selectedKind, selectedStory };

setContext(CTX_RENDER, render);
</script>

<svelte:component this={Stories} />
11 changes: 11 additions & 0 deletions app/svelte3/src/client/components/RegisterContext.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
import { setContext } from 'svelte';
import { CTX_REGISTER, CTX_RENDER } from '../constants';

export let Stories;
export let register;

setContext(CTX_REGISTER, register);
</script>

<svelte:component this={Stories} />
52 changes: 52 additions & 0 deletions app/svelte3/src/client/components/StoriesOf.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script>
import { getContext } from 'svelte';
import storiesOf from '../utils/storiesOf';
import {
CTX_MODULE,
CTX_STORIES,
CTX_DECORATORS,
CTX_PARAMETERS,
CTX_RENDER,
CTX_REGISTER,
} from '../constants';

export let kind;
// export let module;
let m;
export { m as module };
export let parameters;

if (!m) {
m = getContext(CTX_MODULE);
}

storiesOf(kind, m);

const register = getContext(CTX_REGISTER);
const render = getContext(CTX_RENDER);

const isMyKind = ({ selectedKind }) => selectedKind === kind;

if (register) {
const stories = getContext(CTX_STORIES);
const decorators = getContext(CTX_DECORATORS);
const ctxParams = getContext(CTX_PARAMETERS);
if (decorators) {
decorators.forEach(decorator => stories.addDecorator(...decorator));
}
if (ctxParams) {
ctxParams.forEach(params => stories.addParameters(...params));
}
if (parameters) {
if (Array.isArray(parameters)) {
parameters.forEach(params => stories.addParameters(params));
} else {
stories.addParameters(parameters);
}
}
}
</script>

{#if register || render && isMyKind(render)}
<slot />
{/if}
24 changes: 24 additions & 0 deletions app/svelte3/src/client/components/Story.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script>
import { getContext } from 'svelte';
import { CTX_STORIES, CTX_REGISTER, CTX_RENDER } from '../constants';

export let name;
export let parameters;

if (!name) {
throw new Error('Missing Story name');
}

const render = getContext(CTX_RENDER);
const renderThis = render && render.selectedStory === name;

const register = getContext(CTX_REGISTER);
if (register) {
const stories = getContext(CTX_STORIES);
stories.add(name, (...args) => register.storyFn(...args), parameters);
}
</script>

{#if renderThis}
<slot />
{/if}
7 changes: 7 additions & 0 deletions app/svelte3/src/client/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// TODO use truly unique identifiers to avoid collisions with user's context
export const CTX_STORIES = 'stories';
export const CTX_DECORATORS = 'decorators';
export const CTX_PARAMETERS = 'parameters';
export const CTX_MODULE = 'module';
export const CTX_REGISTER = 'register';
export const CTX_RENDER = 'render';
20 changes: 20 additions & 0 deletions app/svelte3/src/client/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export {
setAddon,
addDecorator,
addParameters,
configure,
getStorybook,
forceReRender,
raw,
} from './preview';

// functions
export { default as loadSvelteStories } from './utils/loadSvelteStories';
export { default as storiesOf } from './utils/storiesOf';
// components
export { default as Story } from './components/Story.svelte';
export { default as StoriesOf } from './components/StoriesOf.svelte';

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

window.STORYBOOK_ENV = 'svelte3';
19 changes: 19 additions & 0 deletions app/svelte3/src/client/preview/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { start } from '@storybook/core/client';

import './globals';
import render from './render';

const { clientApi, configApi, forceReRender } = start(render);

export const {
storiesOf,
setAddon,
addDecorator,
addParameters,
clearDecorators,
getStorybook,
raw,
} = clientApi;

export const { configure } = configApi;
export { forceReRender };
49 changes: 49 additions & 0 deletions app/svelte3/src/client/preview/render.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { document } from 'global';

import Preview from '../components/Preview.svelte';

let previousPreview = null;

export default function render({
storyFn,
selectedKind,
selectedStory,
showMain,
// showError,
showException,
}) {
const Stories = storyFn();

if (previousPreview) {
previousPreview.$destroy();
previousPreview = null;
}

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

target.innerHTML = '';

try {
previousPreview = new Preview({
target,
props: {
Stories,
selectedKind,
selectedStory,
},
});
showMain();
} catch (ex) {
showException(ex);
// cleanup
if (previousPreview && previousPreview.$destroy) {
try {
previousPreview.$destroy();
} catch (err) {
// eslint-disable-next-line no-console
console.warn('Failed to destroy previous component', err);
}
}
previousPreview = null;
}
}
43 changes: 43 additions & 0 deletions app/svelte3/src/client/utils/loadSvelteStories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* global document */

import RegisterContext from '../components/RegisterContext.svelte';

const createFragment = document.createDocumentFragment
? () => document.createDocumentFragment()
: () => document.createElement('div');

/**
* This needs to be hooked in user's `loadStories`:
*
* import { configure, loadSvelteStories } from '@storybook/svelte'
*
* function loadStories() {
* const req = require.context('../src', true, /\.(stories|story)\.svelte$/)
* loadSvelteStories(req)
* }
*
* configure(loadStories, module)
*/
const loadSvelteStories = req => {
req.keys().forEach(filename => {
try {
const Stories = req(filename).default;
const storyFn = () => Stories;
const cmp = new RegisterContext({
target: createFragment(),
props: {
Stories,
register: { storyFn },
},
});
cmp.$destroy();
} catch (err) {
// now what?
// TODO find how to display this exception (all while we don't have
// the stories' kind/names) -- meanwhile, don't hide it
throw err;
}
});
};

export default loadSvelteStories;
Loading