storybook icon indicating copy to clipboard operation
storybook copied to clipboard

async configurations

Open JauernigIT opened this issue 5 years ago • 6 comments

Is your feature request related to a problem? Please describe We have to dynamically generate some configurations based on the result of a JSON file on a server. In our case, we have a list of tenants in a JSON file, by which we want to configure addon-cssresources. But since we we just can export parameters ... in preview.js in a synchronous way, we do not have the possibility to achieve this asynchronous task.

Describe the solution you'd like The config exports should also allow for asynchronous functions. E.g. in preview.js for parameters:

export const parameters = async () => ({
    cssresources: await getCssResources()
});

JauernigIT avatar Oct 07 '20 07:10 JauernigIT

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

stale[bot] avatar Dec 26 '20 01:12 stale[bot]

@JauernigIT It's already possible. But you need to change syntax. Storybook expects parameters to be an object and no function. So it cannot handle a parameters function. Instead you need to call a function that returns the parameters object and assign this to parameters.

when you change it like the following, it will work:

const getAsyncParameters = async () => ({
    cssresources: await getCssResources()
});

export const parameters = await getAsyncParameters();

awacode21 avatar Apr 10 '22 13:04 awacode21

@JauernigIT It's already possible. But you need to change syntax. Storybook expects parameters to be an object and no function. So it cannot handle a parameters function. Instead you need to call a function that returns the parameters object and assign this to parameters.

when you change it like the following, it will work:

const getAsyncParameters = async () => ({
    cssresources: await getCssResources()
});

export const parameters = await getAsyncParameters();

This will caused Cannot use keyword 'await' outside an async function failed, can you provide an example? Thanks!

evont avatar Apr 11 '22 07:04 evont

you are right, I have not taken this into account. Unfortunately then I don't have a solution.

awacode21 avatar Apr 11 '22 10:04 awacode21

Loaders are Storybook's method for handling async data: https://storybook.js.org/docs/react/writing-stories/loaders

You can't use it to load parameters async, but you can use it to fetch async data async, and then use this data ins your stories. It might require changing the way you structure your stories, so I wouldn't recommend it unless absolutely needed.

shilman avatar Apr 11 '22 11:04 shilman

I have the same use-case where I need to configure parameters of others addons based on the component type. This is currently only possible using args types.

I have a configuration generated on build time for each component but I cannot apply because parameters doesn't support "dynamic data" as we have with the args types. Would be interesting on supporting something like this:

export const parameters = (context) => {);

As is already available to decorators and arg types. Another option could be to customise the Story params through a central configuration.

This is very valuable to sync documentation across different sources with the same format and be able to control additional addons in a "static but dynamic" way, without having the hassle to write the same code on each story...

ianvieira avatar Oct 19 '22 15:10 ianvieira

I don't think we'll support this use case any time soon cc @tmeasday

However, as a workaround, you might be able to do something like this in your .storybook/main.js

export const previewAnnotations = async (entry, options) => {
  // do something async
  // write a file './foo.js' containing
  // export const parameters = { /* your dynamic data here */ }
  return [...entry, require.resolve('foo.js')];
}

It's not pretty, but might do the trick if you really need it.

shilman avatar Oct 24 '22 12:10 shilman

I was able to workaround that with a also not pretty solution, but it works only on V6 Store, if I enable V7 Store, I have no access to the component on the story (btw, is this going to be like that in V7?):

addons.register(ADDON_NAME, (api) => {
  api.on(STORY_RENDERED, (storyId) => {
    const story = api.resolveStory(storyId) as Story;

    const update = {
      parameters: {
        ...story.parameters,
        ...extractParameters(story.component, api),
      }
    };

    // @ts-ignore
    return api.updateStory(storyId, update);
  });
});

ianvieira avatar Oct 24 '22 17:10 ianvieira

@ianvieira this is code running in the manager (ie. in a register.js?). I'm not sure how component is making it over the channel in v6 mode, it's not serializable AFAIK. I suspect if it is working in v6 mode it is sort of a lucky thing, rather than intentional.

tmeasday avatar Oct 25 '22 00:10 tmeasday

@tmeasday Yes, it's on the manager.ts of my addon.

