Skip to content

Commit

Permalink
Added watchers and better static sites
Browse files Browse the repository at this point in the history
  • Loading branch information
priandsf committed Jul 14, 2020
1 parent c4a4df0 commit 0905e2c
Show file tree
Hide file tree
Showing 28 changed files with 7,787 additions and 159 deletions.
97 changes: 76 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
## What is Storybook?
According to the docuemntation, [Storybook](https://storybook.js.org/), Storybook is a user interface development environment and playground for UI components. The tool enables developers to create components independently and showcase components interactively in an isolated development environment.

![./docs/screenshot.png](./docs/screenshot.png)
The two deployed Storybook sites are available at the following URLs:
[https://lwc-essentials.github.io/storybook/](https://lwc-essentials.github.io/storybook/)


![./docs/screenshot.png](./assets/Storybook-screenshot.png)

Github repo: [https://github.com/LWC-Essentials/storybook](https://github.com/LWC-Essentials/storybook)


## About
Expand Down Expand Up @@ -98,6 +104,7 @@ Storybook is using Webpack as the application bundler. Unfortunately, LWC is gen
- The components displayed as part of a story must be custom elements.
Custom elements limit the values to be passed to the components, as HTML only allow string literals as attributes. To pass more complex values, the developer must create wrappers.


### Storybook folders
Both the library (lwc-library) and the application (lwc-app) feature 2 new directories:

Expand All @@ -107,6 +114,7 @@ Both the library (lwc-library) and the application (lwc-app) feature 2 new direc
Contain all the stories, for all the components. After several discussions, we decided to isolate the stories in their own folder rather than co-locating them with the components. It makes it easier for a developer to find the stories when working on them.
Note that this folder also contains an `index.js` file that loads and register all the components as custom elements.


### Composition
The main application is exposing the stories defined in the library through the [composition](https://medium.com/storybookjs/storybook-composition-af0da9084fba) mechanism. To enable the composition, the library must be deployed as a static site with a properly generated `stories.json` file. Then, the application can either explicitely include it (see: `main.js - refs`) or imnplicitly if the library it includes features an entry in its `package.json` (automatic loading). This sample project uses the later.

Expand All @@ -126,31 +134,90 @@ export const default_ = () => html`
`;
```


### Documenting Web Components
Documentation which is a great strengh of Storybook, can be at least partially generated. The WebComponent organization comes with a meta-data format to desbribe web components: [custom-elements.json](https://github.com/webcomponents/custom-elements-json).Warn: this format is not yet a standard and can evolve in the near future. It currently contains enough information to describe a components, its attributes and properties, events, CSS variables...

Writting or maintaining such a file manually is cumbersome, so we better generate it from the component source files. This tasks is achieved by a third party library: [web-component-analyzer](https://github.com/runem/web-component-analyzer#readme). As Javascript doesn't describe all the meta-data that we need, the source code must be enriched with [JSDoc](https://jsdoc.app/) information. In particular, it supports some web components specific tags describes [here](https://www.npmjs.com/package/web-component-analyzer#%E2%9E%A4-how-to-document-your-components-using-jsdoc).

**Note**: In order for web components to be recognized by web-component-analyzer, it currently must feature an element tag in its header, like:
In order for web components to be recognized by web-component-analyzer, it currently must feature an element tag in its header. Moreover, the attributes, properties, ... should be property taggesd as well:

```js
/**
* The simpliest, possible Hello World component.
*
* @element hello-world
* LWC Component taggeg with JSDoc comments.
* @element my-component
*/
export default class World extends LightningElement {
export default class MyComponent extends LightningElement {
/**
* name is an attribute
* @attr
*/
@api name = ''
/**
* message is a property
* @ property
*/
message = ''

...
}
```


### Story types
This sample application shows different story types.

#### Component story
A component story is defined using the [CSF](https://storybook.js.org/docs/formats/component-story-format/) format in a `xxx.stories.js` file. Simply add the file to the `/stories` folder in the project root, or one of its subdirectory. Storybook is configured to load all of them.

Make sure that the HTML markup uses the Lit Element `html` tag.

Here is an example: [https://github.com/LWC-Essentials/storybook/blob/master/packages/lwc-library/stories/hello-greetings.stories.js](https://github.com/LWC-Essentials/storybook/blob/master/packages/lwc-library/stories/hello-greetings.stories.js).

#### Documentation story
Storybook also uses [MDX](https://storybook.js.org/docs/formats/mdx-syntax/) files to provide a customizable documentation for the components. An MDX file contains a mix of markdown and React JSX. The documentation stories are stored in `xxx.stories.mdx` files that are also automatically loaded. Similarly to the component stories, it uses the Lit Element `html` tag for the component markup.

Here is an example: [https://github.com/LWC-Essentials/storybook/blob/master/packages/lwc-library/stories/doc-greetings.stories.mdx](https://github.com/LWC-Essentials/storybook/blob/master/packages/lwc-library/stories/doc-greetings.stories.mdx).

### Developer experience
One of the value of Storybook is the component development experience, where a code change is reflected directly in the Storybook UI. For this, Storybook relies on Webpack watchers but this is not sufficient as the components are built using Rollup. Rollup is thus started with its own watchers, so it rebuilt when a component is changed. Then Webpack watches the changes to the Rollup built file and rebuilds itself.

As both Webpack and Rollup have watchers, the 2 processes must run in parallel. To make this reliable, we use [npm-run-all](https://www.npmjs.com/package/npm-run-all) instead of the simple `&` operator when launching processes.


### Static content
There are two ways for adding static content:

- Create an MDX story and add your markdown
- Create a component story and inject HTML

Here is an example for the second solution: [https://github.com/LWC-Essentials/storybook/blob/master/packages/lwc-app/stories/welcome.stories.js](https://github.com/LWC-Essentials/storybook/blob/master/packages/lwc-app/stories/welcome.stories.js).


### Deploying the static sites
The sites are deployed by default to Gihub pages, using using [gh-pages](https://www.npmjs.com/package/gh-pages). An easy solution solution would use [storybook-deployer](https://github.com/storybookjs/storybook-deployer), but is is less flexible in particular when deploying multiple Storybook sites from a mono repo.

Deploying the static sites involves the following commands from the project root:

```sh
yarn build-static
yarn deploy-static
```

## LWC Specific

### Complex properties
LWC components can consume complex property values, like objects. Unfortunately, the custom element specification does not allow such values to ba passed through HTML markup.

There are multiple solutions:

- Create a technical Web Component that wraps the desired one, and pass it complex parameters via the template
The library demo defines these components is the `wc` namespace located in `stories`.
-

### Development watchers


#### Static content

### Provided add-ons
The sample app configures a set of add-ons
Expand Down Expand Up @@ -179,21 +246,10 @@ The sample app configures a set of add-ons
StoryShots adds automatic Jest Snapshot Testing for Storybook.
- [@storybook/addon-links](https://www.npmjs.com/package/@storybook/addon-links)
Links can be used to create links that navigate between stories in Storybook.


The WebComponent organization is also coming with some add-ons, like [storybook-addon-web-components-knobs](https://www.npmjs.com/package/storybook-addon-web-components-knobs), but they have not been integrated with this sample project yet.


### Testing
***TODO ...***


### Mocking data services
***TODO ...***


### Deploying a static site
The deployment is done by default to Gihub pages, using [storybook-deployer](https://github.com/storybookjs/storybook-deployer).

## TODOs
Yes there are TODOs...
Expand All @@ -206,9 +262,8 @@ Yes there are TODOs...

- Extend web-component-analyzer to better support LWC
[https://github.com/runem/web-component-analyzer/issues/150 ](https://github.com/runem/web-component-analyzer/issues/150)
- Deduce the elemebtr name from the directory
- Scan the lwc.config.json to enumerate the components
- Deduce the element name from the directory of a component
- Handle @api and other decorators
- ...



Binary file added assets/Storybook-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"clean": "lerna run clean --stream",
"test": "lerna run test --stream",
"build-static": "lerna run storybook:build-static --stream",
"deploy-static": "gh-pages -d static-site"
"deploy-static": "gh-pages -d static-site -u [email protected] -m \"Storybook static site\""
},
"devDependencies": {
"gh-pages": "3.1.0"
Expand Down
3 changes: 3 additions & 0 deletions packages/lwc-app/lwc.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"modules": [
{
"dir" : "src"
},
{
"npm": "lwc-library"
}
Expand Down
5 changes: 5 additions & 0 deletions packages/lwc-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,10 @@
"storybook:build-static": "yarn storybook:build && yarn build-storybook",
"build-storybook": "build-storybook -c .storybook -o ../../static-site/lwc-app && sb extract ../../static-site/lwc-app ../../static-site/lwc-app/stories.json",
"deploy-storybook": "storybook-to-ghpages -e .storybook/build/static-site"
},

"storybook": {
"title": "LWC Application Example",
"url": "https://lwc-essentials.github.io/storybook/lwc-app"
}
}
10 changes: 10 additions & 0 deletions packages/lwc-app/stories/app-main.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { html } from 'lit-html';

export default {
title: "Application Main",
component: 'app-main'
};

export const default_ = () => html`
<app-main></app-main>
`;
4 changes: 3 additions & 1 deletion packages/lwc-app/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
* server.
*/
import buildAndRegisterCustomElement from '../.storybook/utils/build-custom-element';
import AppMain from 'app/main';

// Add components
// Register components
buildAndRegisterCustomElement('app-main', AppMain);
3 changes: 3 additions & 0 deletions packages/lwc-library/lwc.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"modules": [
{
"dir" : "src"
},
{
"dir" : "stories"
}
],
"expose": [
Expand Down
8 changes: 4 additions & 4 deletions packages/lwc-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@storybook/web-components": "6.0.0-rc.3",

"@storybook/cli": "6.0.0-rc.3",
"@storybook/storybook-deployer": "2.8.6",
"npm-run-all": "4.1.5",

"babel-loader": "^8.0.5",
"eventemitter3": "^4.0.0",
Expand All @@ -67,10 +67,10 @@
"scripts": {
"build": "cross-env rollup -c ./scripts/rollup.config.js",

"storybook": "yarn storybook:build && yarn storybook:start",
"storybook": "run-p storybook:build storybook:start",
"storybook:build": "yarn storybook:meta && yarn storybook:buildlwc",
"storybook:meta": "web-component-analyzer src --outFiles .storybook/build/custom-elements.json",
"storybook:buildlwc": "rollup -c ./.storybook/rollup.config.js",
"storybook:buildlwc": "rollup -w -c ./.storybook/rollup.config.js",
"storybook:start": "start-storybook -s ./dist -p 6010",
"storybook:build-static": "yarn storybook:build && yarn build-storybook",
"build-storybook": "build-storybook -c .storybook -o ../../static-site/lwc-library && sb extract ../../static-site/lwc-library ../../static-site/lwc-library/stories.json",
Expand All @@ -79,6 +79,6 @@

"storybook": {
"title": "LWC Library Example",
"url": "https://lwc-essentials.github.io/storybook"
"url": "https://lwc-essentials.github.io/storybook/lwc-library"
}
}
2 changes: 1 addition & 1 deletion packages/lwc-library/src/hello/greetings/greetings.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<template>
<div>{greetings}</div>
<div>It is now: {time}</div>
<div>It is now: <hello-time time={time}></hello-time></div>
</template>
2 changes: 1 addition & 1 deletion packages/lwc-library/src/hello/greetings/greetings.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default class Greetings extends LightningElement {
/**
* This is a property that comes from a wire adapter
*/
@wire(Time) time = '';
@wire(Time) time = {};

/**
* This is a simple property
Expand Down
8 changes: 8 additions & 0 deletions packages/lwc-library/src/hello/time/time.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<template if:false={invalid}>
{time.hours}:{time.minutes}:{time.seconds}
</template>
<template if:true={invalid}>
<span style="color: red">Ouch, the time is invalid</span>
</template>
</template>
40 changes: 40 additions & 0 deletions packages/lwc-library/src/hello/time/time.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { LightningElement, api, wire, buildCustomElementConstructor } from 'lwc';

import { Time } from '../../wire/time'

/**
* Display the time coming from a complex object containing 3 number members.
* {
* hours: 21,
* minutes: 18,
* seconds: 44
* }
*
* @element hello-time
*/
export default class DisplayTime extends LightningElement {

/**
* Time passed as a complex object.
* @attr
*/
@api time = {hours:0,minutes:0,seconds:0}

get invalid() {
if(!this.time) {
return true;
}
if(typeof this.time.hours!=="number" || (this.time.hours<0 || this.time.hours>23) ) {
return true;
}
if(typeof this.time.minutes!=="number" || (this.time.minutes<0 || this.time.minutes>59) ) {
return true;
}
if(typeof this.time.seconds!=="number" || (this.time.seconds<0 || this.time.seconds>59) ) {
return true;
}
return false;
}
}

//customElements.define("hello-time", buildCustomElementConstructor(DisplayTime));
12 changes: 0 additions & 12 deletions packages/lwc-library/src/main.js

This file was deleted.

6 changes: 5 additions & 1 deletion packages/lwc-library/src/wire/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ export class Time {

emitTime() {
const now = new Date();
const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`
const time = {
hours:now.getHours(),
minutes:now.getMinutes(),
seconds:now.getSeconds()
}
this.dataCallback(time);
}
}
24 changes: 24 additions & 0 deletions packages/lwc-library/stories/hello-time.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { withKnobs } from '@storybook/addon-knobs';
import { html } from 'lit-html';

export default {
title: 'Display time',
component: 'hello-time',
decorators: [withKnobs],
};

export const default_ = () => html`
<hello-time></hello-time>
`;

export const nullTime = () => html`
<wc-time></wc-time>
`;

export const emptyTime = () => html`
<wc-time data='1'></wc-time>
`;

export const fixedTime = () => html`
<wc-time data='2'></wc-time>
`;
10 changes: 10 additions & 0 deletions packages/lwc-library/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
import buildAndRegisterCustomElement from '../.storybook/utils/build-custom-element';
import HelloWorld from 'hello/world';
import HelloGreetings from 'hello/greetings';
import HelloTime from 'hello/time';

import WcTime from 'wc/time';


// Register components
buildAndRegisterCustomElement('hello-world', HelloWorld);
buildAndRegisterCustomElement('hello-time', HelloTime);
buildAndRegisterCustomElement('hello-greetings', HelloGreetings);

buildAndRegisterCustomElement('wc-time', WcTime);


3 changes: 3 additions & 0 deletions packages/lwc-library/stories/wc/time/time.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<hello-time time={time}></hello-time>
</template>
Loading

0 comments on commit 0905e2c

Please sign in to comment.