Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Solid with Storybook [How-To Guide] #35

Closed
mnquintana opened this issue Apr 30, 2021 · 27 comments
Closed

Using Solid with Storybook [How-To Guide] #35

mnquintana opened this issue Apr 30, 2021 · 27 comments
Labels
help wanted Looking for assistance on this issue

Comments

@mnquintana
Copy link

The Storybook API has changed quite a lot since Solid's Storybook docs were written in 2019. It would be really helpful (and I think go a loooong way toward helping drive adoption, long-term) if Solid could:

  1. Update the docs to reflect the latest Storybook API (hopefully everything still works with 6.2+?)
  2. Abstract all these manual setup steps away with an official Solid framework plugin for Storybook (docs for how to do this here: https://storybook.js.org/docs/react/api/new-frameworks)

Storybook's become a pretty essential piece of the UI development pipeline, and at least for me, official support for it is a prerequisite for seriously considering any frontend frameworks. I miiiight be able to help with setting this up for Solid, although I haven't tried adding support for a new framework to Storybook before. 😅

@rturnq
Copy link
Member

rturnq commented Apr 30, 2021

I created storybook-solid a little while ago but haven't had the time to really try it out much. This follows the guide from Storybook you linked. It's not ideal yet as it would need buy-in from Storybook to add it as a community supported framework to they cli tool, but it saves you from having to write boilerplate.

It would be great to get some feedback on this and once it has been tested out a bit more, then maybe we could get it added to the docs.

@ryansolid
Copy link
Member

Yeah I'm all for this. I just haven't had the reason to look into it. I was hoping that someone would update the docs at some point. I'm not sure if stuff is different now. If anyone wants to help putting such a plugin together I'd support it and do what is needed to get it recognized. I'm just not working in Storybook personally.

@ryansolid ryansolid added feature Suggest a new feature. good first issue Good for newcomers documentation labels Apr 30, 2021
@ryansolid
Copy link
Member

On further review I think we are best setting up a repo for a plugin following the steps and not bothering with docs here. It probably should be managed independently.

@mnquintana
Copy link
Author

Sounds good to me! Let me know if there's anything I can help with.

@grenierdev
Copy link

grenierdev commented Jul 14, 2021

I was able to set up Storybook (v6.3.4) in my project using the @storybook/html mode and without the solidjs/solid#553 bug.

Turns out we just need to create our own root and call render on this new root. In .storybook/preview.js, add these lines and your good to go!

import { render } from "solid-js/web";

let disposeStory;

export const decorators = [
  Story => {
    if (disposeStory) {
      disposeStory();
    }
    const root = document.getElementById("root");
    const solid = document.createElement("div");
	 
    solid.setAttribute('id', 'solid-root');  
    root.appendChild(solid);
    disposeStory = render(Story, solid);
    return solid;
    // return createRoot(() => Story()); // do not work correctly https://github.com/solidjs/solid/issues/553
  }
]

@zettadam
Copy link

zettadam commented Aug 25, 2021

@grenierdev How did you accomplish this? I followed your decorators in ./storybook/preview.js, but I'm running into issues with Babel & JSX. What's your babel configuration? Did you have to install any Babel presets and/or plugins?

UPDATE: I use vite and vite-plugin-solid in my projects and forgot about babel-preset-solid :) It works now. Here's my .storybook/main.js:

module.exports = {
  stories: [
    '../src/**/*.stories.@(mdx|jsx)'
  ],
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-links'
  ],
  babel: async (options) => ({
    ...options,
    presets: [
      'solid'
    ]
  })
}

@joel1st
Copy link

joel1st commented Feb 18, 2022

@zettadam do you happen to have a working storybook repo I/the community can take a look at? Super keen to use storybook with solid and not quite sure what piece I'm missing..

@arjun-g
Copy link

arjun-g commented Mar 30, 2022

Anyone had success with using mdx with solidjs ?