I believe it worth to mention that my component is a string, as I'm using Stencil and this is the best way to correlate the data. For other frameworks for sure the limitation applies. Also, I'm using the API in this case and not the broadcast channel data, which might not be limited by the serialisation limitations.

Which raises me a question, how an addon is able to know the component a story refers to? Shouldn't this be exposed in someway on the Storybook API? I'd expect the V6 to be correct, as it's properly showing the story data, when V7 is enabled, we have very limited data through API (component is not the only one missing).

ianvieira avatar Oct 25 '22 00:10 ianvieira

@ianvieira what else is missing?

Which raises me a question, how an addon is able to know the component a story refers to?

Usually the title of the "component" (ex-kind) on the story contains the name of the component. Not sure what else you can do in a non-string-based context, when the addon is running in a separate JS context (iframe) to the component itself.

tmeasday avatar Oct 25 '22 02:10 tmeasday

@tmeasday I'm posting a summary of what I have in the story for V6 and V7 stores, using the api.resolveStory(storyId):

V6:

{
  "type": "story",
  "args": {},
  "componentId": "button-test",
  "title": "Button test",
  "kind": "Button test",
  "id": "button-test--primary",
  "name": "Primary",
  "story": "Primary",
  "component": "bp-button",
  "parameters": {},
  "initialArgs": {},
  "argTypes": {},
  "depth": 1,
  "parent": "button-test",
  "isLeaf": true,
  "isComponent": false,
  "isRoot": false,
  "prepared": true
}

V7:

{
  "type": "story",
  "id": "button-test--primary",
  "kind": "Button test",
  "name": "Primary",
  "parameters": {},
  "depth": 1,
  "parent": "button-test",
  "isLeaf": true,
  "isComponent": false,
  "isRoot": false,
  "prepared": true,
  "initialArgs": {},
  "argTypes": {},
  "args": {}
}

On the V7 I cannot have any information to correlate a component with a story, none of them are safe as it would be based on the assumption that a title I don't control is the same of the html tag, what is an unsafe path and also not preferred as the components do have name.

Not sure what else you can do in a non-string-based context, when the addon is running in a separate JS context (iframe) to the component itself.

This issue only raises due to the impossibility of the properties of the story being derived from the component type, what is not a problem for args. I just need to create this add-on because it's the only API I could find capable of knowing which is the component of a determined story and adding the correct parameters.

To you understand the use case, this is used mainly for 2 things:

  1. Add the designs for each one of the components, using storybook-addon-designs
  2. Add badges to indicate the component state (Ready for Adoption, Deprecated, etc), using @geometricpanda/storybook-addon-badges

Those two information are really important in a Design System context and this will always be component bound, although I can have multiple stories for that. Those addons are really interesting in creating a Design System documentation but they rely on parameters configurations, as recommended, and cannot be automated due to parameters not being able to be dynamically defined.

I believe this could be very helpful in a Design System environment as we can properly orchestrate the documentation without having to repeat the same thing on each story or manual writing, also syncing with the README.

ianvieira avatar Oct 25 '22 10:10 ianvieira

So I don't think there's anything else in there that's interesting that's gone from v6->v7.

Like I said, I think the fact that component used to get passed over the channel was an accident and wouldn't have occurred in any non-string based framework.

My advice to reproduce something similar would be to put the component "name" in parameters. Appreciate that you can't do that dynamically, and it's not very DRY, but it's the only thing that's going to make sense for other frameworks:

export default {
  component: Button,
  parameters: {
    componentName: Button /* or in other frameworks 'Button' */
  }
}

Then again, I would highly encourage you to name the story files the same as the component anyway, in which case you could make the assumption that the title is right.

tmeasday avatar Oct 25 '22 22:10 tmeasday

I'd like to see the option for async configurations.

Our use case would be to check if ports are open when settings refs:

config.refs = {
  "@some/package": {
    title: "@some/package",
    url:  await detect(6007) ? "http://localhost:6007" : "http://our-chromatic-url.chromatic.com",
  },
},

Alternatives for this use case:

  • allow refs to be an async function
  • add config option to specify local and published url, and have storybook fallback at runtime

penx avatar Jan 30 '23 17:01 penx