How to resolve aliases in Storybook?
Asked Answered
F

10

34

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.

Ferwerda answered 2/3, 2021 at 18:14 Comment(3)
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.logging the config from within webpackFinal to confirm. EDIT on further review looks like you can import and merge your existing webpack config: storybook.js.org/docs/react/configure/… – Squirm
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" – Ferwerda
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. – Squirm
R
38

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) => {
    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

Ropeway answered 6/8, 2021 at 6:2 Comment(2)
Worked for me in 12/2023. The "../" within path.resolve(__dirname, "../interfaces") is very important, as the .storybook/main.ts file lives in a different relative location than your webpack.config.js, so you need to add that ".." to whatever aliasing you have already setup in your webpack.config.js (I tried to just copy/paste πŸ€¦β€β™‚οΈ) – Intend
For me it only started working when I used only "@" as the key: config.resolve.alias["@"] = path.resolve(__dirname, "..", "src"); – Maines
B
17

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 { ... }
}
...
Bently answered 23/6, 2021 at 16:16 Comment(2)
Link to the documentation: storybook.js.org/docs/react/configure/… – Barouche
The link is broken, this is the link updated storybook.js.org/docs/react/builders/… – Woods
I
14

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

Interfluve answered 27/1, 2022 at 15:19 Comment(1)
Thanks. The link is broken, this is the updated link storybook.js.org/docs/react/builders/… – Woods
C
10

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;
  },
}
Continuous answered 25/3, 2022 at 12:3 Comment(0)
C
8

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

const tsconfigPaths = require("vite-tsconfig-paths");
...
module.exports = {
  ...
  async viteFinal(config) {
    return {
      ...config,
      plugins: [...(config.plugins || []), tsconfigPaths.default()],
    };
  },
};
Cuthburt answered 25/7, 2022 at 7:31 Comment(2)
Code from this answer works with vite, helped me to finally run a storybook – Cyanic
The builder is named @storybook/builder-vite – Antonetteantoni
I
2

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.

Ingrained answered 12/9, 2021 at 19:21 Comment(0)
M
2

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;
  }
}
Mellman answered 7/2, 2023 at 22:23 Comment(0)
T
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;
}
Teirtza answered 28/11, 2022 at 15:35 Comment(0)
T
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 !

Tally answered 9/6, 2023 at 15:7 Comment(0)
S
0

My problem was related to react-docgen-typescript it was not crawling types correctly in autodocs. adding baseUrl to reactDocgenTypescriptOptions. compilerOptions fixed the problem.


    reactDocgenTypescriptOptions: {
      // Speeds up Storybook build time
      compilerOptions: {
        baseUrl: 'src', // newly added 
        allowSyntheticDefaultImports: false,
        esModuleInterop: false,
      },
      // Makes union prop types like variant and size appear as select controls
      shouldExtractLiteralValuesFromEnum: true,
      // Makes string and boolean types that can be undefined appear as inputs and switches
      shouldRemoveUndefinedFromOptional: true,
      // Filter out third-party props from node_modules except @mui packages
      propFilter: (prop) =>
        prop.parent ? !/node_modules\/(?!@mui)/.test(prop.parent.fileName) : true,
    }, 
Sawicki answered 15/7, 2024 at 16:20 Comment(0)

© 2022 - 2025 β€” McMap. All rights reserved.