@pakholeung37
Copy link

pakholeung37 commented May 28, 2022

If someone wants to use storybook with vite, this would help
https://codesandbox.io/s/qx2r0?file=/.storybook/main.js:0-404

And with pnpm you need to add more devDependencies to avoid errors. Here is part of my package.json

{
  "devDependencies": {
    "@babel/core": " ^7.0.0",
    "@babel/preset-env": "^7.18.0",
    "@mdx-js/react": "1.6.22",
    "@storybook/addon-actions": "6.5.0-alpha.61",
    "@storybook/addon-backgrounds": "6.5.0-alpha.61",
    "@storybook/addon-docs": "6.5.0-alpha.61",
    "@storybook/addon-essentials": "6.5.0-alpha.61",
    "@storybook/addon-links": "6.5.0-alpha.61",
    "@storybook/addon-measure": "6.5.0-alpha.61",
    "@storybook/addon-outline": "6.5.0-alpha.61",
    "@storybook/builder-vite": "^0.1.27",
    "@storybook/client-api": "6.5.0-alpha.61",
    "@storybook/client-logger": "6.5.0-alpha.61",
    "@storybook/core-common": "6.5.0-alpha.61",
    "@storybook/html": "6.5.0-alpha.61",
    "react": "16.14.0",
    "react-dom": "16.14.0",
    "typescript": "^4.6.4",
    "vite": "^2.9.9",
    "vite-plugin-solid": "^2.2.6"
  },
}

@ryansolid ryansolid transferred this issue from solidjs/solid Jun 2, 2022
@Jutanium Jutanium added help wanted Looking for assistance on this issue advanced-experience and removed documentation feature Suggest a new feature. good first issue Good for newcomers labels Jun 13, 2022
@Jutanium Jutanium changed the title Update Storybook support for storybook@latest Using Solid with Storybook Jun 13, 2022
@Jutanium Jutanium changed the title Using Solid with Storybook Using Solid with Storybook [How-To Guide] Jun 13, 2022
epompeii added a commit to bencherdev/bencher that referenced this issue Jun 26, 2022
@NorbertBodziony
Copy link

What about https://github.com/histoire-dev/histoire they are planing solid support. This seems like great alternative.

@carere
Copy link

carere commented Jul 7, 2022

Hello, is there anyone succeed to use the Args of Story book with SolidJs ?

I tried to implement the types ComponentStoryFn and ComponentMeta, by taking example over storybook/react, but it did not work, Typescript don't want to compile.

@carere
Copy link

carere commented Jul 7, 2022

Ok I may have something 😄

So first here is the button component from the codesandbox example used on this thread:

import { For } from 'solid-js'

export const Button = ({ count }: { count: number }) => {
  const labels = Array(count)
    .fill(null)
    .map((_, i) => i + 1)

  return <For each={labels}>{label => <button>{label}</button>}</For>
}

And then the story

import { Meta, StoryFn } from '@storybook/html'
import { Component, ComponentProps } from 'solid-js'
import { Button } from '../components/Button'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ComponentStoryFn<T extends Component<any>> = StoryFn<ComponentProps<T>>

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ComponentMeta<T extends Component<any>> = Meta<ComponentProps<T>>

export default {
  title: 'Example/Button',
  argTypes: {
    count: { control: 'number' },
  },
} as ComponentMeta<typeof Button>

const Template = (args => <Button {...args} />) as ComponentStoryFn<typeof Button>

export const Primary = Template.bind({})

Primary.args = {
  count: 5,
}

Since, I'm coming from FP land (Rescript), i use no-explicit-any rule, but this time, i need to avoid it, else, Typescript gonna prevent me from compiling.
Also, we need to "force cast" the function used during the initialization of Template variable.
If a Typescript wizard has a better and prettier solution to solve this problem, it would be wonderful 😄
Anyway, this is the up to date solution to use SolidJS with Storybook

