21

I have a React/Typescript project with Storybook. Storybook works great, but as soon as I start importing files with aliases, it crashes.

Example:

import Foo from "@components/foo" => crash
import Foo from "../../components/foo" => ok

The app works fine with the aliases. The issue is only related to Storybook.

Here is my storybook config:

module.exports = {
  stories: ["../**/stories.tsx"],
  webpackFinal: (config) => {
    return {
      ...config,
      module: {
        ...config.module,
        rules: [
          {
            test: /\.(ts|js)x?$/,
            exclude: /node_modules/,
            use: { loader: "babel-loader" },
          },
          { test: /\.css$/, use: ["style-loader", "css-loader"] },
          { test: /\.(png|jpg|gif)$/, use: ["file-loader"] },
          {
            test: /\.svg$/,
            use: [
              {
                loader: "babel-loader",
              },
              {
                loader: "react-svg-loader",
                options: {
                  jsx: true,
                },
              },
            ],
          },
        ],
      },
    };
  },
  typescript: {
    check: false,
    checkOptions: {},
    reactDocgen: "react-docgen-typescript",
    reactDocgenTypescriptOptions: {
      shouldExtractLiteralValuesFromEnum: true,
      propFilter: (prop) =>
        prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
    },
  },
};

My webpack config:

/* eslint-env node */
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const Dotenv = require("dotenv-webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const isProductionMode = (mode) => mode === "production";

module.exports = () => {
  const env = require("dotenv").config({ path: __dirname + "/.env" });
  const nodeEnv = env.parsed.NODE_ENV;
  return {
    mode: "development",
    entry: "./src/index.tsx",
    output: {
      path: path.join(__dirname, "./dist"),
      filename: "[name].[contenthash].bundle.js",
      publicPath: "/",
    },
    resolve: {
      extensions: [".ts", ".tsx", ".js", "jsx", ".json"],
      alias: {
    "@api": path.resolve(__dirname, "src/api/"),
    "@assets": path.resolve(__dirname, "src/assets/"),
    "@components": path.resolve(__dirname, "src/components/"),
    "@containers": path.resolve(__dirname, "src/containers/"),
    "@data": path.resolve(__dirname, "src/data/"),
    "@i18n": path.resolve(__dirname, "src/i18n/"),
    "@models": path.resolve(__dirname, "src/models/"),
    "@pages": path.resolve(__dirname, "src/pages/"),
    "@src": path.resolve(__dirname, "src/"),
    "@stores": path.resolve(__dirname, "src/stores/"),
    "@utils": path.resolve(__dirname, "src/utils/"),
  },
    },
    module: {
      rules: [
        {
          test: /\.(ts|js)x?$/,
          exclude: /node_modules/,
          use: { loader: "babel-loader" },
        },
        { test: /\.css$/, use: ["style-loader", "css-loader"] },
        { test: /\.(png|jpg|jpeg|gif)$/, use: ["file-loader"] },
        {
          test: /\.svg$/,
          use: [
            {
              loader: "babel-loader",
            },
            {
              loader: "react-svg-loader",
              options: {
                jsx: true,
              },
            },
          ],
        },
      ],
    },
    devServer: {
      historyApiFallback: true,
      port: 3000,
      inline: true,
      hot: true,
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: "./src/index.html",
      }),
      new Dotenv(),
    ],
    optimization: {
      minimize: isProductionMode(nodeEnv),
      minimizer: isProductionMode(nodeEnv) ? [new TerserPlugin()] : [],
      splitChunks: { chunks: "all" },
    },
  };
};

How to fix this? I am on webpack 5.24.2 and storybook 6.1.20, so these are the latest versions.

