Storybook.JS is a very interesting development tool from JavaScript ecosystem1. This tutorial shows how we can use it with Shadow-CLJS. The code resides at storybook.js-with-shadow-cljs repo.
Prerequisites
The tutorial uses the following:
- Java version 11
- Node.js version 14.15.4
- Reagent version 1.0.0
- Shadow-CLJS version 2.11.8
- Storybook.JS version 6.1.17
Make sure the first two are installed prior the tutorial. The others will be installed along the way.
Getting a simple React app running
Let’s create the scaffold to kick-start:
$ mkdir acme
$ cd acme
$ npm init # just keep pressing enter until the prompt ends
$ npm install --save-dev shadow-cljs
In the generated package.json
, add a helper script to launch shadow-cljs
and automatically compile when it detect changes:
"scripts": {
"dev": "shadow-cljs watch frontend"
}
The script uses the :frontend
profile defined in shadow-clj.edn
for
ClojureScript compiler. Run npx shadow-cljs init
to generate the skeleton
shadow-cljs.edn
file and edit it as follows:
|
|
Line 8 adds Reagent as a dependency; lines 11 and 12 create the
profile :frontend
(that matches the npm script’s shadow-cljs watch
command).
This profile specifies that the build targets the browser and should
generate the file main.js
(‘cos of the :main
key) that will invoke
acme.core/init
function at initialization. Let’s implement init
that uses
a simple Reagent component in src/main/acme/core.cljs
:
|
|
Simple enough: a custom header
component that outputs the given text in
an h1
element and the init
function that renders the header. To see this
glorious app render, create the public/index.html
as follows:
|
|
By default, Shadow-CLJS generates the output to public/js
,
hence the highlighted line (line 9). When the page is ready, init
will run and
renders the header component. Before running npm run dev
, add dev-http
to shadow-cljs.edn
to configure the dev-server to listen to port 8080 and
serve artifacts from public
directory:
|
|
With all these set up, run npm run dev
and load
the page localhost:8080
in your favorite browser; you should see “Hello,
World!”:
Some cleanup
Before integrating with Storybook.JS, let’s do some cleaning
up: extract the custom header
component to its own namespace and make
acme.core/init
use that extracted one instead. First, the extracted
component at src/main/acme/components/header.cljs
:
|
|
Then, in src/main/acme/core.cljs
, delete header
function and require
the header component namespace (as shown in line 2 below):
|
|
Adding Storybook.JS to the mix
Time to add Storybook.JS to the project. Install it with
npm install --save-dev @storybook/react
; then create .storybook/main.js
with the following contents to configure Storybook.JS to look for stories
in public/js/stories
directory:
|
|
Update shadow-cljs.edn
to create a new profile specifically for stories
that outputs the transpiled stories to public/js/stories
too:
|
|
A few notable points on the new :stories
profile:
:entries
specifies the namespaces to transpile to stories; unlike:frontend
profile that specifies the target filename to output to (main.js
), Shadow-CLJS uses the namespace as the output filename, e.g.,acme.stories.header_stories.js
:target
states the build should target npm module which works for Storybook.JS2
Add two script commands to package.json
to ease the auto-compilation of
stories and to start Storybook.JS:
|
|
And finally, the story. Let’ create a very simple story at
src/stories/acme/stories/header_stories.cljs
that says “Hello, World!”:
|
|
The snippet above uses Component Story Format, hence the need to
add the metadata ^:export
to default
and HelloWorldHeader
. Because
Storybook.JS operates on React components, reactify-component
at line 7 turns the Reagent component into a React
one.3 With all these preparation, run npm run dev-stories
in one console,
and npm run storybook
in another. You should see Storybook.JS
render our first story:
For the fun of it, let’ append another story to header-stories
:
|
|
Wrapping up
That concludes this tutorial on using Storybook.JS with Shadow-CLJS. In this case, we are using Reagent to create the components for Storybook.JS to render. It shouldn’t be that difficult to adapt the setup to work with other ClojureScript rendering libraries, e.g., Helix.
-
Shadow-CLJS has a new
:esm
target that outputs to ES Modules, but as of this writing, it is cumbersome to use (the^:export
metadata hint isn’t working, thus requiring the need to declare all exports inshadow-cljs.edn
. ↩︎ -
Refer to Reagent’s tutorial on Interop with React for more information. ↩︎