PS: I'm also using vite, the config from the codesandbox for vite, is still correct.

@elite174
Copy link

elite174 commented Oct 6, 2022

If somebody wants to see the repo with examples: https://github.com/elite174/storybook-solid-js

@joel1st it might be useful for you

@JReinhold
Copy link

(Storybook maintainer here 👋)

@elite174 that looks pretty dope actually, and I'm astounded that it works with so little configuration.
Are there any drawbacks with the solution that you know of? Missing features or annoying UX/DX?

If you or anyone else ever want to try and turn this into an actual Solid addon/renderer let me know and I'd be happy to assist you with whatever you need.
I'm also hanging around on the Solid Discord as JReinhold#4884, just ping me.

@davedbase
Copy link
Member

@elite174 @JReinhold we'd really love for Solid to have an official Storybook adapter. Myself and core will support and provide insight wherever is needed. ❤️

@elite174
Copy link

elite174 commented Oct 18, 2022

Honestly, I prefer framework-agnostic approach. In my repo it's clear enough how to setup storybook with just simple html setup. IMO it would be better if a tool provided plain JS API which can be used by any framework. Just imagine that tommorow there will be Solid-Vue-Svelte super framework and you need to write an integration for it...
I do love how simple it was to setup storybook for solid-js framework. So I'll keep my repo available in case someone wants to use it. I think that integration support costs more than having simple framework-agnostic setup. Maybe it's better to add docs for storybook about how to setup html project with Solid?

UPD: Added instructions to my repo.

@JReinhold All hope is on you :)

@noahbrenner
Copy link

noahbrenner commented Dec 31, 2022

I was able to get this set up using storybook 7 (currently storybook@next), vite, and the starting points provided in this issue by @grenierdev and @carere:

.storybook/preview.js is actually working for me without any decorators defined at all. With the new Storybook version, I initially got an error caused by document.getElementById("root") returning null (the decorator was called before that div was created), and eventually pared down the function till I found I could remove it entirely.
Edit: It does still need a decorator for more complex components than my first basic example. See my next comment for the updated decorator I'm using.

I'm staring from the html-vite preset, so .storybook/main.js includes this entry:

framework: {
  name: "@storybook/html-vite"
  options: {},
}

Story example

These examples use CSF 3.0 - https://storybook.js.org/blog/component-story-format-3-0/

// Button.tsx
// `children` would be better than `text`, but I went with this to test the handling of prop types
export function Button({ text }: { text: string }) {
  return <button>{text}</button>;
}
// Button.stories.tsx
import type {
  ComponentAnnotations,
  StoryAnnotations,
  WebRenderer,
} from "@storybook/types";
import type { Component, ComponentProps } from "solid-js";
import { Button } from "./Button";

// I'm including all these types here, but in a real project, I'd define them in a separate file.
// Only Meta and StoryObj would need to be exported from that file.

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyComponent = Component<any>;

interface SolidRenderer extends WebRenderer {
  component: AnyComponent;
  storyResult: ReturnType<AnyComponent>;
}

type Meta<T extends AnyComponent> = ComponentAnnotations<
  SolidRenderer,
  ComponentProps<T>
>;

type StoryObj<T extends AnyComponent> = StoryAnnotations<
  SolidRenderer,
  ComponentProps<T>
>;

// And here's the actual story content

export default {
  title: "MyButton",
  component: Button,
} satisfies Meta<typeof Button>;

export const FooButton: StoryObj<typeof Button> = {
  args: {
    text: "Foo",
  },
};

Edit: Here's a StoryFn type using the same approach:

import type { AnnotatedStoryFn } from "@storybook/types";

// AnyComponent and SolidRenderer are the same as above
type StoryFn<T extends AnyComponent> = AnnotatedStoryFn<
  SolidRenderer,
  ComponentProps<T>
>;

const Template: StoryFn<typeof Button> = (args) => <Button {...args} />;

