rspack-contrib / storybook-rsbuild

Storybook builder and frameworks powered by Rsbuild.
MIT License
75 stars 4 forks source link

compatibility with storybook-addon-manual-mocks #53

Open abenhamdine opened 2 months ago

abenhamdine commented 2 months ago

We are migrating from vite, and we are blocked by an incompatibility with https://github.com/gebeto/storybook-addon-manual-mocks which was working perfectly with vite.

You can see the issue we ran into here : https://github.com/gebeto/storybook-addon-manual-mocks/issues/19

There's also a version of this preset for webpack so we tried to use webpack addon but the error was the same.

Finally we tried to mimic the code of the addon here : https://github.com/gebeto/storybook-addon-manual-mocks/blob/main/webpack/preset.js by overriding the rsbuild config in storybook/main.ts like that :

import { StorybookConfig } from 'storybook-react-rsbuild'
import { mergeRsbuildConfig, rspack } from '@rsbuild/core'
import path from 'path'
import fs from 'fs'

const EXTENSIONS = [".ts", ".js"]
const MOCKS_DIRECTORY = "mocks"

const config: StorybookConfig = {
  stories: [
    "../src/**/*.stories.@(ts|tsx)"
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-onboarding",
    "@storybook/addon-interactions",
    'storybook-addon-remix-react-router',
  ],
  framework: {
    name: 'storybook-react-rsbuild',
    options: {}
  },
  rsbuildFinal: (config) => {
    return mergeRsbuildConfig(config, {
      dev: {
        client: {
          overlay: false
        }
      },
      tools: {
        rspack: (config) => {
          const mocksReplacementPlugins = [
            new rspack.NormalModuleReplacementPlugin(/^\.\//, async (resource) => {
              const mockedPath = path.resolve(
                resource.context,
                MOCKS_DIRECTORY,
                resource.request
              )
              for (let ext of EXTENSIONS) {
                const isReplacementPathExists = fs.existsSync(mockedPath + ext)
                if (isReplacementPathExists) {
                  const newImportPath =
                    "./" + path.join(MOCKS_DIRECTORY, resource.request)
                  resource.request = newImportPath
                  break
                }
              }
            }),
            new rspack.NormalModuleReplacementPlugin(/^\.\.\//, async (resource) => {
              const mockedPath = path.resolve(
                resource.context,
                MOCKS_DIRECTORY,
                resource.request
              )
              for (let ext of EXTENSIONS) {
                const isReplacementPathExists = fs.existsSync(mockedPath + ext)
                if (isReplacementPathExists) {
                  const newImportPath =
                    "./" + path.join(MOCKS_DIRECTORY, resource.request)
                  resource.request = newImportPath
                  break
                }
              }
            })
          ]
          config.plugins = (config.plugins ?? []).concat(mocksReplacementPlugins)
          return config
        }
      },
      source: {
        include: [
          /node_modules[\\/]storybook-addon-remix-react-router[\\/]dist[\\/]index.js/
        ]
      }
    })
  },
  staticDirs: ['../public'],
  docs: {
    defaultName: "Docs",
    docsMode: false,
    autodocs: false,
  },
}

but still no luck : same error.

Looks like the NormalReplacement plugin has no effect, though the code is the exact copy of the webpack addon. I really don't understand where the problem is.

Thx by advance for any kind of help.

fi3ework commented 2 months ago

Try adding a plugin like this. The config merging is quite complex inside, this is the way I know it should work. The way you provided should also work but not for now.

import { mergeRsbuildConfig } from '@rsbuild/core'

// --- snip ---
async rsbuildFinal(config) {
    return mergeRsbuildConfig(config, {
      tools: {
        rspack: (config, { addRules, appendPlugins, rspack, mergeConfig }) => {
          return mergeConfig(config, {
            plugins: [
              // YOUR PLUGIN HERE
            ],
          })
        },
      },
    })
}
fi3ework commented 2 months ago

Hi, does this configuration help out?

abenhamdine commented 2 months ago

no it doesn't work either

here's my storybook/main.ts file :

import { StorybookConfig } from 'storybook-react-rsbuild'
import { mergeRsbuildConfig, rspack } from '@rsbuild/core'
import path from 'path'
import fs from 'fs'

const EXTENSIONS = [".ts", ".js"]
const MOCKS_DIRECTORY = "mocks"

