Merge branch 'refactoring/remove-mantra' into refactoring/remove-mantra-ui-overhaul

This commit is contained in:
Norbert de Langen 2018-08-10 16:09:33 +02:00
commit d2fb2eca92
No known key found for this signature in database
GPG Key ID: 976651DA156C2825
99 changed files with 2316 additions and 379 deletions

View File

@ -39,6 +39,7 @@ jobs:
- examples/official-storybook/node_modules
- examples/polymer-cli/node_modules
- examples/vue-kitchen-sink/node_modules
- examples/svelte-kitchen-sink/node_modules
- examples/marko-cli/node_modules
- save_cache:
name: "Cache core dist"
@ -72,6 +73,11 @@ jobs:
command: |
cd examples/vue-kitchen-sink
yarn build-storybook
- run:
name: "Build svelte kitchen-sink"
command: |
cd examples/svelte-kitchen-sink
yarn build-storybook
- run:
name: "Build angular-cli"
command: |
@ -126,6 +132,11 @@ jobs:
command: |
cd examples/vue-kitchen-sink
yarn storybook --smoke-test
- run:
name: "Run svelte kitchen-sink (smoke test)"
command: |
cd examples/svelte-kitchen-sink
yarn storybook --smoke-test
- run:
name: "Run angular-cli (smoke test)"
command: |

View File

@ -3,6 +3,6 @@ root = true
[*]
end_of_line = lf
[*.{js,json,ts,vue,html}]
[*.{js,json,ts,vue,svelte,html}]
indent_style = space
indent_size = 2

6
.github/CODEOWNERS vendored
View File

@ -13,7 +13,7 @@
/addons/links/ @hypnosphi @ndelangen
/addons/notes/ @alexandrebodin
/addons/options/ @danielduan @UsulPro
/addons/storyshots/ @igor-dv @thomasbertet @hypnosphi
/addons/storyshots/ @igor-dv @thomasbertet @hypnosphi
/addons/storysource/ @igor-dv
/addons/viewport/ @saponifi3d
@ -22,16 +22,18 @@
/app/react/ @xavcz @shilman @thomasbertet
/app/react-native/ @rmevans9 @danielduan @Gongreg @tmeasday
/app/vue/ @thomasbertet @kazupon
/app/svelte/ @plumpNation
/docs/ @ndelangen @shilman @hypnosphi
/examples/angular-cli/ @igor-dv @alterx
/examples/cra-kitchen-sink/ @ndelangen @UsulPro @hypnosphi
/examples/cra-kitchen-sink/ @ndelangen @UsulPro @hypnosphi
/examples/crna-kitchen-sink/ @Gongreg @danielduan
/examples/official-storybook/ @hypnosphi @danielduan @UsulPro
/examples/polymer-cli/ @naipath @igor-dv
/examples/react-native-vanilla/ @tmeasday @danielduan
/examples/vue-kitchen-sink/ @igor-dv @alexandrebodin
/examples/svelte-kitchen-sink/ @plumpNation
/lib/addons/ @ndelangen @theinterned
/lib/channel-postmessage/ @mnmtanish @ndelangen

View File

@ -17,6 +17,7 @@
'app: react-native': ["app/react-native/**"]
'app: react': ["app/react/**"]
'app: vue': ["app/vue/**"]
'app: svelte': ["app/svelte/**"]
'app: mithril': ["app/mithril/**"]
'babel / webpack': ["webpack", "babel"]
'cli': ["lib/cli/**"]

View File

@ -6,6 +6,7 @@
addon
1
vue
svelte
webcomponents
aurelia
iframe

View File