export const BarButton = Template.bind({});
Bar.args = {
  text: "Bar",
};

@carere
Copy link

carere commented Dec 31, 2022

@noahbrenner Can you share with us your package.json, main.js, and preview.js ?
I remembered, that in order to had a fully working storybook in 6.5, I needed to add way so much dependencies.

@elite174
Copy link

Guys, storybook 7 is just in beta
I'll make an update to my repo when storybook v7 is released.

@noahbrenner
Copy link

noahbrenner commented Dec 31, 2022

@carere Absolutely, I'll paste those files below.

The storybook-related dependencies were installed by running npx storybook@next init --type html and I haven't made any changes to preview.js. I'm sure the storybook addon dependencies could be removed if you don't plan to use them. It seems odd to need react as a direct dependency, but it probably makes sense so that a react project doesn't end up depending on 2 different versions -- one for the project itself and one for storybook. In any case, the react deps were added automatically by the storybook init script.

This isn't a fully minimal repro, since I have some non-essential deps (prettier, eslint, husky, lint-staged, vitest, jsdom, tauri), but it is still just the initial setup of the project.

package.json
{
  "version": "0.0.0",
  "description": "",
  "license": "MIT",
  "scripts": {
    "prepare": "husky install",
    "start": "vite",
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview",
    "lint": "tsc --noEmit && eslint --ext .ts,.tsx --ignore-path .gitignore . && prettier --check ./*.* src",
    "test": "vitest --reporter=verbose",
    "tauri": "tauri",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  },
  "lint-staged": {
    "**/*": "prettier --write --ignore-unknown"
  },
  "devDependencies": {
    "@storybook/addon-essentials": "^7.0.0-beta.17",
    "@storybook/addon-interactions": "^7.0.0-beta.17",
    "@storybook/addon-links": "^7.0.0-beta.17",
    "@storybook/blocks": "^7.0.0-beta.17",
    "@storybook/html": "^7.0.0-beta.17",
    "@storybook/html-vite": "^7.0.0-beta.17",
    "@storybook/testing-library": "^0.0.13",
    "@tauri-apps/cli": "^1.2.2",
    "@types/babel__core": "^7.1.20",
    "@types/node": "^18.7.10",
    "@typescript-eslint/eslint-plugin": "^5.47.1",
    "@typescript-eslint/parser": "^5.47.1",
    "eslint": "^8.30.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-storybook": "^0.6.8",
    "husky": "^8.0.2",
    "jsdom": "^20.0.3",
    "lint-staged": "^13.1.0",
    "prettier": "^2.8.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "storybook": "^7.0.0-beta.17",
    "typescript": "^4.7.4",
    "vite": "^4.0.0",
    "vite-plugin-solid": "^2.3.0",
    "vitest": "^0.26.2"
  },
  "dependencies": {
    "@tauri-apps/api": "^1.2.0",
    "solid-js": "^1.4.7"
  }
}
.storybook/main.js
module.exports = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/html-vite",
    options: {},
  },
  docs: {
    autodocs: "tag",
  },
};
.storybook/preview.js
import { render } from "solid-js/web"

let disposeStory = () => {};
export const decorators = [
  (Story) => {
    disposeStory();
    const solidRoot = document.createElement("div");
    disposeStory = render(Story, solidRoot);
    return solidRoot;
  },
];

// This isn't part of the solution, it's just what `storybook init` scaffolded and I didn't delete it
export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};

And just so everything is in one place, here are the component and story files again:

src/components/Button.tsx
export function Button({ text }: { text: string }) {
  return <button>{text}</button>;
}
src/components/Button.stories.tsx
import type {
  AnnotatedStoryFn,
  ComponentAnnotations,
  StoryAnnotations,
  WebRenderer,
} from "@storybook/types";
import type { Component, ComponentProps } from "solid-js";
import { Button } from "./Button";

