Storybook: Storybook 4 + create-react-app 2 + Typescript: loader issue

Created on 6 Nov 2018  路  18Comments  路  Source: storybookjs/storybook

Describe the bug
I'm trying to setup a storybook using the guidelines here: https://storybook.js.org/configurations/typescript-config, but this doesn't seem to be accurate (anymore).

To Reproduce

npx create-react-app my-app
yarn add -D typescript @types/node @types/react @types/react-dom @types/jest

Rename react files from .js to .ts/.tsx

npx -p @storybook/cli sb init
yarn add -D awesome-typescript-loader
yarn add -D @types/storybook__react
yarn add -D @storybook/addon-info react-docgen-typescript-webpack-plugin

Results in following depedencies:

{
  "dependencies": {
    "react": "^16.6.0",
    "react-dom": "^16.6.0",
    "react-scripts": "2.1.1"
  },
  "devDependencies": {
    "@babel/core": "^7.1.2",
    "@storybook/addon-actions": "^4.0.4",
    "@storybook/addon-info": "^4.0.4",
    "@storybook/addon-links": "^4.0.4",
    "@storybook/addons": "^4.0.4",
    "@storybook/react": "^4.0.4",
    "@types/jest": "^23.3.9",
    "@types/node": "^10.12.2",
    "@types/react": "^16.4.18",
    "@types/react-dom": "^16.0.9",
    "@types/storybook__react": "^3.0.9",
    "awesome-typescript-loader": "^5.2.1",
    "babel-loader": "^8.0.4",
    "react-docgen-typescript-webpack-plugin": "^1.1.0",
    "typescript": "^3.1.6"
  }
}

Copy tsconfig.json + .storybook/webpack.config.js from https://storybook.js.org/configurations/typescript-config/

Add simple button component:

// src/components/button/Button.tsx

import * as React from "react";

interface ButtonProps {
    label: string
}
export const Button: React.SFC<ButtonProps> = (props) => {
    return <button>{props.label}</button>;
}

Add Storybook config

// .storybook/config.ts

import { configure } from '@storybook/react';

const req = require.context('../src/stories', true, /\.tsx$/);

configure(() => {
  req.keys().forEach(filename => req(filename));
}, module);

Add story

// src/stories/index.tsx

import React from 'react';

import { storiesOf } from '@storybook/react';
import { Button } from '../components/button/Button';

const stories = storiesOf('Button', module);

stories.add(
    'test', 
    () => <Button>Test</Button>,
);

Expected behavior
yarn storybook should mount a storybook with one story.

Screenshots
schermafbeelding 2018-11-06 om 16 53 32

Code snippets
I also had to remove "isolatedModules": true from tsconfig.json before I could get the storybook running.

System:

  • OS: MacOS
  • Browser: Chrome
  • Framework: react
  • Version: 4.0.4

Additional context
Storybook 4 + create-react-app 2.1 without typescript enabled works like a charm :)

react compatibility with other tools cra question / support typescript

Most helpful comment

@mohamedmansour this works like a charm indeed:

  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('babel-loader'),
    options: {
        presets: [['react-app', { flow: false, typescript: true }]]
      }
  });

no need for awesome-typescript-loader anymore.

All 18 comments

CRA 2.0 typescript config wants "jsx": "preserve" so that it can use babel to transform the JSX. But storybook wants "jsx": "react".

Okay @ashutoshrishi , thanks for clearing that out.

It seems like yarn start in a CRA 2 app alters tsconfig.json and thus overwrites the suggested Storybook tsconfig:

cra

Is there a way to use the same config for Storybook as CRA 2?

Found the solution here: https://vincenttunru.com/migrate-create-react-app-typescript-to-create-react-app: a custom tsconfig.json for Storybook.

// .storybook/tsconfig.json

{
    "extends": "../tsconfig",
    "compilerOptions": {
      "jsx": "react",
      "isolatedModules": false,
      "noEmit": false
  }
}
// .storybook/webpack.config.js