@ -15,7 +15,7 @@ enum class StorybookApp(val appName: String, val exampleDir: String, val merged:
HTML("HTML", "html-kitchen-sink"),
MARKO("Marko", "marko-cli"),
HYPERAPP("Hyperapp", "hyperapp-kitchen-sink", false),
SVELTE("Svelte", "svelte-kitchen-sink", false);
SVELTE("Svelte", "svelte-kitchen-sink");
val lowerName = appName.toLowerCase()

View File

@ -1,19 +1,19 @@
## Addon / Framework Support Table
| |[React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [HTML](app/html)| [Marko](app/marko)|
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|[a11y](addons/a11y) |+| |+|+|+|+|+|+|
|[actions](addons/actions) |+|+|+|+|+|+|+|+|
|[backgrounds](addons/backgrounds) |+| |+|+|+|+|+|+|
|[centered](addons/centered) |+| |+|+| |+|+| |
|[events](addons/events) |+| |+|+|+|+|+|+|
|[graphql](addons/graphql) |+| | | | | | | |
|[info](addons/info) |+| | | | | | | |
|[jest](addons/jest) |+| | |+| | |+| |
|[knobs](addons/knobs) |+|+|+|+|+|+|+|+|
|[links](addons/links) |+|+|+|+|+|+|+| |
|[notes](addons/notes) |+| |+|+|+|+|+| |
|[options](addons/options) |+|+|+|+|+|+|+| |
|[storyshots](addons/storyshots) |+|+|+|+| | |+| |
|[storysource](addons/storysource)|+| |+|+|+|+|+|+|
|[viewport](addons/viewport) |+| |+|+|+|+|+|+|
| |[React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [HTML](app/html)| [Marko](app/marko)| [Svelte](app/svelte)|
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|[a11y](addons/a11y) |+| |+|+|+|+|+|+| |
|[actions](addons/actions) |+|+|+|+|+|+|+|+|+|
|[backgrounds](addons/backgrounds) |+| |+|+|+|+|+|+|+|
|[centered](addons/centered) |+| |+|+| |+|+| |+|
|[events](addons/events) |+| |+|+|+|+|+|+| |
|[graphql](addons/graphql) |+| | | | | | | | |
|[info](addons/info) |+| | | | | | | | |
|[jest](addons/jest) |+| | |+| | |+| | |
|[knobs](addons/knobs) |+|+|+|+|+|+|+|+|+|
|[links](addons/links) |+|+|+|+|+|+|+| |+|
|[notes](addons/notes) |+| |+|+|+|+|+| |+|
|[options](addons/options) |+|+|+|+|+|+|+| |+|
|[storyshots](addons/storyshots) |+|+|+|+| | |+| |+|
|[storysource](addons/storysource)|+| |+|+|+|+|+|+|+|
|[viewport](addons/viewport) |+| |+|+|+|+|+|+|+|

View File

@ -53,7 +53,7 @@ You can use the `--update` flag to update snapshots or screenshots as needed.
You can also pick suites from CLI. Suites available are listed below.
##### Core & React & Vue Tests
##### Core & React & Vue & Svelte Tests
`yarn test --core`
@ -72,7 +72,7 @@ Before these tests are ran, the project must be bootstrapped with the React Nati
`yarn test --image`
This option executes tests from `<rootdir>/examples/official-storybook`
In order for the image snapshots to be correctly generated, you must have static build of the storybook up-to-date :
In order for the image snapshots to be correctly generated, you must have static build of the storybook up-to-date :
```javascript
cd examples/official-storybook

View File

@ -77,6 +77,7 @@ For additional help, join us [in our Slack](https://now-examples-slackin-rrirkqo
- [Mithril](app/mithril) <sup>alpha</sup>
- [Marko](app/marko) <sup>alpha</sup>
- [HTML](app/html) <sup>alpha</sup>
- [Svelte](app/svelte) <sup>alpha</sup>
### Sub Projects
@ -115,6 +116,7 @@ See [Addon / Framework Support Table](ADDONS_SUPPORT.md)
- [Mithril](https://storybooks-mithril.netlify.com/)
- [Marko](https://storybooks-marko.netlify.com/)
- [HTML](https://storybooks-html.netlify.com/)
- [Svelte](https://storybooks-svelte.netlify.com/)
### 3.4
- [React Official](https://release-3-4--storybooks-official.netlify.com)

View File

@ -2,7 +2,7 @@ import addons, { makeDecorator } from '@storybook/addons';
import CoreEvents from '@storybook/core-events';
import deprecate from 'util-deprecate';
import Events, { ADDON_ID } from './constants';
import Events from './constants';
let prevBackgrounds;
@ -12,7 +12,7 @@ const subscription = () => () => {
};
export const withBackgrounds = makeDecorator({
name: ADDON_ID,
name: 'withBackgrounds',
parameterName: 'backgrounds',
skipIfNoParametersOrOptions: true,
allowDeprecatedUsage: true,

View File

@ -47,6 +47,25 @@ storiesOf('MyComponent', module)
}));
```
example for Svelte:
```js
import { storiesOf } from '@storybook/svelte';
import Centered from '@storybook/addon-centered/svelte';
import Component from '../Component.svelte';
storiesOf('Addon|Centered', module)
.addDecorator(Centered)
.add('rounded', () => ({
Component,
data: {
rounded: true,
text: "Look, I'm centered!",
},
}))
```
example for Mithril:
```js
@ -96,7 +115,7 @@ storiesOf('Addon|Centered', module)
)
.addDecorator(centered)
.add('centered template', () => ({
template: `<storybook-button-component
template: `<storybook-button-component
[text]="text" (onClick)="onClick($event)">
</storybook-button-component>`,
props: {
@ -137,6 +156,19 @@ configure(function () {
}, module);
```
example for Svelte:
```js
import { configure, addDecorator } from '@storybook/svelte';
import Centered from '@storybook/addon-centered/svelte';
addDecorator(Centered);
configure(function () {
//...
}, module);
```
example for Mithril:
```js

View File

@ -0,0 +1,16 @@
<div class="svelte-centered-wrapper" style="{style}">
<div class="svelte-centered-container" style="{innerStyle}">
<slot></slot>
</div>
</div>
<script>
export default {
data() {
return {
style: '',
innerStyle: ''
};
}
};
</script>

View File

@ -0,0 +1,21 @@
import { document } from 'global';
/**
* Not all frameworks support an object for the style attribute but we want all to
* consume `styles.json`. Since `styles.json` uses standard style properties for keys,
* we can just set them on an element and then get the string result of that element's
* `style` attribute. This also means that invalid styles are filtered out.
*
* @param {Object} jsonStyles
* @returns {string}
* @see https://stackoverflow.com/questions/38533544/jsx-css-to-inline-styles
*/
export default function jsonToCss(jsonStyles) {
const frag = document.createElement('div');
Object.keys(jsonStyles).forEach(key => {
frag.style[key] = jsonStyles[key];
});
return frag.getAttribute('style');
}

View File

@ -0,0 +1,25 @@
import Centered from './components/Centered.svelte';
import styles from './styles';
import json2CSS from './helpers/json2CSS';
const centeredStyles = {
/** @type {string} */
style: json2CSS(styles.style),
/** @type {string} */
innerStyle: json2CSS(styles.innerStyle),
};
/**
* This functionality works by passing the svelte story component into another
* svelte component that has the single purpose of centering the story component
* using a wrapper and container.
*
* We use the special element <svelte:component /> to achieve this.
*
* @see https://svelte.technology/guide#svelte-component
*/
export default function(storyFn) {
const { Component: OriginalComponent, data, on } = storyFn();
return { Component: OriginalComponent, data, on, Wrapper: Centered, WrapperData: centeredStyles };
}

View File

@ -0,0 +1 @@
module.exports = require('./dist/svelte');

View File

@ -6,6 +6,7 @@ module.exports = {
'@storybook/react',
'@storybook/react-native',
'@storybook/vue',
'@storybook/svelte',
'enzyme',
],
},

View File

@ -0,0 +1,32 @@
import global from 'global';
import hasDependency from '../hasDependency';
import configure from '../configure';
function test(options) {
return (
options.framework === 'svelte' || (!options.framework && hasDependency('@storybook/svelte'))
);
}
function load(options) {
global.STORYBOOK_ENV = 'svelte';
const { configPath, config } = options;
const storybook = require.requireActual('@storybook/svelte');
configure({ configPath, config, storybook });
return {
framework: 'svelte',
renderTree: require.requireActual('./renderTree').default,
renderShallowTree: () => {
throw new Error('Shallow renderer is not supported for svelte');
},
storybook,
};
}
export default {
load,
test,
};

View File

@ -0,0 +1,29 @@
import { document } from 'global';
/**
* Provides functionality to convert your raw story to the resulting markup.
*
* Storybook snapshots need the rendered markup that svelte outputs,
* but since we only have the story config data ({ Component, data }) in
* the Svelte stories, we need to mount the component, and then return the
* resulting HTML.
*
* If we don't render to HTML, we will get a snapshot of the raw story
* i.e. ({ Component, data }).
*/
function getRenderedTree(story, context) {
const { Component, data } = story.render(context);
// We need to create a target to mount onto.
const target = document.createElement('section');
new Component({ target, data }); // eslint-disable-line
// Classify the target so that it is clear where the markup
// originates from, and that it is specific for snapshot tests.
target.className = 'storybook-snapshot-container';
return target;
}
export default getRenderedTree;

View File

@ -19,6 +19,6 @@
"puppeteer": "^1.6.2"
},
"peerDependencies": {
"@storybook/addon-storyshots": "4.0.0-alpha.9"
"@storybook/addon-storyshots": "4.0.0-alpha.16"
}
}

View File

@ -80,7 +80,9 @@ export function applyAngularCliWebpackConfig(baseConfig, cliWebpackConfigOptions
// cliStyleConfig.entry adds global style files to the webpack context
const entry = {
...baseConfig.entry,
...cliStyleConfig.entry,
iframe: []
.concat(baseConfig.entry.iframe)
.concat(Object.values(cliStyleConfig.entry).reduce((acc, item) => acc.concat(item), [])),
};
const mod = {

View File

@ -47,10 +47,11 @@
"case-sensitive-paths-webpack-plugin": "^2.1.2",
"commander": "^2.17.0",
"dotenv-webpack": "^1.5.7",
"ejs": "^2.6.1",
"express": "^4.16.3",
"find-cache-dir": "^2.0.0",
"generate-page-webpack-plugin": "^1.0.0",
"global": "^4.3.2",
"html-webpack-plugin": "^3.2.0",
"json5": "^1.0.1",
"prop-types": "^15.6.2",
"raw-loader": "^0.5.1",

View File

@ -116,7 +116,7 @@ export default function(configType, baseConfig, projectDir, configDir) {
if (typeof customConfig === 'function') {
logger.info('=> Loading custom webpack config (full-control mode).');
return customConfig(config, configType, defaultConfig);
return customConfig(config, configType, defaultConfig, configDir);
}
logger.info('=> Loading custom webpack config.');

View File

@ -4,54 +4,73 @@ import { getEnvironment } from 'universal-dotenv';
import Dotenv from 'dotenv-webpack';
import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin';
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import { indexHtmlPath } from '@storybook/core/server';
import { version } from '../../../package.json';
import { includePaths, excludePaths, nodeModulesPaths } from './utils';
const getConfig = options => ({
mode: 'development',
devtool: '#cheap-module-eval-source-map',
entry: {
manager: [require.resolve('../../manager')],
},
output: {
path: path.join(__dirname, 'dist'),
filename: 'static/[name].bundle.js',
publicPath: '/',
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
data: {
version,
},
template: indexHtmlPath,
}),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(nodeModulesPaths),
new webpack.DefinePlugin(getEnvironment().webpack),
new Dotenv({ silent: true }),
new webpack.DefinePlugin({
storybookOptions: JSON.stringify(options),
}),
],
module: {
rules: [
{
test: /\.jsx?$/,
loader: require.resolve('babel-loader'),
query: require('./babel.js'), // eslint-disable-line
include: includePaths,
exclude: excludePaths,
},
{
test: /\.md$/,
loader: require.resolve('raw-loader'),
},
import GeneratePagePlugin from 'generate-page-webpack-plugin';
import { getManagerHeadHtml } from '@storybook/core/server';
import { includePaths, excludePaths, nodeModulesPaths } from './utils';
import { version } from '../../../package.json';
const getConfig = options => {
const entriesMeta = {
manager: {
headHtmlSnippet: getManagerHeadHtml(options.configDir, process.env),
},
};
return {
mode: 'development',
devtool: '#cheap-module-eval-source-map',
entry: {
manager: [require.resolve('../../manager')],
},
output: {
path: path.join(__dirname, 'dist'),
filename: 'static/[name].bundle.js',
publicPath: '/',
},
plugins: [
new GeneratePagePlugin(
{
template: require.resolve('@storybook/core/dist/server/templates/index.html.ejs'),
// eslint-disable-next-line global-require
parser: require('ejs'),
filename: entry => (entry === 'manager' ? 'index' : entry),
},
{
data: { version },
headHtmlSnippet: entry =>
entriesMeta[entry] ? entriesMeta[entry].headHtmlSnippet : null,
bodyHtmlSnippet: entry =>
entriesMeta[entry] ? entriesMeta[entry].bodyHtmlSnippet : null,
}
),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(nodeModulesPaths),
new webpack.DefinePlugin(getEnvironment().webpack),
new Dotenv({ silent: true }),
new webpack.DefinePlugin({
storybookOptions: JSON.stringify(options),
}),
],
},
});
module: {
rules: [
{
test: /\.jsx?$/,
loader: require.resolve('babel-loader'),
query: require('./babel.js'), // eslint-disable-line
include: includePaths,
exclude: excludePaths,
},
{
test: /\.md$/,
loader: require.resolve('raw-loader'),
},
],
},
};
};
export default getConfig;

View File

@ -2,8 +2,9 @@ import path from 'path';
import webpack from 'webpack';
import { getEnvironment } from 'universal-dotenv';
import Dotenv from 'dotenv-webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import { indexHtmlPath } from '@storybook/core/server';
import GeneratePagePlugin from 'generate-page-webpack-plugin';
import { getManagerHeadHtml } from '@storybook/core/dist/server/utils';
import { version } from '../../../package.json';
import { includePaths, excludePaths } from './utils';
@ -26,13 +27,18 @@ const getConfig = options => {
publicPath: '/',
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
data: {
version,
new GeneratePagePlugin(
{
template: require.resolve('@storybook/core/dist/server/templates/index.html.ejs'),
// eslint-disable-next-line global-require
parser: require('ejs'),
filename: entry => (entry === 'manager' ? 'index' : entry),
},
template: indexHtmlPath,
}),
{
data: { version },
headHtmlSnippet: getManagerHeadHtml(options.configDir, process.env),
}
),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"',
storybookOptions: JSON.stringify(options),

View File

@ -20,7 +20,8 @@ function getMiddleware(configDir) {
return () => {};
}
export default function({ projectDir, configDir, ...options }) {
export default function(options) {
const { projectDir, configDir } = options;
// Build the webpack configuration using the `baseConfig`
// custom `.babelrc` file and `webpack.config.js` files
const environment = options.environment || 'DEVELOPMENT';

2
app/svelte/.npmignore Normal file
View File

@ -0,0 +1,2 @@
docs
.babelrc

33
app/svelte/README.md Normal file
View File

@ -0,0 +1,33 @@
# 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/storybooks/storybook/blob/master/media/demo.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
npm i -g @storybook/cli
cd my-svelte-app
getstorybook
```
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
app/svelte/bin/build.js Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env node
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
require('../dist/server/build');

3
app/svelte/bin/index.js Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env node
require('../dist/server');

35
app/svelte/package.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "@storybook/svelte",
"version": "4.0.0-alpha.16",
"description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/svelte",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"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"
},
"dependencies": {
"@storybook/core": "4.0.0-alpha.16",
"common-tags": "^1.8.0",
"global": "^4.3.2",
"react": "^16.4.0",
"react-dom": "^16.4.0"
},
"devDependencies": {
"svelte": "^2.7.2",
"svelte-loader": "^2.9.1"
}
}

View File

@ -0,0 +1,9 @@
export {
storiesOf,
setAddon,
addDecorator,
addParameters,
configure,
getStorybook,
forceReRender,
} from './preview';

View File

@ -0,0 +1,3 @@
import { window } from 'global';
window.STORYBOOK_ENV = 'svelte';

View File

@ -0,0 +1,18 @@
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,
} = clientApi;
export const { configure } = configApi;
export { forceReRender };

View File

@ -0,0 +1,85 @@
import { document } from 'global';
import { stripIndents } from 'common-tags';
let previousComponent = null;
function cleanUpPreviousStory() {
if (!previousComponent) {
return;
}
previousComponent.destroy();
previousComponent = null;
}
function mountView({ Component, target, data, on, Wrapper, WrapperData }) {
let component;
if (Wrapper) {
const fragment = document.createDocumentFragment();
component = new Component({ target: fragment, data });
const wrapper = new Wrapper({
target,
slots: { default: fragment },
data: WrapperData || {},
});
component.on('destroy', () => {
wrapper.destroy(true);
});
} else {
component = new Component({ target, data });
}
if (on) {
// Attach svelte event listeners.
Object.keys(on).forEach(eventName => {
component.on(eventName, on[eventName]);
});
}
previousComponent = component;
}
export default function render({
story,
selectedKind,
selectedStory,
showMain,
showError,
// showException,
}) {
const {
/** @type {SvelteComponent} */
Component,
/** @type {any} */
data,
/** @type {{[string]: () => {}}} Attach svelte event handlers */
on,
Wrapper,
WrapperData,
} = story();
cleanUpPreviousStory();
if (!Component) {
showError({
title: `Expecting a Svelte component from the story: "${selectedStory}" of "${selectedKind}".`,
description: stripIndents`
Did you forget to return the Svelte component configuration from the story?
Use "() => ({ Component: YourComponent, data: {} })"
when defining the story.
`,
});
return;
}
const target = document.getElementById('root');
target.innerHTML = '';
mountView({ Component, target, data, on, Wrapper, WrapperData });
showMain();
}

5
app/svelte/src/server/build.js Executable file
View File

@ -0,0 +1,5 @@
import { buildStatic } from '@storybook/core/server';
import options from './options';
buildStatic(options);

5
app/svelte/src/server/index.js Executable file
View File

@ -0,0 +1,5 @@
import { buildDev } from '@storybook/core/server';
import options from './options';
buildDev(options);

View File

@ -0,0 +1,8 @@
import packageJson from '../../package.json';
import wrapInitialConfig from './wrapInitialConfig';
export default {
packageJson,
wrapInitialConfig,
};

View File

@ -0,0 +1,19 @@
export default config => ({
...config,
module: {
...config.module,
rules: [
...config.module.rules,
{
test: /\.(svelte|html)$/,
loader: require.resolve('svelte-loader'),
options: {},
},
],
},
resolve: {
...config.resolve,
extensions: [...config.resolve.extensions, '.svelte'],
alias: config.resolve.alias,
},
});

View File

@ -0,0 +1,158 @@
---
id: 'guide-svelte'
title: 'Storybook for Svelte'
---
You may have tried to use our quick start guide to setup your project for Storybook. If you want to set up Storybook manually, this is the guide for you.
> This will also help you understand how Storybook works.
## Starter Guide Svelte
Storybook has its own Webpack setup and a dev server.
The Webpack setup is very similar to [Svelte CLI's](https://github.com/sveltejs/svelte-cli), but allows you to [configure it however you want](/configurations/custom-webpack-config/).
In this guide, we are trying to set up Storybook for your Svelte project.
> It is very important to remember that Svelte components are precompiled from
.svelte or .html files to vanilla javascript, so there is no 'runtime'.
## Table of contents
- [Add @storybook/svelte](#add-storybooksvelte)
- [Create the NPM script](#create-the-npm-script)
- [Create the config file](#create-the-config-file)
- [Write your stories](#write-your-stories)
- [Run your Storybook](#run-your-storybook)
## Add @storybook/svelte
First of all, you need to add `@storybook/svelte` to your project. To do that, simply run:
```sh
yarn add @storybook/svelte --dev
```
## Create the NPM script
Add the following NPM script to your `package.json` in order to start the storybook later in this guide:
```json
{
"scripts": {
"storybook": "start-storybook -p 9001 -c .storybook"
}
}
```
Those flags mean port (-p) 9001 and configuration (-c) located in the .storybook directory.
## Create the config file
Storybook can be configured in several different ways.
Thats why we need a config directory. We've added a `-c` option to the above NPM script mentioning `.storybook` as the config directory.
Here's an example `.storybook/config.js` to get you started:
```js
import { configure } from '@storybook/svelte';
function loadStories() {
// You can require as many stories as you need.
require('../src/stories');
}
configure(loadStories, module);
```
> This stories folder is just an example, you can load stories from wherever you want to.
> We think stories are best located close to the source files.
## Write your stories
Now you can write some stories inside the `../stories/index.js` file, like this:
```js
// Story about MyButton
import { storiesOf } from '@storybook/svelte';
import MyButton from '../components/MyButton.svelte';
storiesOf('MyButton', module)
.add('simple component example', () => ({
Component: MyButton,
data: {
rounded: true
},
on: {
click: event => {
console.log('clicked', event);
}
}
}));
```
Svelte storybooks don't support using templates in a story yet. Instead,
you can create a .svelte file to compose components together, or simply to access
all normal Svelte functionality, like slots.
So you can create a story "view" file, essentially just a .svelte file to load
your components into to test.
```html
<!-- MyButtonView -->
<MyButton rounded="{rounded}" on:click>
{buttonText}
</Button>
```
In this example, the `on:click` that is heard on the `MyButton` component is
simply passed up to the containing component `MyButtonView` using the svelte
shorthand. It's the equivalent to `on:click="fire('click', event)"`, but it's
worth knowing about especially in this "component wrapper" scenario.
> If your component doesn't use slots, you don't need to do this, but if it does
or some other svelte functionality that requires the component to exist in a svelte
view, then this is how to do that.
You would then write a story for this "view" the exact same way you did with a component.
```js
// MyButtonView
import { storiesOf } from '@storybook/svelte';
import MyButtonView from '../views/MyButtonView.svelte';
storiesOf('MyButtonView', module)
.add('wrapped component(s) example', () => ({
Components: MyButtonView,
data: {
buttonText: 'Some button text',
rounded: true
},
on: {
click: (event) => {
console.log('clicked', event);
}
}
}));
```
Each story represents a single state of your component.
## Run your Storybook
Now everything is ready. Simply run your storybook with:
```sh
yarn storybook
```
Now you can change components and write stories whenever you need to.
You'll get those changes into Storybook in a snap with the help of Webpack's HMR API.

View File

@ -13,6 +13,7 @@ title: 'Live Examples'
- [Mithril](https://storybooks-mithril.netlify.com/)
- [Marko](https://storybooks-marko.netlify.com/)
- [HTML](https://storybooks-html.netlify.com/)
- [Svelte](https://storybooks-svelte.netlify.com/)
### 3.4
- [React Official](https://release-3-4--storybooks-official.netlify.com)

View File

@ -4,7 +4,7 @@ title: 'Quick Start Guide'
---
Storybook supports many different frontend frameworks with more coming!
React, Vue, Angular, Mithril, Marko, and HTML are currently supported. Follow these steps to get started with Storybook.
React, Vue, Angular, Mithril, Marko, HTML and Svelte are currently supported. Follow these steps to get started with Storybook.
Get started using the automated command line tool. This command adds a set of boilerplate files for Storybook in your project:
```sh
@ -19,6 +19,7 @@ To install storybook for HTML, add `--html` argument:
getstorybook --html
```
Start Storybook with:
```sh
npm run storybook
@ -35,6 +36,6 @@ To learn more about what `getstorybook` command does, have a look at the slow st
* [Mithril](/basics/guide-mithril/)
* [Marko](/basics/guide-marko/)
* [HTML](/basics/guide-html/)
* [Svelte](/basics/guide-svelte/)
A tutorial is also available at [Learn Storybook](https://www.learnstorybook.com) for a step-by-step guide (only available for React).

View File

@ -11,3 +11,4 @@ Storybook supports multiple UI libraries. The manual setup for each framework is
- [Storybook for Mithril](/basics/guide-mithril/)
- [Storybook for Marko](/basics/guide-marko/)
- [Storybook for HTML](/basics/guide-html/)
- [Storybook for Svelte](/basics/guide-svelte/)

View File

@ -5,9 +5,8 @@ title: 'Custom Webpack Config'
## Default mode
The default Webpack config of Storybook is balanced for a medium-size project (specially created with [Create React App](https://github.com/facebookincubator/create-react-app)) or a library. But if you already have your own Webpack setup, that's not useable.
That's why we allow you to customize our Webpack setup by providing a `webpack.config.js` file exporting a **webpack 2** compatible config exported as a **commonjs module**.
That's why we allow you to customize our webpack setup by providing a `webpack.config.js` file exporting a **webpack 2** compatible config exported as a **commonjs module**.
There are a few ways to do it:
@ -38,11 +37,11 @@ Since this config file stays in the Storybook directory, you need to set the inc
You also need to install the loaders (style, css, sass, as well as node-sass) used in above config manually.
> Once you create this `webpack.config.js` file, Storybook won't load the [default Webpack config](/configurations/default-config/) other than loading JS files with the Babel loader. This will disable included functionality like svg loading. Read on to learn how to [retain defaults](#full-control-mode--default).
> Once you create this `webpack.config.js` file, Storybook won't load the [default webpack config](/configurations/default-config/) other than loading JS files with the Babel loader. This will disable included functionality like svg loading. Read on to learn how to [retain defaults](#full-control-mode--default).
### Supported Webpack Options
You can add any kind of Webpack configuration options with the above config, whether they are plugins, loaders, or aliases.
You can add any kind of webpack configuration options with the above config, whether they are plugins, loaders, or aliases.
But you won't be able to change the following config options:
* entry
@ -117,9 +116,9 @@ For full instructions on Typescript setup, check [our dedicated Typescript page]
## Using Your Existing Config
You may have an existing Webpack config for your project. So, you may need to copy and paste some config items into Storybook's custom Webpack config file.
You may have an existing webpack config for your project. So, you may need to copy and paste some config items into Storybook's custom webpack config file.
But you don't need to. There are a few options:
* Import your main Webpack config into Storybook's `webpack.config.js` and use the loaders and plugins used in that.
* Create a new file with common Webpack options and use it in both inside the main Webpack config and inside Storybook's `webpack.config.js`.
* Import your main webpack config into Storybook's `webpack.config.js` and use the loaders and plugins used in that.
* Create a new file with common webpack options and use it in both inside the main webpack config and inside Storybook's `webpack.config.js`.

View File

@ -0,0 +1 @@
../../svelte-kitchen-sink/storybook-static

View File

@ -47,3 +47,11 @@ exports[`Storyshots App|acceptance vue-kitchen-sink 1`] = `
title="vue-kitchen-sink"
/>
`;
exports[`Storyshots App|acceptance svelte-kitchen-sink 1`] = `
<iframe
src="svelte-kitchen-sink/index.html"
style="border:0;position:absolute;top:0;left:0;width:100vw;height:100vh"
title="svelte-kitchen-sink"
/>
`;

View File

@ -19,6 +19,7 @@ const style = {
[
'cra-kitchen-sink',
'vue-kitchen-sink',
'svelte-kitchen-sink',
'angular-cli',
'polymer-cli',
'mithril-kitchen-sink',

16
examples/svelte-kitchen-sink/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# testing
coverage
# production
build
# misc
.DS_Store
.env
npm-debug.log
yarn-error.log

View File

@ -0,0 +1,8 @@
import '@storybook/addon-storysource/register';
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
import '@storybook/addon-notes/register';
import '@storybook/addon-knobs/register';
import '@storybook/addon-backgrounds/register';
import '@storybook/addon-viewport/register';
import '@storybook/addon-options/register';

View File

@ -0,0 +1,15 @@
import { configure } from '@storybook/svelte';
import { setOptions } from '@storybook/addon-options';
// Used with @storybook/addon-options/register
setOptions({ hierarchyRootSeparator: /\|/ });
function loadStories() {
require('../src/stories');
const req = require.context('../src/stories', true, /\.stories\.js$/);
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);

View File

@ -0,0 +1,12 @@
const path = require('path');
module.exports = (storybookBaseConfig, configType, defaultConfig) => {
defaultConfig.module.rules.push({
test: [/\.stories\.js$/, /index\.js$/],
loaders: [require.resolve('@storybook/addon-storysource/loader')],
include: [path.resolve(__dirname, '../src')],
enforce: 'pre',
});
return defaultConfig;
};

View File

@ -0,0 +1,5 @@
# Storybook Svelte Demo
This example directory represents the application you wish to integrate storybook into.
Run `yarn install` to sync Storybook module with the source code and run `yarn storybook` to start the Storybook.

View File

@ -0,0 +1,26 @@
{
"name": "svelte-example",
"version": "4.0.0-alpha.16",
"private": true,
"scripts": {
"build-storybook": "build-storybook -s public",
"storybook": "start-storybook -p 9009 -s public"
},
"dependencies": {
"global": "^4.3.2"
},
"devDependencies": {
"@storybook/addon-actions": "4.0.0-alpha.16",
"@storybook/addon-backgrounds": "4.0.0-alpha.16",
"@storybook/addon-centered": "4.0.0-alpha.16",
"@storybook/addon-knobs": "4.0.0-alpha.16",
"@storybook/addon-links": "4.0.0-alpha.16",
"@storybook/addon-notes": "4.0.0-alpha.16",
"@storybook/addon-options": "4.0.0-alpha.16",
"@storybook/addon-storyshots": "4.0.0-alpha.16",
"@storybook/addon-storysource": "4.0.0-alpha.16",
"@storybook/addon-viewport": "4.0.0-alpha.16",
"@storybook/addons": "4.0.0-alpha.16",
"@storybook/svelte": "4.0.0-alpha.16"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tag above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `yarn build`.
-->
<title>Svelte App</title>
</head>
<body>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `yarn storybook`.
To create a production bundle, use `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,48 @@
<button
class="button {roundedClass}"
on:click="onClick(event)">
<strong>{rounded ? 'Round' : 'Square'} corners</strong><br>
{text}
<slot></slot>
</button>
<style>
.rounded {
border-radius: 35px;
}
.button {
border: 3px solid;
padding: 10px 20px;
background-color: white;
outline: none;
}
</style>
<script>
export default {
data () {
return {
count: 0,
text: '', // component supports both <slot> and text prop.
rounded: true
};
},
computed: {
roundedClass({ rounded }) {
return rounded ? 'rounded' : ''
}
},
methods: {
onClick(event) {
const {rounded} = this.get();
this.set({rounded: !rounded});
this.fire('click', event)
}
}
}
</script>

View File

@ -0,0 +1,24 @@
import { document } from 'global';
import Button from './Button.svelte';
let target;
let component;
describe('Button Component', () => {
beforeEach(() => {
target = document.createElement('div');
component = new Button({ target });
});
it('should render `text` property', () => {
const text = 'Hello world';
const expected = `Round corners\n ${text}`;
component.set({ text });
const componentText = target.firstChild.textContent.trim();
expect(componentText).toEqual(expected);
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Addon|Actions Action on component method 1`] = `
<section
class="storybook-snapshot-container"
>
<button
class="button rounded svelte-n2tx93"
>
<strong>
Round
corners
</strong>
<br />
Custom text
</button>
</section>
`;
exports[`Storyshots Addon|Actions Action on view method 1`] = `
<section
class="storybook-snapshot-container"
>
<h1>
Button view
</h1>
<button
class="button svelte-n2tx93"
>
<strong>
Square
corners
</strong>
<br />
<!---->
You clicked
:
0
</button>
<p>
A little text to show this is a view.
</p>
<p>
If we need to test components in a Svelte environment, for instance to test slot behaviour,
</p>
<p>
then wrapping the component up in a view
</p>
<p>
made just for the story is the simplest way to achieve this.
</p>
</section>
`;

View File

@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Addon|Backgrounds story 1 1`] = `
<section
class="storybook-snapshot-container"
>
<h1>
Button view
</h1>
<button
class="button svelte-n2tx93"
>
<strong>
Square
corners
</strong>
<br />
<!---->
You clicked
:
0
</button>
<p>
A little text to show this is a view.
</p>
<p>
If we need to test components in a Svelte environment, for instance to test slot behaviour,
</p>
<p>
then wrapping the component up in a view
</p>
<p>
made just for the story is the simplest way to achieve this.
</p>
</section>
`;

View File

@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Addon|Centered rounded 1`] = `
<section
class="storybook-snapshot-container"
>
<button
class="button rounded svelte-n2tx93"
>
<strong>
Round
corners
</strong>
<br />
Look, I'm centered!
</button>
</section>
`;
exports[`Storyshots Addon|Centered with action 1`] = `
<section
class="storybook-snapshot-container"
>
<button
class="button rounded svelte-n2tx93"
>
<strong>
Round
corners
</strong>
<br />
</button>
</section>
`;

View File

@ -0,0 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Addon|Knobs Simple 1`] = `
<section
class="storybook-snapshot-container"
>
<div
style="width: 200px; height: 200px; background-color: green;"
>
<p>
I am interactive
</p>
</div>
</section>
`;

View File

@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Addon|Links Go to welcome view 1`] = `
<section
class="storybook-snapshot-container"
>
<div
class="main"
>
<h1>
Link Action
</h1>
<button
class="link"
>
Return to the
<code
class="code"
>
Welcome
</code>
view story.
</button>
</div>
</section>
`;

View File

@ -0,0 +1,101 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Addon|Notes Note with HTML 1`] = `
<section
class="storybook-snapshot-container"
>
<h1>
Button view
</h1>
<button
class="button svelte-n2tx93"
>
<strong>
Square
corners
</strong>
<br />
<!---->
🤔😳😯😮😄😩😓😱🤓😑😶😊
:
0
</button>
<p>
A little text to show this is a view.
</p>
<p>
If we need to test components in a Svelte environment, for instance to test slot behaviour,
</p>
<p>
then wrapping the component up in a view
</p>
<p>
made just for the story is the simplest way to achieve this.
</p>
</section>
`;
exports[`Storyshots Addon|Notes Simple note 1`] = `
<section
class="storybook-snapshot-container"
>
<h1>
Button view
</h1>
<button
class="button svelte-n2tx93"
>
<strong>
Square
corners
</strong>
<br />
<!---->
You clicked
:
0
</button>
<p>
A little text to show this is a view.
</p>
<p>
If we need to test components in a Svelte environment, for instance to test slot behaviour,
</p>
<p>
then wrapping the component up in a view
</p>
<p>
made just for the story is the simplest way to achieve this.
</p>
</section>
`;

View File

@ -0,0 +1,211 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Button rounded 1`] = `
<section
class="storybook-snapshot-container"
>
<h1>
Button view
</h1>
<button
class="button rounded svelte-n2tx93"
>
<strong>
Round
corners
</strong>
<br />
<!---->
You clicked
:
0
</button>
<p>
A little text to show this is a view.
</p>
<p>
If we need to test components in a Svelte environment, for instance to test slot behaviour,
</p>
<p>
then wrapping the component up in a view
</p>
<p>
made just for the story is the simplest way to achieve this.
</p>
</section>
`;
exports[`Storyshots Button square 1`] = `
<section
class="storybook-snapshot-container"
>
<h1>
Button view
</h1>
<button
class="button svelte-n2tx93"
>
<strong>
Square
corners
</strong>
<br />
<!---->
You clicked
:
0
</button>
<p>
A little text to show this is a view.
</p>
<p>
If we need to test components in a Svelte environment, for instance to test slot behaviour,
</p>
<p>
then wrapping the component up in a view
</p>
<p>
made just for the story is the simplest way to achieve this.
</p>
</section>
`;
exports[`Storyshots Welcome Welcome 1`] = `
<section
class="storybook-snapshot-container"
>
<div
class="main svelte-1unvhkg"
>
<h1>
Welcome to Storybook for Svelte
</h1>
<p>
This is a UI component dev environment for your svelte app.
</p>
<p>
We've added some basic stories inside the
<code
class="code svelte-1unvhkg"
>
src/stories
</code>
directory.
<strong>
A story is like a visual test case
</strong>
and represents a single state of one or more UI components.
You can have as many stories as you want.
</p>
<h1
class="logo svelte-1unvhkg"
>
Svelte
</h1>
<p>
Just like that, you can add your own components as stories.
You can also edit those components and see changes right away.
<br />
(Try editing the
<code
class="code svelte-1unvhkg"
>
Button
</code>
component
located at
<code
class="code svelte-1unvhkg"
>
src/stories/views/Welcome.svelte
</code>
.)
</p>
<p>
Usually we create stories with smaller UI components in the app.
<br />
Have a look at the
<a
class="link svelte-1unvhkg"
href="https://storybook.js.org/basics/writing-stories"
target="_blank"
>
Writing Stories
</a>
section in our documentation.
</p>
<p
class="note svelte-1unvhkg"
>
<b>
NOTE:
</b>
<br />
Have a look at the
<code
class="code svelte-1unvhkg"
>
.storybook/webpack.config.js
</code>
to add webpack
loaders and plugins you are using in this project.
</p>
</div>
</section>
`;

View File

@ -0,0 +1,22 @@
import { storiesOf } from '@storybook/svelte';
import { action } from '@storybook/addon-actions';
import ButtonView from './views/ButtonView.svelte';
import Button from '../components/Button.svelte';
storiesOf('Addon|Actions', module)
.add('Action on view method', () => ({
Component: ButtonView,
on: {
click: action('I am logging in the actions tab'),
},
}))
.add('Action on component method', () => ({
Component: Button,
data: {
text: 'Custom text',
},
on: {
click: action('I am logging in the actions tab too'),
},
}));

View File

@ -0,0 +1,15 @@
import { storiesOf } from '@storybook/svelte';
import { withBackgrounds } from '@storybook/addon-backgrounds';
import ButtonView from './views/ButtonView.svelte';
storiesOf('Addon|Backgrounds', module)
.addDecorator(
withBackgrounds([
{ name: 'twitter', value: '#00aced' },
{ name: 'facebook', value: '#3b5998', default: true },
])
)
.add('story 1', () => ({
Component: ButtonView,
}));

View File

@ -0,0 +1,21 @@
import { storiesOf } from '@storybook/svelte';
import Centered from '@storybook/addon-centered/svelte';
import { action } from '@storybook/addon-actions';
import Button from '../components/Button.svelte';
storiesOf('Addon|Centered', module)
.addDecorator(Centered)
.add('rounded', () => ({
Component: Button,
data: {
rounded: true,
text: "Look, I'm centered!",
},
}))
.add('with action', () => ({
Component: Button,
on: {
click: action(`Tell me it ain't so! Centered and with actions! Thanks @ekhaled :)`),
},
}));

View File

@ -0,0 +1,32 @@
import { storiesOf } from '@storybook/svelte';
import { withKnobs, text, number } from '@storybook/addon-knobs';
import ActionKnobView from './views/ActionKnobView.svelte';
storiesOf('Addon|Knobs', module)
.addDecorator(withKnobs)
.add('Simple', () => {
const backgroundColor = text('Background', 'green');
const width = number('Width', 200, {
range: true,
min: 100,
max: 1000,
step: 100,
});
const height = number('Height', 200, {
range: true,
min: 100,
max: 1000,
step: 100,
});
return {
Component: ActionKnobView,
data: {
backgroundColor,
width,
height,
},
};
});

View File

@ -0,0 +1,11 @@
import { storiesOf } from '@storybook/svelte';
import { linkTo } from '@storybook/addon-links';
import ActionLinkView from './views/ActionLinkView.svelte';
storiesOf('Addon|Links', module).add('Go to welcome view', () => ({
Component: ActionLinkView,
on: {
click: linkTo('Welcome'),
},
}));

View File

@ -0,0 +1,32 @@
import { storiesOf } from '@storybook/svelte';
import { withNotes } from '@storybook/addon-notes';
import ButtonView from './views/ButtonView.svelte';
storiesOf('Addon|Notes', module)
.addDecorator(withNotes)
.add(
'Simple note',
() => ({
Component: ButtonView,
}),
{ notes: 'My notes on the ButtonView component' }
)
.add(
'Note with HTML',
() => ({
Component: ButtonView,
data: {
text: '🤔😳😯😮😄😩😓😱🤓😑😶😊',
},
}),
{
notes: `
<h2>My notes on emojies</h2>
<em>It's not all that important to be honest, but..</em>
Emojis are great, I love emojis, in fact I like using them in my Component notes too! 😇
`,
}
);

View File

@ -0,0 +1,24 @@
import { storiesOf } from '@storybook/svelte';
import WelcomeView from './views/WelcomeView.svelte';
import ButtonView from './views/ButtonView.svelte';
storiesOf('Welcome', module).add('Welcome', () => ({
Component: WelcomeView,
}));
storiesOf('Button', module)
.add('rounded', () => ({
Component: ButtonView,
data: {
rounded: true,
message: 'Rounded text',
},
}))
.add('square', () => ({
Component: ButtonView,
data: {
rounded: false,
message: 'Squared text',
},
}));

View File

@ -0,0 +1,15 @@
<div style="width: {width}px; height: {height}px; background-color: {backgroundColor}">
<p>I am interactive</p>
</div>
<script>
export default {
data() {
return {
width: 200,
height: 100,
backgroundColor: 'yellow'
};
}
};
</script>

View File

@ -0,0 +1,20 @@
<div class="main">
<h1>Link Action</h1>
<button on:click="onClick(event)" class="link">
Return to the <code class="code">Welcome</code> view story.
</button>
</div>
<style>
</style>
<script>
export default {
methods: {
onClick(event) {
this.fire('click', event);
},
}
};
</script>

View File

@ -0,0 +1,39 @@
<h1>Button view</h1>
<Button {rounded} on:click="handleClick(event)">{text}: {count}</Button>
<p>A little text to show this is a view.</p>
<p>If we need to test components in a Svelte environment, for instance to test slot behaviour,</p>
<p>then wrapping the component up in a view</p>
<p>made just for the story is the simplest way to achieve this.</p>
<script>
import Button from '../../components/Button.svelte';
export default {
data() {
return {
count: 0,
text: 'You clicked'
};
},
methods: {
handleClick(event) {
this.incrementCount();
this.fire('click', { event });
},
incrementCount() {
let {count} = this.get();
count += 1;
this.set({count})
}
},
components: {
Button
}
};
</script>

View File

@ -0,0 +1,83 @@
<div class="main">
<h1>Welcome to Storybook for Svelte</h1>
<p>
This is a UI component dev environment for your svelte app.
</p>
<p>
We've added some basic stories inside the <code class="code">src/stories</code> directory.
<strong>A story is like a visual test case</strong>
and represents a single state of one or more UI components.
You can have as many stories as you want.
</p>
<h1 class="logo">Svelte</h1>
<p>
Just like that, you can add your own components as stories.
You can also edit those components and see changes right away.
<br />
(Try editing the <code class="code">Button</code> component
located at <code class="code">src/stories/views/Welcome.svelte</code>.)
</p>
<p>
Usually we create stories with smaller UI components in the app.<br />
Have a look at the
<a
class="link"
href="https://storybook.js.org/basics/writing-stories"
target="_blank"
>
Writing Stories
</a>
section in our documentation.
</p>
<p class="note">
<b>NOTE:</b>
<br />
Have a look at the
<code class="code">.storybook/webpack.config.js</code>
to add webpack
loaders and plugins you are using in this project.
</p>
</div>
<style>
@import url('https://fonts.googleapis.com/css?family=Rajdhani');
.main {
margin: 15px;
max-width: 600;
line-height: 1.4;
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
}
.logo {
font-family: 'Rajdhani', sans-serif;
color: rgb(170,30,30);
font-weight: 300;
font-size: 8rem;
text-align: center;
margin: 0;
}
.link {
color: #1474f3;
text-decoration: none;
border-bottom: 1px solid #1474f3;
padding-bottom: 2px;
}
.code {
font-size: 15;
font-weight: 600;
padding: 2px 5px;
border: 1px solid #eae9e9;
border-radius: 4px;
background-color: #f3f2f2;
color: #3a3a3a;
}
.note {
opacity: 0.5;
}
</style>

View File

@ -0,0 +1,9 @@
import path from 'path';
import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots';
initStoryshots({
framework: 'svelte',
configPath: path.join(__dirname, '.storybook'),
integrityOptions: { cwd: path.join(__dirname, 'src', 'stories') },
test: multiSnapshotWithOptions(),
});

View File

@ -16,6 +16,7 @@ module.exports = {
'<rootDir>/lib',
'<rootDir>/examples/cra-kitchen-sink',
'<rootDir>/examples/vue-kitchen-sink',
'<rootDir>/examples/svelte-kitchen-sink',
'<rootDir>/examples/html-kitchen-sink',
'<rootDir>/examples/official-storybook',
'<rootDir>/examples/angular-cli',
@ -25,6 +26,7 @@ module.exports = {
'^.+[/\\\\].storybook[/\\\\]config\\.ts$': '<rootDir>/scripts/jest-ts-babel.js',
'^.+\\.(ts|html)$': '<rootDir>/node_modules/jest-preset-angular/preprocessor.js',
'.*\\.(vue)$': '<rootDir>/node_modules/jest-vue-preprocessor',
'.*\\.(svelte)$': '<rootDir>/node_modules/svelte-jest',
},
testPathIgnorePatterns: ['/node_modules/', 'addon-jest.test.js', '/cli/test/'],
collectCoverage: false,
@ -41,5 +43,5 @@ module.exports = {
setupTestFrameworkScriptFile: './scripts/jest.init.js',
setupFiles: ['raf/polyfill'],
testURL: 'http://localhost',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node', '.html', 'vue'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node', '.html', '.svelte', 'vue'],
};

View File

@ -38,12 +38,13 @@
"core-js": "^2.5.7",
"css-loader": "^1.0.0",
"dotenv-webpack": "^1.5.7",
"ejs": "^2.6.1",
"emotion": "^9.2.6",
"express": "^4.16.3",
"file-loader": "^1.1.11",
"find-cache-dir": "^2.0.0",
"generate-page-webpack-plugin": "^1.0.0",
"global": "^4.3.2",
"html-webpack-plugin": "^3.2.0",
"interpret": "^1.1.0",
"json5": "^1.0.1",
"postcss-flexbugs-fixes": "^4.1.0",

View File

@ -4,7 +4,4 @@ const serverUtils = require('./dist/server/utils');
const buildStatic = require('./dist/server/build-static');
const buildDev = require('./dist/server/build-dev');
module.exports = assign({}, defaultWebpackConfig, buildStatic, buildDev, serverUtils, {
indexHtmlPath: require.resolve('./src/server/index.html.ejs'),
iframeHtmlPath: require.resolve('./src/server/iframe.html.ejs'),
});
module.exports = assign({}, defaultWebpackConfig, buildStatic, buildDev, serverUtils);

View File

@ -0,0 +1,114 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`mergeConfigs merges partial custom config 1`] = `
Object {
"devtool": "source-maps",
"entry": Object {
"bundle": "index.js",
},
"module": Object {
"rules": Array [
"r1",
"r2",
],
},
"output": Object {
"filename": "[name].js",
},
"plugins": Array [
"p1",
"p2",
"p3",
],
"resolve": Object {
"alias": Object {
"A1": "src/B1",
"A2": "src/B2",
},
"enforceModuleExtension": true,
"extensions": Array [
".js",
".json",
".ts",
".tsx",
],
},
}
`;
exports[`mergeConfigs merges successfully if custom config is empty 1`] = `
Object {
"devtool": "source-maps",
"entry": Object {
"bundle": "index.js",
},
"module": Object {
"rules": Array [
"r1",
"r2",
],
},
"output": Object {
"filename": "[name].js",
},
"plugins": Array [
"p1",
"p2",
],
"resolve": Object {
"alias": Object {
"A1": "src/B1",
"A2": "src/B2",
},
"enforceModuleExtension": true,
"extensions": Array [
".js",
".json",
],
},
}
`;
exports[`mergeConfigs merges two full configs in one 1`] = `
Object {
"devtool": "source-maps",
"entry": Object {
"bundle": "index.js",
},
"module": Object {
"noParse": /jquery\\|lodash/,
"rules": Array [
"r1",
"r2",
"r3",
"r4",
],
},
"output": Object {
"filename": "[name].js",
},
"plugins": Array [
"p1",
"p2",
"p3",
"p4",
],
"profile": true,
"resolve": Object {
"alias": Object {
"A1": "src/B1",
"A2": "src/B2",
"A3": "src/B3",
"A4": "src/B4",
},
"enforceExtension": false,
"enforceModuleExtension": true,
"extensions": Array [
".js",
".json",
".ts",
".tsx",
],
},
}
`;

View File

@ -4,6 +4,7 @@ import { createDefaultWebpackConfig } from './config/defaults/webpack.config';
import devBabelConfig from './config/babel';
import loadCustomBabelConfig from './loadCustomBabelConfig';
import loadCustomWebpackConfig from './loadCustomWebpackConfig';
import mergeConfigs from './mergeConfigs';
const noopWrapper = config => config;
@ -17,36 +18,6 @@ function getBabelConfig({
return wrapBabelConfig(loadCustomBabelConfig(configDir, defaultConfig));
}
function mergeConfigs(config, customConfig) {
return {
...customConfig,
// We'll always load our configurations after the custom config.
// So, we'll always load the stuff we need.
...config,
// Override with custom devtool if provided
devtool: customConfig.devtool || config.devtool,
// We need to use our and custom plugins.
plugins: [...config.plugins, ...(customConfig.plugins || [])],
module: {
...config.module,
// We need to use our and custom rules.
...customConfig.module,
rules: [
...config.module.rules,
...((customConfig.module && customConfig.module.rules) || []),
],
},
resolve: {
...config.resolve,
...customConfig.resolve,
alias: {
...config.alias,
...(customConfig.resolve && customConfig.resolve.alias),
},
},
};
}
function informAboutCustomConfig(defaultConfigName) {
if (!defaultConfigName) {
logger.info('=> Using default webpack setup.');

View File

@ -37,7 +37,7 @@ export function loadEnv(options = {}) {
}
export function getEntries(configDir) {
const preview = [require.resolve('./polyfills'), require.resolve('./globals')];
const iframe = [require.resolve('./polyfills'), require.resolve('./globals')];
const manager = [require.resolve('./polyfills'), require.resolve('../../client/manager')];
// Check whether a config.{ext} file exists inside the storybook
@ -47,7 +47,7 @@ export function getEntries(configDir) {
throw new Error(`=> Create a storybook config file in "${configDir}/config.{ext}".`);
}
preview.push(require.resolve(storybookConfigPath));
iframe.push(require.resolve(storybookConfigPath));
// Check whether addons.{ext} file exists inside the storybook.
const storybookCustomAddonsPath = getInterpretedFile(path.resolve(configDir, 'addons'));
@ -56,5 +56,5 @@ export function getEntries(configDir) {
manager.unshift(storybookCustomAddonsPath);
}
return { preview, manager };
return { iframe, manager };
}

View File

@ -2,12 +2,10 @@ import path from 'path';
import webpack from 'webpack';
import { getEnvironment } from 'universal-dotenv';
import Dotenv from 'dotenv-webpack';
import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin';
import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin';
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import { getPreviewHeadHtml, getManagerHeadHtml } from '../utils';
import GeneratePagePlugin from 'generate-page-webpack-plugin';
import { getPreviewHeadHtml, getManagerHeadHtml, getPreviewBodyHtml } from '../utils';
import {
includePaths,
@ -21,13 +19,23 @@ import { version } from '../../../package.json';
export default ({ configDir, quiet, babelOptions }) => {
const entries = getEntries(configDir, true);
const entriesMeta = {
iframe: {
headHtmlSnippet: getPreviewHeadHtml(configDir, process.env),
bodyHtmlSnippet: getPreviewBodyHtml(),
},
manager: {
headHtmlSnippet: getManagerHeadHtml(configDir, process.env),
},
};
return {
mode: 'development',
devtool: 'cheap-module-source-map',
entry: {
...entries,
preview: [
...entries.preview,
iframe: [
...entries.iframe,
`${require.resolve('webpack-hot-middleware/client')}?reload=true`,
],
},
@ -42,26 +50,21 @@ export default ({ configDir, quiet, babelOptions }) => {
publicPath: '',
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
chunks: ['manager'],
chunksSortMode: 'none',
data: {
managerHead: getManagerHeadHtml(configDir),
version,
new GeneratePagePlugin(
{
template: require.resolve('../templates/index.html.ejs'),
// eslint-disable-next-line global-require
parser: require('ejs'),
filename: entry => (entry === 'manager' ? 'index' : entry),
},
template: require.resolve('../index.html.ejs'),
}),
new HtmlWebpackPlugin({
filename: 'iframe.html',
excludeChunks: ['manager'],
chunksSortMode: 'none',
data: {
previewHead: getPreviewHeadHtml(configDir),
},
template: require.resolve('../iframe.html.ejs'),
}),
new InterpolateHtmlPlugin(process.env),
{
data: { version },
headHtmlSnippet: entry =>
entriesMeta[entry] ? entriesMeta[entry].headHtmlSnippet : null,
bodyHtmlSnippet: entry =>
entriesMeta[entry] ? entriesMeta[entry].bodyHtmlSnippet : null,
}
),
new webpack.DefinePlugin(loadEnv()),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),

View File

@ -1,92 +1,100 @@
import webpack from 'webpack';
import { getEnvironment } from 'universal-dotenv';
import Dotenv from 'dotenv-webpack';
import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import GeneratePagePlugin from 'generate-page-webpack-plugin';
import { version } from '../../../package.json';
import { getPreviewHeadHtml, getManagerHeadHtml } from '../utils';
import { getPreviewHeadHtml, getManagerHeadHtml, getPreviewBodyHtml } from '../utils';
import { includePaths, excludePaths, loadEnv, nodePaths, getEntries } from './utils';
export default ({ configDir, babelOptions }) => ({
mode: 'production',
bail: true,
devtool: '#cheap-module-source-map',
entry: getEntries(configDir),
output: {
filename: 'static/[name].[chunkhash].bundle.js',
// Here we set the publicPath to ''.
// This allows us to deploy storybook into subpaths like GitHub pages.
// This works with css and image loaders too.
// This is working for storybook since, we don't use pushState urls and
// relative URLs works always.
publicPath: '',
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
chunks: ['manager', 'runtime~manager'],
chunksSortMode: 'none',
data: {
managerHead: getManagerHeadHtml(configDir),
version,
},
template: require.resolve('../index.html.ejs'),
}),
new HtmlWebpackPlugin({
filename: 'iframe.html',
excludeChunks: ['manager', 'runtime~manager'],
chunksSortMode: 'none',
data: {
previewHead: getPreviewHeadHtml(configDir),
},
template: require.resolve('../iframe.html.ejs'),
}),
new InterpolateHtmlPlugin(process.env),
new webpack.DefinePlugin(loadEnv({ production: true })),
new webpack.DefinePlugin(getEnvironment().webpack),
new Dotenv({ silent: true }),
],
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: require.resolve('babel-loader'),
options: babelOptions,
},
],
include: includePaths,
exclude: excludePaths,
},
{
test: /\.md$/,
use: [
{
loader: require.resolve('raw-loader'),
},
],
},
],
},
resolve: {
// Since we ship with json-loader always, it's better to move extensions to here
// from the default config.
extensions: ['.js', '.json'],
// Add support to NODE_PATH. With this we could avoid relative path imports.
// Based on this CRA feature: https://github.com/facebookincubator/create-react-app/issues/253
modules: ['node_modules'].concat(nodePaths),
},
optimization: {
// Automatically split vendor and commons for preview bundle
// https://twitter.com/wSokra/status/969633336732905474
splitChunks: {
chunks: chunk => chunk.name !== 'manager',
export default ({ configDir, babelOptions }) => {
const entries = getEntries(configDir);
const entriesMeta = {
iframe: {
headHtmlSnippet: getPreviewHeadHtml(configDir, process.env),
bodyHtmlSnippet: getPreviewBodyHtml(),
},
// Keep the runtime chunk seperated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985
runtimeChunk: true,
},
});
manager: {
headHtmlSnippet: getManagerHeadHtml(configDir, process.env),
},
};
return {
mode: 'production',
bail: true,
devtool: '#cheap-module-source-map',
entry: entries,
output: {
filename: 'static/[name].[chunkhash].bundle.js',
// Here we set the publicPath to ''.
// This allows us to deploy storybook into subpaths like GitHub pages.
// This works with css and image loaders too.
// This is working for storybook since, we don't use pushState urls and
// relative URLs works always.
publicPath: '',
},
plugins: [
new GeneratePagePlugin(
{
template: require.resolve('../templates/index.html.ejs'),
// eslint-disable-next-line global-require
parser: require('ejs'),
filename: entry => (entry === 'manager' ? 'index' : entry),
},
{
data: { version },
headHtmlSnippet: entry =>
entriesMeta[entry] ? entriesMeta[entry].headHtmlSnippet : null,
bodyHtmlSnippet: entry =>
entriesMeta[entry] ? entriesMeta[entry].bodyHtmlSnippet : null,
}
),
new webpack.DefinePlugin(loadEnv({ production: true })),
new webpack.DefinePlugin(getEnvironment().webpack),
new Dotenv({ silent: true }),
],
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: require.resolve('babel-loader'),
options: babelOptions,
},
],
include: includePaths,
exclude: excludePaths,
},
{
test: /\.md$/,
use: [
{
loader: require.resolve('raw-loader'),
},
],
},
],
},
resolve: {
// Since we ship with json-loader always, it's better to move extensions to here
// from the default config.
extensions: ['.js', '.json'],
// Add support to NODE_PATH. With this we could avoid relative path imports.
// Based on this CRA feature: https://github.com/facebookincubator/create-react-app/issues/253
modules: ['node_modules'].concat(nodePaths),
},
optimization: {
// Automatically split vendor and commons for preview bundle
// https://twitter.com/wSokra/status/969633336732905474
splitChunks: {
chunks: chunk => chunk.name !== 'manager',
},
// Keep the runtime chunk seperated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985
runtimeChunk: true,
},
};
};

View File

@ -1,89 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
<base target="_parent">
<title>Storybook</title>
<link rel="shortcut icon" href="favicon.ico?v=1" />
<%= htmlWebpackPlugin.options.data.previewHead %>
<style>
:not(.sb-show-main) > .sb-main,
:not(.sb-show-nopreview) > .sb-nopreview,
:not(.sb-show-errordisplay) > .sb-errordisplay {
display: none;
}
.sb-wrapper {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
font-family: -apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
-webkit-font-smoothing: antialiased;
}
.sb-heading {
font-size: 20px;
font-weight: 600;
letter-spacing: 0.2px;
margin: 10px 0;
}
.sb-nopreview {
display: flex;
align-content: center;
justify-content: center;
}
.sb-nopreview_main {
margin: auto;
padding: 30px;
border-radius: 10px;
background: rgba(0,0,0,0.03);
}
.sb-nopreview_heading {
text-align: center;
}
.sb-errordisplay {
background-color: rgb(187, 49, 49);
color: #FFF;
}
.sb-errordisplay_code {
font-size: 14px;
width: 100vw;
overflow: auto;
}
</style>
</head>
<body class="sb-show-main">
<div id="root" class="sb-main"></div>
<div class="sb-nopreview sb-wrapper">
<div class="sb-nopreview_main">
<h1 class="sb-nopreview_heading sb-heading">No Preview</h1>
<p>Sorry, but you either have no stories or none are selected somehow.</p>
<ul>
<li>Please check the storybook config.</li>
<li>Try reloading the page.</li>
</ul>
</div>
</div>
<div class="sb-errordisplay sb-wrapper">
<div id="error-message" class="sb-heading"></div>
<pre class="sb-errordisplay_code">
<code id="error-stack"></code>
</pre>
</div>
</body>
</html>

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="storybook-version" content="<%= htmlWebpackPlugin.options.data.version %>">
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
<title>Storybook</title>
<link rel="shortcut icon" href="favicon.ico?v=1" />
<%= htmlWebpackPlugin.options.data.managerHead %>
</head>
<body style="margin: 0;">
<style>
html, body {
overflow: hidden;
height: 100%;
width: 100%;
}
</style>
<div id="root"></div>
</body>
</html>

View File

@ -0,0 +1,50 @@
function plugins({ plugins: defaultPlugins = [] }, { plugins: customPlugins = [] }) {
return [...defaultPlugins, ...customPlugins];
}
function rules({ rules: defaultRules = [] }, { rules: customRules = [] }) {
return [...defaultRules, ...customRules];
}
function extensions({ extensions: defaultExtensions = [] }, { extensions: customExtensions = [] }) {
return [...defaultExtensions, ...customExtensions];
}
function alias({ alias: defaultAlias = {} }, { alias: customAlias = {} }) {
return {
...defaultAlias,
...customAlias,
};
}
function module({ module: defaultModule = {} }, { module: customModule = {} }) {
return {
...defaultModule,
...customModule,
rules: rules(defaultModule, customModule),
};
}
function resolve({ resolve: defaultResolve = {} }, { resolve: customResolve = {} }) {
return {
...defaultResolve,
...customResolve,
alias: alias(defaultResolve, customResolve),
extensions: extensions(defaultResolve, customResolve),
};
}
function mergeConfigs(config, customConfig) {
return {
// We'll always load our configurations after the custom config.
// So, we'll always load the stuff we need.
...customConfig,
...config,
devtool: customConfig.devtool || config.devtool,
plugins: plugins(config, customConfig),
module: module(config, customConfig),
resolve: resolve(config, customConfig),
};
}
export default mergeConfigs;

View File

@ -0,0 +1,75 @@
import mergeConfigs from './mergeConfigs';
const config = {
devtool: 'source-maps',
entry: {
bundle: 'index.js',
},
output: {
filename: '[name].js',
},
module: {
rules: ['r1', 'r2'],
},
plugins: ['p1', 'p2'],
resolve: {
enforceModuleExtension: true,
extensions: ['.js', '.json'],
alias: {
A1: 'src/B1',
A2: 'src/B2',
},
},
};
describe('mergeConfigs', () => {
it('merges two full configs in one', () => {
const customConfig = {
profile: true,
entry: {
bundle: 'should_not_be_merged.js',
},
output: {
filename: 'should_not_be_merged.js',
},
module: {
noParse: /jquery|lodash/,
rules: ['r3', 'r4'],
},
plugins: ['p3', 'p4'],
resolve: {
enforceExtension: false,
extensions: ['.ts', '.tsx'],
alias: {
A3: 'src/B3',
A4: 'src/B4',
},
},
};
const result = mergeConfigs(config, customConfig);
expect(result).toMatchSnapshot();
});
it('merges partial custom config', () => {
const customConfig = {
plugins: ['p3'],
resolve: {
extensions: ['.ts', '.tsx'],
},
};
const result = mergeConfigs(config, customConfig);
expect(result).toMatchSnapshot();
});
it('merges successfully if custom config is empty', () => {
const customConfig = {};
const result = mergeConfigs(config, customConfig);
expect(result).toMatchSnapshot();
});
});

View File

@ -0,0 +1,16 @@
<style>
html, body {
overflow: hidden;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
</style>
<script>
if (window.parent !== window) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
window.__VUE_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__VUE_DEVTOOLS_GLOBAL_HOOK__;
}
</script>

View File

@ -0,0 +1,17 @@
<div class="sb-nopreview sb-wrapper">
<div class="sb-nopreview_main">
<h1 class="sb-nopreview_heading sb-heading">No Preview</h1>
<p>Sorry, but you either have no stories or none are selected somehow.</p>
<ul>
<li>Please check the storybook config.</li>
<li>Try reloading the page.</li>
</ul>
</div>
</div>
<div class="sb-errordisplay sb-wrapper">
<div id="error-message" class="sb-heading"></div>
<pre class="sb-errordisplay_code">
<code id="error-stack"></code>
</pre>
</div>

View File

@ -0,0 +1,62 @@
<base target="_parent">
<style>
:not(.sb-show-main) > .sb-main,
:not(.sb-show-nopreview) > .sb-nopreview,
:not(.sb-show-errordisplay) > .sb-errordisplay {
display: none;
}
.sb-wrapper {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
font-family: -apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
-webkit-font-smoothing: antialiased;
}
.sb-heading {
font-size: 20px;
font-weight: 600;
letter-spacing: 0.2px;
margin: 10px 0;
}
.sb-nopreview {
display: flex;
align-content: center;
justify-content: center;
}
.sb-nopreview_main {
margin: auto;
padding: 30px;
border-radius: 10px;
background: rgba(0,0,0,0.03);
}
.sb-nopreview_heading {
text-align: center;
}
.sb-errordisplay {
background-color: rgb(187, 49, 49);
color: #FFF;
}
.sb-errordisplay_code {
font-size: 14px;
width: 100vw;
overflow: auto;
}
</style>
<script>
if (window.parent !== window) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
window.__VUE_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__VUE_DEVTOOLS_GLOBAL_HOOK__;
}
</script>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
<title>Storybook</title>
<link rel="shortcut icon" href="favicon.ico?v=1" />
<% if (options.links) { %>
<% for (item of options.links) { %>
<% if (typeof item === 'string' || item instanceof String) { item = { href: item, rel: 'stylesheet' } } %>
<link<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %>>
<% } %>
<% } %>
<% if (options.headHtmlSnippet) { %>
<%- options.headHtmlSnippet %>
<% } %>
</head>
<body>
<% if (options.window) { %>
<script>
<% for (key in options.window) { %>
window['<%= key %>'] = <%= JSON.stringify(options.window[key]) %>;
<% } %>
</script>
<% } %>
<% if (options.bodyHtmlSnippet) { %>
<%- options.bodyHtmlSnippet %>
<% } %>
<div id="root"></div>
<% if (options.scripts) { %>
<% for (item of options.scripts) { %>
<% if (typeof item === 'string' || item instanceof String) { item = { src: item } } %>
<script <% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %> defer></script>
<% } %>
<% } %>
<% for (key in dlls) { %>
<script src="<%= compilation.outputOptions.publicPath %><%= dlls[key] %>" defer></script>
<% } %>
<% for (index in chunks) { %>
<% for (key in chunks[index].files) { %>
<script src="<%= compilation.outputOptions.publicPath %><%= chunks[index].files[key] %>" defer></script>
<% } %>
<% } %>
</body>
</html>

View File

@ -27,22 +27,35 @@ export function getMiddleware(configDir) {
return () => {};
}
export function getPreviewHeadHtml(configDirPath) {
const interpolate = (string, data = {}) =>
Object.entries(data).reduce((acc, [k, v]) => acc.replace(`%${k}%`, v), string);
export function getPreviewBodyHtml() {
return fs.readFileSync(path.resolve(__dirname, 'templates/base-preview-body.html'), 'utf8');
}
export function getPreviewHeadHtml(configDirPath, interpolations) {
const base = fs.readFileSync(path.resolve(__dirname, 'templates/base-preview-head.html'), 'utf8');
const headHtmlPath = path.resolve(configDirPath, 'preview-head.html');
let result = base;
if (fs.existsSync(headHtmlPath)) {
return fs.readFileSync(headHtmlPath, 'utf8');
result += fs.readFileSync(headHtmlPath, 'utf8');
}
return '';
return interpolate(result, interpolations);
}
export function getManagerHeadHtml(configDirPath) {
export function getManagerHeadHtml(configDirPath, interpolations) {
const base = fs.readFileSync(path.resolve(__dirname, 'templates/base-manager-head.html'), 'utf8');
const scriptPath = path.resolve(configDirPath, 'manager-head.html');
let result = base;
if (fs.existsSync(scriptPath)) {
return fs.readFileSync(scriptPath, 'utf8');
result += fs.readFileSync(scriptPath, 'utf8');
}
return '';
return interpolate(result, interpolations);
}

View File

@ -2,11 +2,13 @@ import mock from 'mock-fs';
import { getPreviewHeadHtml } from './utils';
const HEAD_HTML_CONTENTS = '<script>console.log("custom script!");</script>';
const BASE_HTML_CONTENTS = '<script>console.log("base script!");</script>';
describe('server.getPreviewHeadHtml', () => {
describe('when .storybook/preview-head.html does not exist', () => {
beforeEach(() => {
mock({
[`${__dirname}/templates/base-preview-head.html`]: BASE_HTML_CONTENTS,
config: {},
});
});
@ -17,13 +19,14 @@ describe('server.getPreviewHeadHtml', () => {
it('return an empty string', () => {
const result = getPreviewHeadHtml('./config');
expect(result).toEqual('');
expect(result).toEqual(BASE_HTML_CONTENTS);
});
});
describe('when .storybook/preview-head.html exists', () => {
beforeEach(() => {
mock({
[`${__dirname}/templates/base-preview-head.html`]: BASE_HTML_CONTENTS,
config: {
'preview-head.html': HEAD_HTML_CONTENTS,
},
@ -36,7 +39,7 @@ describe('server.getPreviewHeadHtml', () => {
it('return the contents of the file', () => {
const result = getPreviewHeadHtml('./config');
expect(result).toEqual(HEAD_HTML_CONTENTS);
expect(result).toEqual(BASE_HTML_CONTENTS + HEAD_HTML_CONTENTS);
});
});
});

View File

@ -112,6 +112,7 @@
"remark-lint": "^6.0.2",
"remark-preset-lint-recommended": "^3.0.2",
"shelljs": "^0.8.2",
"svelte-jest": "^0.2.0",
"tslint": "~5.11.0",
"tslint-config-prettier": "^1.14.0",
"tslint-plugin-prettier": "^1.3.0",
@ -175,6 +176,7 @@
"examples/mithril-kitchen-sink",
"examples/polymer-cli",
"examples/vue-kitchen-sink",
"examples/svelte-kitchen-sink",
"examples/official-storybook",
"lib/cli/test/run/*"
]

View File

@ -2,7 +2,7 @@
#
# This builds all the example storybooks for running chromatic on your dev machine
examples=(angular-cli cra-kitchen-sink html-kitchen-sink marko-cli mithril-kitchen-sink polymer-cli vue-kitchen-sink official-storybook)
examples=(angular-cli cra-kitchen-sink html-kitchen-sink marko-cli mithril-kitchen-sink polymer-cli vue-kitchen-sink svelte-kitchen-sink official-storybook)
for example in "${examples[@]}"
do

View File

@ -28,6 +28,13 @@ elif [ "$BUILD_CONTEXT" = "VUE" ]; then
yarn build-storybook
mv storybook-static ../../netlify-build
popd
elif [ "$BUILD_CONTEXT" = "SVELTE" ]; then
echo "netlify-build Svelte examples"
pushd examples/svelte-kitchen-sink
yarn
yarn build-storybook
mv storybook-static ../../netlify-build
popd
elif [ "$BUILD_CONTEXT" = "ANGULAR" ]; then
echo "netlify-build Angular examples"
pushd examples/angular-cli

View File

@ -43,7 +43,7 @@ const createOption = ({ defaultValue, option, name, extraParam }) => ({
const tasks = {
core: createProject({
name: `Core & React & Vue & Polymer & Angular ${chalk.gray('(core)')}`,
name: `Core & React & Vue & Polymer & Angular & Svelte ${chalk.gray('(core)')}`,
defaultValue: true,
option: '--core',
projectLocation: path.join(__dirname, '..'),

View File

@ -5822,6 +5822,10 @@ ejs@^2.5.7:
version "2.5.7"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
ejs@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
ejson@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ejson/-/ejson-2.1.2.tgz#0eed4055bc7e0e7561fe59e8c320edc3ff8ce7df"
@ -6394,14 +6398,7 @@ eslint-plugin-jsx-a11y@^6.1.1:
has "^1.0.3"
jsx-ast-utils "^2.0.1"
eslint-plugin-prettier@^2.2.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.4.0.tgz#85cab0775c6d5e3344ef01e78d960f166fb93aae"
dependencies:
fast-diff "^1.1.1"
jest-docblock "^21.0.0"
eslint-plugin-prettier@^2.6.2:
eslint-plugin-prettier@^2.2.0, eslint-plugin-prettier@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.2.tgz#71998c60aedfa2141f7bfcbf9d1c459bf98b4fad"
dependencies:
@ -7584,6 +7581,10 @@ gaze@^1.0.0:
dependencies:
globule "^1.0.0"
generate-page-webpack-plugin@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/generate-page-webpack-plugin/-/generate-page-webpack-plugin-1.0.0.tgz#e6261efb7e6b9ef8a8136126b14fa26aedfb7f33"
genfun@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/genfun/-/genfun-4.0.1.tgz#ed10041f2e4a7f1b0a38466d17a5c3e27df1dfc1"
@ -14654,7 +14655,7 @@ react-docgen@^3.0.0-beta12:
node-dir "^0.1.10"
recast "^0.12.6"
react-dom@^16.4.2:
react-dom@^16.4.0, react-dom@^16.4.2:
version "16.4.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.2.tgz#4afed569689f2c561d2b8da0b819669c38a0bda4"
dependencies:
@ -14952,7 +14953,7 @@ react-transition-group@^1.1.2:
prop-types "^15.5.6"
warning "^3.0.0"
react@^16.4.2:
react@^16.4.0, react@^16.4.2:
version "16.4.2"
resolved "https://registry.yarnpkg.com/react/-/react-16.4.2.tgz#2cd90154e3a9d9dd8da2991149fdca3c260e129f"
dependencies:
@ -17306,6 +17307,25 @@ supports-hyperlinks@^1.0.1:
has-flag "^2.0.0"
supports-color "^5.0.0"
svelte-dev-helper@^1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/svelte-dev-helper/-/svelte-dev-helper-1.1.7.tgz#b7d887c7be5abf7e2436f9467560061b1e2a935f"
svelte-jest@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/svelte-jest/-/svelte-jest-0.2.0.tgz#a05ed16d092e8916de7a4526d1d5673499094756"
svelte-loader@^2.9.1:
version "2.9.1"
resolved "https://registry.yarnpkg.com/svelte-loader/-/svelte-loader-2.9.1.tgz#e493c51ac6c2fe53867b1512abf46eb991ca3205"
dependencies:
loader-utils "^1.1.0"
svelte-dev-helper "^1.1.7"
svelte@^2.7.2:
version "2.7.2"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-2.7.2.tgz#17019df4997d10206378fb45136b8cbb6edf2611"
svg-tags@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
@ -17806,14 +17826,14 @@ tsickle@^0.32.1:
source-map "^0.6.0"
source-map-support "^0.5.0"
tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac"
tslib@^1.9.0:
tslib@^1.7.1, tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
tslib@^1.8.0, tslib@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac"
tslint-config-prettier@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.14.0.tgz#860b36634e53f4c70c64c51ff3ef7fd9bbab7676"