const config: StorybookConfig = {
  stories: [
    "../src/**/*.stories.@(ts|tsx)"
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-onboarding",
    "@storybook/addon-interactions",
    'storybook-addon-remix-react-router',
  ],
  framework: {
    name: 'storybook-react-rsbuild',
    options: {}
  },
  rsbuildFinal: (config) => {
    return mergeRsbuildConfig(config, {
      dev: {
        client: {
          overlay: false
        }
      },
      tools: {
        rspack: (config, { mergeConfig }) => {
          return mergeConfig(config, {
            plugins: [
              new rspack.NormalModuleReplacementPlugin(/^\.\//, async (resource) => {
                const mockedPath = path.resolve(
                  resource.context,
                  MOCKS_DIRECTORY,
                  resource.request
                )
                for (let ext of EXTENSIONS) {
                  const isReplacementPathExists = fs.existsSync(mockedPath + ext)
                  if (isReplacementPathExists) {
                    const newImportPath =
                      "./" + path.join(MOCKS_DIRECTORY, resource.request)
                    resource.request = newImportPath
                    break
                  }
                }
              }),
              new rspack.NormalModuleReplacementPlugin(/^\.\.\//, async (resource) => {
                const mockedPath = path.resolve(
                  resource.context,
                  MOCKS_DIRECTORY,
                  resource.request
                )
                for (let ext of EXTENSIONS) {
                  const isReplacementPathExists = fs.existsSync(mockedPath + ext)
                  if (isReplacementPathExists) {
                    const newImportPath =
                      "./" + path.join(MOCKS_DIRECTORY, resource.request)
                    resource.request = newImportPath
                    break
                  }
                }
              })
            ]
          })
        }
      },
      source: {
        define: {
          'import.meta.env.PUBLIC_GRAPHQL_SERVER_URL': JSON.stringify(process.env.PUBLIC_GRAPHQL_SERVER_URL),
          'import.meta.env.PUBLIC_MODE': JSON.stringify(process.env.PUBLIC_MODE),
          'import.meta.env.PUBLIC_DEBUG': JSON.stringify(process.env.PUBLIC_DEBUG),
          'import.meta.env.PUBLIC_GLEAP_API_KEY': JSON.stringify(process.env.PUBLIC_GLEAP_API_KEY),
          'import.meta.env.PUBLIC_SENTRY_DSN': JSON.stringify(process.env.PUBLIC_SENTRY_DSN),
          'import.meta.env.PUBLIC_CLARITY_PROJECT_ID': JSON.stringify(process.env.PUBLIC_CLARITY_PROJECT_ID)
        },
        include: [
          /node_modules[\\/]storybook-addon-remix-react-router[\\/]dist[\\/]index.js/
        ]
      }
    })
  },
  staticDirs: ['../public'],
  docs: {
    defaultName: "Docs",
    docsMode: false,
    autodocs: false,
  },
}

export default config

and still the same error : the import is not mocked.

fi3ework commented 2 months ago

Could you add a console.log under new rspack.NormalModuleReplacementPlugin(/^\.\//, async (resource) => { to test is the plugin being invoked indeed?

abenhamdine commented 2 months ago

yes, the plugin is being invoked

$ cross-env NODE_OPTIONS=--max-old-space-size=8192 storybook dev -p 6006
storybook v8.2.7

info => Serving static files from ./.\public at /
info => Starting manager..
info => Starting preview..
info Addon-docs: using MDX3
start   Compiling...
info Using tsconfig paths for react-docgen
 // below the console.logs calls
1st plugin : resource.context :  D:\GitHub\payroll-app-next\client\src\state
1st plugin : resource.request :  ./state
1st plugin remplacement exists :  D:\GitHub\payroll-app-next\client\src\state\mocks\state.ts
1st plugin : new import path :  ./mocks\state
//
ready   Compiled in 5.79 s (web)

the replacement path is correct, however the import is not mocked
Is it allowed to mutate resource.request ? I would say yes because if i set a new path 'fooBar/state', there's an error, thus the request path is actually being changed

fi3ework commented 2 months ago

The new import path ./mocks\state looks weird (I'm not familiar with Windows tbh), is that correct?

abenhamdine commented 2 months ago

Yes it looks weird, but If change the path like this :

    console.log('1st plugin : remplacement exists : ', mockedPath + ext)  

    // old method
    //const newImportPath = "./" + path.join(MOCKS_DIRECTORY, resource.request)

    // new method : 
    const fileRequested = resource.request.split('/').pop()
    const newImportPath = "./" + MOCKS_DIRECTORY + "/" + fileRequested

    resource.request = newImportPath

logs :

start   Compiling...
info Using tsconfig paths for react-docgen
1st plugin : resource.context :  D:\GitHub\payroll-app-next\client\src\state
1st plugin : resource.request :  ./state
1st plugin remplacement exists :  D:\GitHub\payroll-app-next\client\src\state\mocks\state.ts
1st plugin : new import path :  ./mocks/state
ready   Compiled in 11.4 s (web)

see, the path looks right, but the issue is the same, the module is not mocked

I don't understand why this doesn't work, the replacement seems being processed but the mock is not active.

fi3ework commented 2 months ago

The NormalModuleReplacementPlugin is supprted by Rspack in https://github.com/web-infra-dev/rspack/pull/6028. I suspect this has something to do with Storybook's internal configuration. It would be very nice if you could provide a minimal reproduction to help me easy to debug.

abenhamdine commented 2 months ago

Thx for investigating, I will prepare a minimal reproduction as soon as possible