DoneDeal0
  • 5,273
  • 13
  • 55
  • 114
  • Have you tried repeating your aliases in the storybook config? Not 100% sure but I think the `config` passed to `webpackFinal` is storybook's default config and doesn't know anything about your other webpack config. You might try `console.log`ging the `config` from within `webpackFinal` to confirm. EDIT on further review looks like you can import and merge your existing webpack config: https://storybook.js.org/docs/react/configure/webpack#using-your-existing-config – azundo Mar 02 '21 at 18:22
  • If I add the aliases with "resolve", it says "configuration.module has an unknown property 'resolve'". If I import webpack like in the official doc, it says "can't read modules of undefined" – DoneDeal0 Mar 02 '21 at 18:32
  • Couple things, `resolve` goes at the top-level of the webpack config, not under `config.module`. Not 100% sure about the webpack export/import issue but do note that your webpack config export is a function, not a plain object, so you'll need to call it and merge the output of that call with the storybook default if you get the import to work. – azundo Mar 02 '21 at 19:16

9 Answers9

26

Just add this in your .storybook/main.js

const path = require('path');

module.exports = {
  "stories": [
    "../components/**/*.stories.mdx",
    "../components/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    '@storybook/preset-scss',
  ],
  webpackFinal: async (config, { configType }) => {
    config.resolve.alias = {
      ...config.resolve.alias,
      '@/interfaces': path.resolve(__dirname, "../interfaces"),
    };

    return config;
  }
}

here interface is folder at my project root

It works For Me

Krishna Jangid
  • 4,961
  • 5
  • 27
  • 33
12

This worked for me when I had the same problem:

  • Install a package in dev deps yarn add -D tsconfig-paths-webpack-plugin.
  • Then adjust your ./storybook/main.js config:
... // other imports
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");

...
webpackFinal: (config) => {
  config.resolve.plugins = config.resolve.plugins || [];
  config.resolve.plugins.push(
    new TsconfigPathsPlugin({
      configFile: path.resolve(__dirname, "../tsconfig.json"),
    })
  );

  return { ... }
}
...
hiimtmac
  • 131
  • 2
  • 6
  • 2
    Link to the documentation: https://storybook.js.org/docs/react/configure/webpack#typescript-module-resolution – Stuart Nichols Jan 21 '22 at 12:49
  • The link is broken, this is the link updated https://storybook.js.org/docs/react/builders/webpack#typescript-module-resolution – Mosh Feu Mar 04 '23 at 19:00
12

From the docs:

// .storybook/main.js

const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

module.exports = {
  webpackFinal: async (config) => {
    config.resolve.plugins = [
      ...(config.resolve.plugins || []),
      new TsconfigPathsPlugin({
        extensions: config.resolve.extensions,
      }),
    ];
    return config;
  },
};

Link

Alois
  • 241
  • 3
  • 6
  • Thanks. The link is broken, this is the updated link https://storybook.js.org/docs/react/builders/webpack#typescript-module-resolution – Mosh Feu Mar 04 '23 at 18:59
7

My React/TypeScript Storybook project uses Vite rather than Webpack.

The readme for storybook-builder-vite clarifies "The builder will not read your vite.config.js file by default," so anything that you specified in there may be having no influence whatsoever on the Storybook build; instead, you have to customise the Storybook-specific Vite config via the viteFinal option in .storybook/main.js.

Here's how I went about introducing vite-tsconfig-paths into the Storybook Vite config to resolve tsconfig path aliases:

// .storybook/main.js
const path = require("path");
const tsconfigPaths = require("vite-tsconfig-paths").default;

module.exports = {
  "stories": [
    "../frontend/**/*.stories.mdx",
    "../frontend/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions"
  ],
  "framework": "@storybook/react",
  "core": {
    "builder": "storybook-builder-vite"
  },
  /**
   * A option exposed by storybook-builder-vite for customising the Vite config.
   * @see https://github.com/eirslett/storybook-builder-vite#customize-vite-config
   * @param {import("vite").UserConfig} config
   * @see https://vitejs.dev/config/
   */
  viteFinal: async (config) => {
    config.plugins.push(
      /** @see https://github.com/aleclarson/vite-tsconfig-paths */
      tsconfigPaths({
        // My tsconfig.json isn't simply in viteConfig.root,
        // so I've passed an explicit path to it:
        projects: [path.resolve(path.dirname(__dirname), "frontend", "tsconfig.json")],
      })
    );
    
    return config;
  },
}
Jamie Birch
  • 5,839
  • 1
  • 46
  • 60