const path = require('path');
const TSDocgenPlugin = require('react-docgen-typescript-webpack-plugin');
module.exports = (baseConfig, env, config) => {
  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('awesome-typescript-loader'),
    options: { configFileName: path.resolve(__dirname, './tsconfig.json') } // << added 
  });
  config.plugins.push(new TSDocgenPlugin()); // optional
  config.resolve.extensions.push('.ts', '.tsx');
  return config;
};

This is incorrect, please follow my guide #4763 you shouldn't use awesome-typescript-loader you should use babel-loader which is inside cra2

@mohamedmansour this works like a charm indeed:

  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('babel-loader'),
    options: {
        presets: [['react-app', { flow: false, typescript: true }]]
      }
  });

no need for awesome-typescript-loader anymore.

For CRA using react-scripts-ts(Microsoft/TypeScript-React-Starter), I had to install:
yarn add -D @babel/preset-typescript

And use the following .storybook/webpack.config.js

module.exports = (baseConfig, env, config) => {
    config.module.rules.push({
        test: /\.(ts|tsx)$/,
        loader: require.resolve('babel-loader'),
        options: {
            presets: ['@babel/preset-react', '@babel/preset-typescript']
        }
    });
    config.resolve.extensions.push('.ts', '.tsx');
    return config;
};

@ibrambe

@mohamedmansour this works like a charm indeed:

  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('babel-loader'),
    options: {
        presets: [['react-app', { flow: false, typescript: true }]]
      }
  });

no need for awesome-typescript-loader anymore.

It semi-works - It indeed compiles TypeScript but it also doesn't do any type validation, e.g:

const a: number = "d";

In a story works without any error.

you can use fork-ts-checker-webpack-plugin for this.

@vitalybe @igor-dv

Upgraded to storybook 4.0.6 and using this as webpack.config.js:

// .storybook/webpack.config.js

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt');

module.exports = (baseConfig, env, config) => {
  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('babel-loader'),
    options: {
        presets: [require.resolve('babel-preset-react-app')]
      }
  });

  config.resolve.extensions.push('.ts', '.tsx');

  config.plugins.push(
    new ForkTsCheckerWebpackPlugin({
        async: false,
        checkSyntacticErrors: true,
        formatter: require('react-dev-utils/typescriptFormatter'),
      }),
  );

  return config;
};

This works and has live type validation, it's based on the config in react-scripts, so all dependencies are already loaded if you use CRA 2.

@ibrambe: why are you using fork-ts-checker-webpack-plugin-alt instead of fork-ts-checker-webpack-plugin?

@ibrambe: why are you using fork-ts-checker-webpack-plugin-alt instead of fork-ts-checker-webpack-plugin?

I have no clue, wasn't on purpose :)

@ibrambe how to configure babel-loader with custom tsconfig.json path? I can't find it.

After following the example here

Cannot find module 'react-scripts\config\webpack.config.dev'

using packages

{
...,
"@storybook/react": "^4.1.3",
"@storybook/addon-actions": "^4.1.3",
"react-docgen-typescript-webpack-plugin": "^1.1.0",
"react-scripts": "2.1.2",
...
}

Has anyone encountered this issue also?

Has anyone encountered this issue also?

@harry-sm Same here, using 4.0.x instead of 4.1.x worked

@osi-oswald
4.0.x works thanks.

In webpack.config.js, I was getting

TypeScript error: Cannot compile namespaces when the '--isolatedModules' flag is provided. TS1208

Worked around this by adding
export function dummy() {}

to the top of that file. It would make more sense to tell CRA to ignore .stories/, but I don't know how.

@mohamedmansour this works like a charm indeed:

  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('babel-loader'),
    options: {
        presets: [['react-app', { flow: false, typescript: true }]]
      }
  });

no need for awesome-typescript-loader anymore.

Hi, @mohamedmansour @ibrambe. This worked for me! Can you please explain this? Didn't quite get my head around this :/

This works:

module.exports = ({config}) => {
  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('react-scripts/node_modules/babel-loader'),
    options: {
        presets: [require.resolve('babel-preset-react-app')]
      }
  });

  config.resolve.extensions.push('.ts', '.tsx');
Was this page helpful?
0 / 5 - 0 ratings