Typescript Config

Edit this page

This is a central reference for using Storybook with Typescript.

Dependencies you may need

yarn add -D typescript
yarn add -D awesome-typescript-loader
yarn add -D @storybook/addon-info react-docgen-typescript-webpack-plugin # optional but recommended
yarn add -D jest "@types/jest" ts-jest #testing

We have had the best experience using awesome-typescript-loader, but other tutorials may use ts-loader, just configure accordingly. You can even use babel-loader with a ts-loader configuration.

Setting up Typescript to work with Storybook

We first have to use the custom Webpack config in full control mode, extending default configs:

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")
  });
  config.plugins.push(new TSDocgenPlugin()); // optional
  config.resolve.extensions.push(".ts", ".tsx");
  return config;
};

The above example shows a working config with the TSDocgen plugin also integrated; remove the optional sections if you don’t plan on using them.

tsconfig.json

{
  "compilerOptions": {
    "outDir": "build/lib",
    "module": "commonjs",
    "target": "es5",
    "lib": ["es5", "es6", "es7", "es2017", "dom"],
    "sourceMap": true,
    "allowJs": false,
    "jsx": "react",
    "moduleResolution": "node",
    "rootDirs": ["src", "stories"],
    "baseUrl": "src",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "suppressImplicitAnyIndexErrors": true,
    "noUnusedLocals": true,
    "declaration": true,
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "build", "scripts"]
}

This is for the default configuration where /stories is a peer of src. If you have them all in just src you may wish to replace "rootDirs": ["src", "stories"] above with "rootDir": "src",.

Using Typescript with the TSDocgen addon

The very handy Storybook Info addon autogenerates prop tables documentation for each component, however it doesn’t work with Typescript types. The current solution is to use react-docgen-typescript-loader to preprocess the Typescript files to give the Info addon what it needs. The webpack config above does this, and so for the rest of your stories you use it as per normal:

import React from "react";
import { storiesOf } from "@storybook/react";
import { withInfo } from "@storybook/addon-info";
import { action } from "@storybook/addon-actions";
import TicTacToeCell from "./TicTacToeCell";

const stories = storiesOf("Components", module);

stories.add(
  "TicTacToeCell",
  withInfo({ inline: true })(() => <TicTacToeCell value="X" position={{ x: 0, y: 0 }} onClick={action("onClick")} />)
);

Customizing Type annotations/descriptions

Please refer to the react-docgen-typescript-loader docs for writing prop descriptions and other annotations to your Typescript interfaces.

Additional annotation can be achieved by creating a wInfo higher order component:

import { withInfo } from "@storybook/addon-info";
const wInfoStyle = {
  header: {
    h1: {
      marginRight: "20px",
      fontSize: "25px",
      display: "inline"
    },
    body: {
      paddingTop: 0,
      paddingBottom: 0
    },
    h2: {
      display: "inline",
      color: "#999"
    }
  },
  infoBody: {
    backgroundColor: "#eee",
    padding: "0px 5px",
    lineHeight: "2"
  }
};
export const wInfo = text => withInfo({ inline: true, source: false, styles: wInfoStyle, text: text });

This can be used like so:

import React from "react";

import { storiesOf } from "@storybook/react";
import { PrimaryButton } from "./Button";
import { wInfo } from "../../utils";
import { text, select, boolean } from "@storybook/addon-knobs/react";

storiesOf("Components/Button", module).addWithJSX(
  "basic PrimaryButton",
  wInfo(`

  ### Notes

  light button seen on <https://zpl.io/aM49ZBd>

  ### Usage
  ~~~js
  <PrimaryButton
    label={text('label', 'Enroll')}
    disabled={boolean('disabled',false)}
    onClick={() => alert('hello there')}
  />
  ~~~

`)(() => (
    <PrimaryButton
      label={text("label", "Enroll")}
      disabled={boolean("disabled", false)}
      onClick={() => alert("hello there")}
    />
  ))
);

And this is how it looks:

image

Note: Component docgen information can not be generated for components that are only exported as default. You can work around the issue by exporting the component using a named export.

Setting up Jest tests

The ts-jest README explains pretty clearly how to get Jest to recognize TypeScript code.

This is an example package.json config for jest:

"jest": {
  "transform": {
    ".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
  },
  "mapCoverage": true,
  "testPathIgnorePatterns": [
    "/node_modules/",
    "/lib/"
  ],
  "testRegex": "(/test/.*|\\.(test|spec))\\.(ts|tsx|js)$",
  "moduleFileExtensions": [
    "ts",
    "tsx",
    "js",
    "json"
  ]
}

Building your Typescript Storybook

You will need to set up some scripts - these may help:

  "scripts": {
    "start": "react-scripts-ts start",
    "build": "npm run lint && npm run build-lib && build-storybook",
    "build-lib-watch": "tsc -w",
    "build-lib": "tsc && npm run copy-css-to-lib && npm run copy-svg-to-lib && npm run copy-png-to-lib && npm run copy-woff2-to-lib",
    "test": "react-scripts-ts test --env=jsdom",
    "test:coverage": "npm test -- --coverage",
    "eject": "react-scripts-ts eject",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "copy-css-to-lib": "cpx \"./src/**/*.css\" ./build/lib",
    "copy-woff2-to-lib": "cpx \"./src/**/*.woff2\" ./build/lib",
    "copy-svg-to-lib": "cpx \"./src/**/*.svg\" ./build/lib",
    "copy-png-to-lib": "cpx \"./src/**/*.png\" ./build/lib",
    "lint": "tslint -c tslint.json 'src/**/*.{ts,tsx}'"
  },