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

Is there a lifecycle module that's called before each story in a browser only? #11378

Closed
kettanaito opened this issue Jul 1, 2020 · 17 comments
Closed

Comments

@kettanaito
Copy link

kettanaito commented Jul 1, 2020

Hey 👋 Thanks for an awesome project! I've been using Storybook for years and I love it.

I'm maintaining a usage example of Mock Service Worker library with Storybook, and I have a question about the best way to integrate those two.

Integration

The point of the integration is to call the setupWorker function before each story in a browser. setupWorker cannot be called in a Node environment (that includes DOM-like environments like jsdom).

Current behavior

I'm placing my logic into .storybook/preview.js, which is promised to run before each story. While it does do so in a browser, it's also run in Node when I test my stories.

You can verify that by checking out the examples repository mentioned below, and running the following command:

$ yarn test

Expected behavior

Is there a configuration module that's executed before each story, but only in a browser environment?

Reproduction repository

Please refer to the Storybook usage example repository to play around with this setup by yourself. I'd be thankful for any guidance and suggestion in this. Exciting to bring an awesome example of a usage with Storybook!

@shilman
Copy link
Member

shilman commented Jul 1, 2020

@yannbf i think you looked at this?

I think the most reliable way to hook in before a story render is to add a global decorator, but not sure if that suits your use case...

@yannbf
Copy link
Member

yannbf commented Jul 1, 2020

Hey @kettanaito, Nice to see that you're working on this! As of now I'd say unfortunately it's necessary to add a few workarounds to use msw with storybook, indeed preview is the way to go for registering the service worker, and a decorate to check if the story can be rendered.

But if I'm not mistaken there are people working on making stories async (@tmeasday can explain that better I think) and once that is done, integrating msw will be easier!

@kettanaito
Copy link
Author

Thank you for the quick replies, folks.
I see, it's okay to have a slight workaround at this point. Looking forward to the async stories, though.

Do you find this approach sensible?

// .storybook/preview.js
// Storybook executes this module in both bootstap phase (Node)
// and a story's runtime (browser). However, cannot call `setupWorker`
// in Node environment, so need to check if we're in a browser.
if (typeof global.process === 'undefined') {
  const { worker } = require('../src/mocks')

  // Start the mocking when each story is loaded.
  // Repetitive calls to the `.start()` method do not register a new worker,
  // but check whether there's an existing once, reusing it, if so.
  worker.start()
}

@simmo
Copy link

simmo commented Jul 17, 2020

@kettanaito I have had an issue with this, mainly that stuff is getting 500'ed by msw (like fonts, one works one doesn't, race condition?). Only happens in storybook. My setup has the worker.start() located in my app entry point (not loaded by storybook) as well as in the preview.js as suggested above so I can get msw working in both environments. I found that moving the contents of the condition inside a decorator resolved the issue.

addDecorator(storyFn => {
  if (process.env.NODE_ENV === 'development') {
    const { worker } = require('../src/mocks/browser');

    worker.start();
  }

  return <SomeProviders>{storyFn()}</SomeProviders>
});

Only issue I can see is that navigating between stories outputs a fresh [MSW] Mocking enabled. console.log each time.

@simmo
Copy link

simmo commented Jul 17, 2020

In fact, I seemed to fix that with:

const worker = process.env.NODE_ENV === 'development' ? require('../src/mocks/browser').worker : null;
let workerRun = false;

addDecorator(storyFn => {
  if (!workerRun && worker) {
    workerRun = true;
    worker.start();
  }

  return <SomeProviders >{storyFn()}</SomeProviders>
});

@stale
Copy link

stale bot commented Aug 8, 2020

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 stale bot added the inactive label Aug 8, 2020
@kettanaito
Copy link
Author

@simmo, if you update to the latest version of MSW (0.20.x), some of the previously described issued may be fixed. We've added improvements to the worker instance management. I'd love to hear your feedback on this.

@stale stale bot removed the inactive label Aug 10, 2020
@tmeasday
Copy link
Member