5

In case you use @storybook/vite-builder. This neat config works for me

const tsconfigPaths = require("vite-tsconfig-paths");
...
module.exports = {
  ...
  async viteFinal(config) {
    return {
      ...config,
      plugins: [...config.plugins, tsconfigPaths.default()],
    };
  },
};
Phạm Huy Phát
  • 783
  • 9
  • 17
1

If you're using webpack 5 you'll need to specify that webpack5 should be used by also adding the following in addition to the previous answers:

  core: {
    builder: "webpack5",
  },

Final storybook/main.js would then resemble:

    // .storybook/main.js
const path = require('path');
const appWebpack = require(path.join(process.cwd(), 'webpack.config.js'));
module.exports = {
  stories: ['../src/**/*.stories.@(tsx|mdx)'],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    '@storybook/preset-scss'
  ],
  core: {
    builder: "webpack5",
  },
  webpackFinal: async (config) => {
    config.resolve.modules = [
      ...(config.resolve.modules || []),
      ...[path.resolve(process.cwd(), "src")],
    ];
    config.resolve.alias = {
      ...(config.resolve.alias || {}),
      ...appWebpack().resolve.alias,
    };
    return config;
  },
};

This will allow both absolute paths as well as aliases (as long as those aliases are properly set up in your main webpack.config.js and jsconfig.json/tsconfig.json of course)

Edited

Having trouble after the fact specifically with aliases, I took another trip down the webpack rocky-road.

I've updated the original 'final' for the .storybook/main.js above, explicitly merging in the alias as well as the modules nodes.

Edit 2

Be aware, eslint is going to squawk over using an alias within global decorators you create (and add to .storybook/preview.js). You can safely ignore this - they still work. If/when I figure out how to correct this as well, I'll come back and add a 3rd edit.

Jeremy Little
  • 354
  • 1
  • 3
  • 11
1

We're using Vite and typescript project references, for us adding the following to the storybook main.cjs worked;

viteFinal: async (config) => {
    config.resolve.alias = {
        ...config.resolve.alias,
        '@some-alias': path.resolve(__dirname, '../../some/ts/project/reference'),
    };
    return config;
}
SamHoque
  • 2,978
  • 2
  • 13
  • 43
1

As an alternative to Jamie Birch's excellent answer, if you're using vite and don't want to install vite-tsconfig-paths, you can just edit .storybook/main.js and add viteFinal to the config, like this:

const path = require('path');

module.exports = {
 // ... whatever you already have here
  viteFinal: async (config) => {
    if (config.resolve.alias) {
      config.resolve.alias.push({ find: '@', replacement: path.resolve(__dirname, '../src') + '/' });
    } else {
      config.resolve.alias = [{ find: '@', replacement: path.resolve(__dirname, '../src') + '/' }];
    }
    return config;
  }
}
LGenzelis
  • 764
  • 6
  • 14
0

there can be 2 cases here of using import alias

import something from "@components/somewhere"
import something from "@/components/somewhere"

you can address both of the cases simply by writing these in the storybook/main.ts file

import path from "path";

const config: StorybookConfig = {
...other codes are here

  webpackFinal: async (config) => {
    ...other codes are here
    
    (config.resolve as any).alias = {
      ["@components"]: [path.resolve(__dirname, "./components")],
      ["@/components"]: [path.resolve(__dirname, "../components")],
    };
  }
}

Hope it helps !

JoyShaheb
  • 129
  • 1
  • 7