Storybook

https://storybook.js.org/ is an incredibly useful tool. It allows you to visually document your components. For each component you "tell a story" of what that component looks like in different states. It can even http://blog.caplin.com/2017/10/04/dev-week-storybook-and-jest/.

Integrating it into a ReasonML project is relatively straightforward. Given that bsb already compiles our ReasonML files into JavaScript, it is imply a case of tweaking storybook's configuration.

NOTE: this assumes your project was created using bsb -init <your project> -theme react. If not, and you used create-react-app then the directories/generated JS might be in different places.

Installing Storybook

Install Storybook by following https://storybook.js.org/basics/quick-start-guide. This will automatically do the following:

  • add a new stories directory containing an example index.stories.js
  • create a .storybook directory containing its config.js and a Storybook specific webpack.config.js
  • add a "storybook" script into package.json for running a development Storybook server
  • add a "build-storybook" script into package.json. I will leave it as an exercise for the reader to find out what that does ;-).

Next we add in the plumbing so we can write stories in ReasonML.

Storybook, meet ReasonML

To teach ReasonML and Storybook about each other we need to do the following:

  1. Create a directory where we will store our stories (or accept the default stories directory)
  2. Provide bindings for the Storybook library
  3. Teach bsb to build the stories in that directory
  4. Teach Storybook to consume the stories in that directory
  5. Write some stories!

1. Create the directory

As convention, I prefer to have a dedicated stories directory as a sibling of src, which is also the default setup for Storybook so this should already exist.

2. Provide bindings

As with most libraries we need to provide bindings to bsb so it can understand how to interact with the third party library. There is prior art here at https://github.com/splish-me/bs-storybook, but unfortunately they use the older format style and isn't published anywhere.

For now, I suggest creating a vendor-src directory and putting library bindings under that. Specifically, vendor-src/storybook:

  1. Create vendor-src/storybook
  2. Add vendor-src/storybook to the sources key in bsconfig.json

Now, create the actual bindings in vendor-src/storybook/storybook.rei:

type section;

type story = unit => ReasonReact.reactElement;

[@bs.val] [@bs.module "@storybook/react"]
external createSection : (string, 'a) => section = "storiesOf";

[@bs.send]
external addDecorator : (section, story => ReasonReact.reactElement) => unit =
  "";

[@bs.send] external addStory : (section, string, story) => unit = "add";

Your project should now understand how to interpret stories written in ReasonML.

3. Teach `bsb` to build the stories

In step 1. we created (or used the existing) stories directory. To each bsb about that simply add the stories to the sources key in bsconfig.json.

4. Teach storybook to consume the stories

We should now have stories correctly translated from ReasonML to JS. If you aren't already running yarn start (or running bsb some other way then please do so now).

Storybook's config (in .storybook/config.js is already configured to look in the stories directory, but it is restricted to only files that match/.story.js$/ which won't match our generated JS, so change it to /.js$/.

If you have chosen a different directory for your stories you will need to change the "../stories" to "../whatever-you-called-your-directory".

At this point .storybook/config.js should look something like:

import { configure } from "@storybook/react";

// automatically import all files ending in *.stories.js
const req = require.context("../stories", true, /.js$/);
function loadStories() {
  req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);

5. Write some stories!

To create a story we first need a component. I am assuming you have a src/page.re:

/* This is the basic component. */
let component = ReasonReact.statelessComponent("Page");

/* Your familiar handleClick from ReactJS. This mandatorily takes the payload,
   then the `self` record, which contains state (none here), `handle`, `reduce`
   and other utilities */
let handleClick = (_event, _self) => Js.log("clicked!");

/* `make` is the function that mandatorily takes `children` (if you want to use
   `JSX). `message` is a named argument, which simulates ReactJS props. Usage:

   `<Page message="hello" />`

   Which desugars to

   `ReasonReact.element(Page.make(~message="hello", [||]))` */
let make = (~message, _children) => {
  ...component,
  render: (self) =>
    <div onClick=(self.handle(handleClick))> (ReasonReact.stringToElement(message)) </div>
};

In stories/PageStories.re enter the following:

let pageSection = Storybook.createSection("Page", [%bs.raw "module"]);

Storybook.addStory(pageSection, "Test", () => <Page message="Hello!" />);

Assuming bsb is running (if not then run it) you should see a PageStories.bs.js . If you run yarn storybook then your browser should magically open at http://localhost:6006 and look something like:

Complete source code

https://github.com/yatesco/reason-storybook-example contains a project configured using the following steps.

results matching ""

    No results matching ""