Darek Kay's picture
Darek Kay
Solving web mysteries

Running Storybook from a separate folder

After migrating a project to Vite, I've moved my Storybook setup into a separate module — a folder next to the actual app:

📁 project
├─ 📁 app
|  ├─ 📁 src
|  └─ 📄 package.json
└─ 📁 storybook
   ├─ 📁 .storybook
   |  ├─ 📄 main.js
   |  └─ 📄 preview.js
   └─ 📄 package.json

First, I've moved all Storybook dependencies from app/package.json to storybook/package.json and called yarn install. Next, I've moved the .storybook folder and adjusted all relative paths in main.js and preview.js, e.g.:

module.exports = {
-  stories: ["../src/**/__stories__/*.stories.tsx"],
+  stories: ["../../app/src/**/__stories__/*.stories.tsx"],

This setup was working fine locally. However, when trying to run build-storybook on my server, I've got the following error (also reported by others):

ModuleParseError: Module parse failed: Unexpected token

You may need an appropriate loader to handle this file type,
  currently no loaders are configured to process this file.

Storybook includes a webpack babel-loader to handle TypeScript and/or JSX files. The issue is with how Storybook determines the project root, which is the path that the babel-loader includes by default.

When calculating the project root, Storybook first searches for the closest parent .git folder. In my local development environment, this path was equal to the common parent of app and storybook. But on my server, I separate the Git repository from my working directory. As a result, the project root was incorrect and babel-loader didn't include the app files.

My workaround was to fix the include value of the babel-loader in the main.js file:

const path = require("path");

module.exports = {
  // ...

  webpackFinal: async (config, { configType }) => {
    const babelLoaderRule = config.module.rules.find(
      (rule) => rule.test.toString() === /\.(mjs|tsx?|jsx?)$/.toString()
    // set correct project root
    babelLoaderRule.include = [path.resolve(__dirname, "../..")];

    return config;

This code first finds the babel-loader config by searching for the defined RegExp object (note: in non-TS projects, remove |tsx? to match the Storybook definition). Then, include is changed to the correct project root. In my case, I use ../.. to get to the grandparent of the .storybook folder.

Check out this commit for the exact changes that I did to extract my Storybook module.

Related posts

Running Storybook from a separate folder