// Types that should eventually be defined in their own file

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyComponent = Component<any>;

interface SolidRenderer extends WebRenderer {
  component: AnyComponent;
  storyResult: ReturnType<AnyComponent>;
}

type Meta<T extends AnyComponent> = ComponentAnnotations<
  SolidRenderer,
  ComponentProps<T>
>;

type StoryObj<T extends AnyComponent> = StoryAnnotations<
  SolidRenderer,
  ComponentProps<T>
>;

type StoryFn<T extends AnyComponent> = AnnotatedStoryFn<
  SolidRenderer,
  ComponentProps<T>
>;

// Meta is the same for both CSF 2 and 3

export default {
  title: "MyButton",
  component: Button,
} satisfies Meta<typeof Button>;

// Using StoryObj (CSF 3)

export const FooButton: StoryObj<typeof Button> = {
  args: {
    text: "Foo",
  },
};

// Using StoryFn (CSF 2)

const Template: StoryFn<typeof Button> = (args) => <Button {...args} />;

export const BarButton = Template.bind({});
Bar.args = {
  text: "Bar",
};

@noahbrenner
Copy link

Ah, it broke down when using a more complex component (the component example using <For> that @carere posted), but it worked again using a modified version of @grenierdev's decorator. I'll post that decorator here and update my previous comment to include the decorator.

The important change here is that we're not inserting the created element into the DOM directly, we're just returning the element and letting Storybook handle inserting it.

// .storybook/preview.js
import { render } from "solid-js/web"

let disposeStory = () => {};
export const decorators = [
  (Story) => {
    disposeStory();
    const solidRoot = document.createElement("div");
    disposeStory = render(Story, solidRoot);
    return solidRoot;
  },
];

@noahbrenner
Copy link

Still left to solve: hmr updates

  • 😄 If I update the props using the Storybook controls UI, the component updates right away.

  • ☹️ If I update the component code itself, I see the hmr update reflected both in the terminal running storybook and in the browser console, but the component itself isn't re-rendered, so the update isn't reflected in the storybook UI unless I refresh the page or switch to a different story. To verify this, I added a console.log to both the decorator and the component itself -- both are logged on load and when switching stories, but not when updating the component.

Any ideas on this, @JReinhold? I don't know if this would need to be addressed from the Solid side or the Storybook side.

@carere
Copy link

carere commented Jan 4, 2023

I also have the same problem on 7.x and 6.x

@elite174
Copy link

elite174 commented Jan 6, 2023

@carere @noahbrenner
Guys, seems like I managed to fix the issue with HMR. See my updated repo: https://github.com/elite174/storybook-solid-js#readme

@fabien-ml
Copy link

@elite174 i've followed your repo instruction and got this error on a pnpm monorepo :

Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.

My components and stories files are all .tsx, any idea ?

@noahbrenner
Copy link

@fabien-ml That's not a lot of info to go on. Since you're getting a syntax error, that doesn't sound like it has to do with the Storybook/Solid integration. It's possible that question might be better suited to StackOverflow or something (along with a minimal repro of your issue, versions you're using, and the command you ran to get that message). As a first step, you might try just running tsc --noEmit and/or your linter over your repo to see if you get more specific error messages about the syntax error.

@fabien-ml
Copy link

@fabien-ml That's not a lot of info to go on. Since you're getting a syntax error, that doesn't sound like it has to do with the Storybook/Solid integration. It's possible that question might be better suited to StackOverflow or something (along with a minimal repro of your issue, versions you're using, and the command you ran to get that message). As a first step, you might try just running tsc --noEmit and/or your linter over your repo to see if you get more specific error messages about the syntax error.

Yeah I underdstand, sorry. I was able to get it work by using the webpack5 builder instead of vite. I don't know why but the problem seem related to the combo of storybook + vite builder + pnpm monorepo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Looking for assistance on this issue
Projects
None yet
Development

No branches or pull requests