We've added some docs in the 6.0 release that are probably relevant to this issue: https://storybook.js.org/docs/react/workflows/build-pages-with-storybook

@simmo
Copy link

simmo commented Aug 13, 2020

Thanks @kettanaito + @tmeasday . Looks good, I think MSW 0.20+SB 6 updates are helping but worker init (well, console log at least) is still output multiple times. Looking at updated Storybook docs, a decorator might not be the best solution for implementing it. I'm going to have a play! 😄

@tmeasday
Copy link
Member

If you figure out some useful information feel free to PR the docs, it is all pretty new + raw at this stage. Also you might want to look at what they are doing over at Redwood; they have a msw+SB integration going there.

@stale
Copy link

stale bot commented Sep 11, 2020

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 stale bot added the inactive label Sep 11, 2020
@stale
Copy link

stale bot commented Oct 12, 2020

Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!

@kettanaito
Copy link
Author

Thanks, @tmeasday.

I've got some time to look into this, and I really like the StoryFn.decorators for per-story request handlers, it's simply superb. However, there still seems to be no way to add a decorator once to the entire storybook.

This is what I used to do in .storybook/preview.js:

// .storybook/preview.js
// Storybook executes this module in both bootstap phase (Node)
// and a story's runtime (browser). However, cannot call `setupWorker`
// in Node environment, so need to check if we're in a browser.
if (typeof global.process === 'undefined') {
  const { worker } = require('../src/mocks/browser')
  worker.start()
}

As mentioned before, this does work but is not quite developer-friendly to advocate. If I swap this with a global decorator, it gets executed before each story:

// .storybook/preview.js
export const decorators = [
  (Story) => {
    const { worker } = require('../src/mocks/browser')
    // As this decorator gets called before each story,
    // MSW registers and activates repeatedly.
    worker.start()
    return <Story />
  }
]

While there is a deduplication mechanism on MSW's side and calling worker.start() once the worker is already active doesn't do anything, it certainly contributes to the confusion to see [MSW] Mocking enabled too many times in the browser's console.

And so the search for the right approach continues. I'll reach out to Redwood developers to see how they are integrating MSW.

@tmeasday
Copy link
Member

tmeasday commented Jan 4, 2021

Hey @kettanaito,

I'm not sure a decorator is really necessary if this only needs to happen once. I would have thought just calling code directly in preview.js makes sense. As I wrote on your example, I'm not sure you should need to use the global.process check?

@kettanaito
Copy link
Author

Hey, @tmeasday. Thanks for the suggestions.

SInce preview.js is called both during Storybook build (NodeJS) and browser runtime, I cannot unconditionally import a worker instance there—it's destined to be used only in a browser environment. I did, however, end up with just importing the worker in the preview.js conditionally (based on typeof global.process), which seemed to work.

I'm currently facing another issue: the *.story.js file itself is also imported during build and browser, making it hard to unconditionally import a worker instance to append different HTTP scenarios per-story:

// stories/User.story.js
// This `import` is called in both NodeJS and in a browser,
// raising an error since the worker isn't meant for NodeJS.
import { worker } from '../mocks/browser'

const Story = () => <User />
Story.decorators = [
  (Story) => {
    worker.use(/* runtime handler */)
    return <Story />
  }
]

Now, in order for the example above to work, I need to conditionally export the worker, omitting the export in a NodeJS environment:

// mocks/browser.js
export const worker =
  typeof global.process === 'undefined' && setupWorker(...handlers)

@tmeasday
Copy link
Member

tmeasday commented Feb 4, 2021

Hey @kettanaito

I'm not sure what you mean when you say "during Storybook build" -- preview.js and stories are not evaluated during the storybook build, from the node context.

I wonder if you mean inside storyshots or something?

@kettanaito
Copy link
Author

Hi, @tmeasday. Forgive me for some of the misleading terminologies.

When I try to run Storybook there seems to be no issue, it must have been some quirks on my setup's side. Let me investigate it further and come back with the updates. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants