Storybook + antd less loader

Viewed 92

I've been using storybook-addon-customize-antd-theme for a while, but it seems unmaintained and I realize I am never using it to customize the theme anyway, just load the less files. I've also been recently running into build issues that are solved by removing the add on.

I've been trying to replace it by just using the appropriate webpack loader config like this:

addons: [
    ...,
    {
      "name": "@storybook/preset-create-react-app",
      "options": {
        "craOverrides": {
          "fileLoaderExcludes": ["less"]
        }
      }
    }
],
webpackFinal: async (config) => {
    config.module.rules.push({
      // this is for both less and scss
      test: /.*\.(?:le|c)ss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            modules: false
          }
        },
        {
          loader: 'less-loader',
          options: {
            lessOptions: {
              javascriptEnabled: true
            }
          }
        }
      ]
    });
    return config 
}

But I am receiving this error:

// https://github.com/ant-design/ant-motion/issues/44
.bezierEasingMixin();
^
Inline JavaScript is not enabled. Is it set in your options?

Which makes it seem like the rule isn't being obeyed, because I have javascriptEnabled: true in the less loader.

What gives?

1 Answers

Thx for your config, my storybook finally works ok. I am not sure, if it will be helpfull, but I will share my config. So the main aim for me was, storybook + react + typescript + less + normal component styles import + classnames. My files structure and content:

enter image description here

package.json

    {
      "name": "storybook",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "@storybook/addon-postcss": "^2.0.0",
        "@testing-library/jest-dom": "^5.16.4",
        "@testing-library/react": "^13.3.0",
        "@testing-library/user-event": "^13.5.0",
        "@types/jest": "^27.5.2",
        "@types/node": "^16.11.45",
        "@types/react": "^18.0.15",
        "@types/react-dom": "^18.0.6",
        "classnames": "^2.3.1",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "react-scripts": "5.0.1",
        "styled-components": "^5.3.5",
        "typescript": "^4.7.4",
        "web-vitals": "^2.1.4"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject",
        "storybook": "start-storybook -p 6006 -s public",
        "build-storybook": "build-storybook -s public"
      },
      "eslintConfig": {
        "extends": [
          "react-app",
          "react-app/jest"
        ],
        "overrides": [
          {
            "files": [
              "**/*.stories.*"
            ],
            "rules": {
              "import/no-anonymous-default-export": "off"
            }
          }
        ]
      },
      "browserslist": {
        "production": [
          ">0.2%",
          "not dead",
          "not op_mini all"
        ],
        "development": [
          "last 1 chrome version",
          "last 1 firefox version",
          "last 1 safari version"
        ]
      },
      "devDependencies": {
        "@storybook/addon-actions": "^6.5.9",
        "@storybook/addon-essentials": "^6.5.9",
        "@storybook/addon-interactions": "^6.5.9",
        "@storybook/addon-links": "^6.5.9",
        "@storybook/builder-webpack5": "^6.5.9",
        "@storybook/manager-webpack5": "^6.5.9",
        "@storybook/node-logger": "^6.5.9",
        "@storybook/preset-create-react-app": "^4.1.2",
        "@storybook/react": "^6.5.9",
        "@storybook/testing-library": "^0.0.13",
        "autoprefixer": "^10.4.7",
        "babel-plugin-named-exports-order": "^0.0.2",
        "css-loader": "^6.7.1",
        "less": "^4.1.3",
        "less-loader": "^11.0.0",
        "postcss-loader": "^7.0.1",
        "prop-types": "^15.8.1",
        "storybook-addon-designs": "^6.3.1",
        "storybook-addon-pseudo-states": "^1.15.1",
        "style-loader": "^3.3.1",
        "stylelint": "^14.9.1",
        "stylelint-config-standard": "^26.0.0",
        "webpack": "^5.73.0"
      }
    }

main.js

const path = require('path');

module.exports = {
    "stories": [
        "../src/**/*.stories.mdx",
        "../src/**/*.stories.@(js|jsx|ts|tsx)"
    ],
    "addons": [
        "@storybook/addon-links",
        "@storybook/addon-essentials",
        "@storybook/addon-interactions",
        "storybook-addon-designs",
        "storybook-addon-pseudo-states",
        {
            "name": "@storybook/preset-create-react-app",
            "options": {
                "craOverrides": {
                    "fileLoaderExcludes": ["less"]
                }
            }
        }
    ],
    "framework": "@storybook/react",
    "core": {
        "builder": "@storybook/builder-webpack5"
    },
    webpackFinal: async (config, { configType }) => {
        // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
        // You can change the configuration based on that.
        // 'PRODUCTION' is used when building the static version of storybook.

        // Make whatever fine-grained changes you need
        config.module.rules.push({
            test: /\.less$/,
            include: path.resolve(__dirname, '../'),
            sideEffects: true,
            exclude: /node_modules/,
            use: [
                'style-loader',
                {
                    loader: 'css-loader',
                    options: {
                        modules: true,
                        modules: {
                            auto: true,
                            localIdentName: '[path][name]__[local]--[hash:base64:5]'
                        },
                        importLoaders: 2,
                    }
                },
                'less-loader',
                {
                    loader: 'postcss-loader',
                    options: {
                        postcssOptions: {
                            ident: 'postcss',
                            plugins: [
                                require('stylelint')(),
                                require('autoprefixer')()
                            ]
                        }
                    }
                }
            ]
        });

        // Return the altered config
        return config;
    },
}

Button.stories.tsx

import { ComponentStory, ComponentMeta } from '@storybook/react';

import { Button } from './Button';

// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
  title: 'Example/Button',
  component: Button,
  // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
  argTypes: {
  },
} as ComponentMeta<typeof Button>;

// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;

export const Primary = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Primary.args = {
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

Button.tsx

import React from 'react';
// import './button.css';
import classnames                   from 'classnames/bind';
import styles                       from './ButtonStyles.less'
// import './ButtonStyles.css';

interface ButtonProps {
  type?: 'primary' | 'secondary' | 'cta' | 'secondaryOnlyText' | 'distractive' | 'social';
  /**
   * How large should the button be?
   */
  size?: 'small' | 'medium' | 'primary' | 'large';
  /**
   * Button contents
   */
  label: string;
  /**
   * Optional click handler
   */
  onClick?: () => void;
}

/**
 * Primary UI component for user interaction
 */
const cx = classnames.bind(styles);

export const Button = ({
  type = 'primary',
  size = 'medium',
  label,
  ...props
}: ButtonProps) => {

const buttonStyles = cx({
  storybookButton: true
})

  return (
    <button
      type="button"
      // className="storybookButton"
      className={buttonStyles}
      {...props}
    >
      {label}
    </button>
  );
};

ButtonStyles.less

.storybookButton {
  font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: 700;
  border: 0;
  border-radius: 3em;
  cursor: pointer;
  display: inline-block;
  line-height: 1;
  background-color: yellow;
  color: red;
}
.storybook-button--primary {
  color: white;
  background-color: #1ea7fd;
}
.storybook-button--secondary {
  color: #333;
  background-color: transparent;
  box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
}
.storybook-button--small {
  font-size: 12px;
  padding: 10px 16px;
}
.storybook-button--medium {
  font-size: 14px;
  padding: 11px 20px;
}
.storybook-button--large {
  font-size: 16px;
  padding: 12px 24px;
}
Related